pyglove 0.5.0.dev202510230131__py3-none-any.whl → 0.5.0.dev202511300809__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.
- pyglove/core/io/file_system.py +295 -2
- pyglove/core/io/file_system_test.py +291 -0
- pyglove/core/symbolic/__init__.py +7 -0
- pyglove/core/symbolic/base.py +24 -9
- pyglove/core/symbolic/list_test.py +1 -2
- pyglove/core/symbolic/object.py +2 -1
- pyglove/core/symbolic/object_test.py +13 -10
- pyglove/core/symbolic/unknown_symbols.py +147 -0
- pyglove/core/symbolic/unknown_symbols_test.py +100 -0
- pyglove/core/typing/annotation_conversion.py +3 -0
- pyglove/core/typing/annotation_conversion_test.py +11 -19
- pyglove/core/typing/type_conversion.py +17 -3
- pyglove/core/typing/type_conversion_test.py +7 -2
- pyglove/core/typing/value_specs.py +5 -1
- pyglove/core/typing/value_specs_test.py +5 -0
- pyglove/core/utils/json_conversion.py +107 -49
- pyglove/core/utils/json_conversion_test.py +85 -10
- pyglove/core/views/html/controls/tab.py +33 -0
- pyglove/core/views/html/controls/tab_test.py +37 -0
- pyglove/ext/evolution/base_test.py +1 -1
- {pyglove-0.5.0.dev202510230131.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/METADATA +8 -1
- {pyglove-0.5.0.dev202510230131.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/RECORD +25 -23
- {pyglove-0.5.0.dev202510230131.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/WHEEL +0 -0
- {pyglove-0.5.0.dev202510230131.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/licenses/LICENSE +0 -0
- {pyglove-0.5.0.dev202510230131.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/top_level.txt +0 -0
pyglove/core/io/file_system.py
CHANGED
|
@@ -14,8 +14,11 @@
|
|
|
14
14
|
"""Pluggable file system."""
|
|
15
15
|
|
|
16
16
|
import abc
|
|
17
|
+
import fnmatch
|
|
18
|
+
import glob as std_glob
|
|
17
19
|
import io
|
|
18
20
|
import os
|
|
21
|
+
import re
|
|
19
22
|
from typing import Any, Literal, Optional, Union
|
|
20
23
|
|
|
21
24
|
|
|
@@ -84,6 +87,10 @@ class FileSystem(metaclass=abc.ABCMeta):
|
|
|
84
87
|
def exists(self, path: Union[str, os.PathLike[str]]) -> bool:
|
|
85
88
|
"""Returns True if a path exists."""
|
|
86
89
|
|
|
90
|
+
@abc.abstractmethod
|
|
91
|
+
def glob(self, pattern: Union[str, os.PathLike[str]]) -> list[str]:
|
|
92
|
+
"""Lists all files or sub-directories."""
|
|
93
|
+
|
|
87
94
|
@abc.abstractmethod
|
|
88
95
|
def listdir(self, path: Union[str, os.PathLike[str]]) -> list[str]:
|
|
89
96
|
"""Lists all files or sub-directories."""
|
|
@@ -111,6 +118,14 @@ class FileSystem(metaclass=abc.ABCMeta):
|
|
|
111
118
|
def rm(self, path: Union[str, os.PathLike[str]]) -> None:
|
|
112
119
|
"""Removes a file based on a path."""
|
|
113
120
|
|
|
121
|
+
@abc.abstractmethod
|
|
122
|
+
def rename(
|
|
123
|
+
self,
|
|
124
|
+
oldpath: Union[str, os.PathLike[str]],
|
|
125
|
+
newpath: Union[str, os.PathLike[str]],
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Renames a file based on a path."""
|
|
128
|
+
|
|
114
129
|
@abc.abstractmethod
|
|
115
130
|
def rmdir(self, path: Union[str, os.PathLike[str]]) -> bool:
|
|
116
131
|
"""Removes a directory based on a path."""
|
|
@@ -137,9 +152,10 @@ def resolve_path(path: Union[str, os.PathLike[str]]) -> str:
|
|
|
137
152
|
class StdFile(File):
|
|
138
153
|
"""The standard file."""
|
|
139
154
|
|
|
140
|
-
def __init__(self, file_object) -> None:
|
|
155
|
+
def __init__(self, file_object, path: Union[str, os.PathLike[str]]) -> None:
|
|
141
156
|
super().__init__()
|
|
142
157
|
self._file_object = file_object
|
|
158
|
+
self._path = path
|
|
143
159
|
|
|
144
160
|
def read(self, size: Optional[int] = None) -> Union[str, bytes]:
|
|
145
161
|
return self._file_object.read(size)
|
|
@@ -169,7 +185,7 @@ class StdFileSystem(FileSystem):
|
|
|
169
185
|
def open(
|
|
170
186
|
self, path: Union[str, os.PathLike[str]], mode: str = 'r', **kwargs
|
|
171
187
|
) -> File:
|
|
172
|
-
return StdFile(io.open(path, mode, **kwargs))
|
|
188
|
+
return StdFile(io.open(path, mode, **kwargs), path)
|
|
173
189
|
|
|
174
190
|
def chmod(self, path: Union[str, os.PathLike[str]], mode: int) -> None:
|
|
175
191
|
os.chmod(path, mode)
|
|
@@ -177,6 +193,9 @@ class StdFileSystem(FileSystem):
|
|
|
177
193
|
def exists(self, path: Union[str, os.PathLike[str]]) -> bool:
|
|
178
194
|
return os.path.exists(path)
|
|
179
195
|
|
|
196
|
+
def glob(self, pattern: Union[str, os.PathLike[str]]) -> list[str]:
|
|
197
|
+
return std_glob.glob(pattern)
|
|
198
|
+
|
|
180
199
|
def listdir(self, path: Union[str, os.PathLike[str]]) -> list[str]:
|
|
181
200
|
return os.listdir(path)
|
|
182
201
|
|
|
@@ -196,6 +215,13 @@ class StdFileSystem(FileSystem):
|
|
|
196
215
|
) -> None:
|
|
197
216
|
os.makedirs(path, mode, exist_ok)
|
|
198
217
|
|
|
218
|
+
def rename(
|
|
219
|
+
self,
|
|
220
|
+
oldpath: Union[str, os.PathLike[str]],
|
|
221
|
+
newpath: Union[str, os.PathLike[str]],
|
|
222
|
+
) -> None:
|
|
223
|
+
os.rename(oldpath, newpath)
|
|
224
|
+
|
|
199
225
|
def rm(self, path: Union[str, os.PathLike[str]]) -> None:
|
|
200
226
|
os.remove(path)
|
|
201
227
|
|
|
@@ -286,6 +312,21 @@ class MemoryFileSystem(FileSystem):
|
|
|
286
312
|
def exists(self, path: Union[str, os.PathLike[str]]) -> bool:
|
|
287
313
|
return self._locate(path) is not None
|
|
288
314
|
|
|
315
|
+
def glob(self, pattern: Union[str, os.PathLike[str]]) -> list[str]:
|
|
316
|
+
pattern = resolve_path(pattern)
|
|
317
|
+
regex = re.compile(fnmatch.translate(pattern))
|
|
318
|
+
|
|
319
|
+
results = []
|
|
320
|
+
def _traverse(node, prefix_parts):
|
|
321
|
+
for k, v in node.items():
|
|
322
|
+
path = self._prefix + '/'.join(prefix_parts + [k])
|
|
323
|
+
if regex.match(path):
|
|
324
|
+
results.append(path)
|
|
325
|
+
if isinstance(v, dict):
|
|
326
|
+
_traverse(v, prefix_parts + [k])
|
|
327
|
+
_traverse(self._root, [])
|
|
328
|
+
return results
|
|
329
|
+
|
|
289
330
|
def listdir(self, path: Union[str, os.PathLike[str]]) -> list[str]:
|
|
290
331
|
d = self._locate(path)
|
|
291
332
|
if not isinstance(d, dict):
|
|
@@ -338,6 +379,46 @@ class MemoryFileSystem(FileSystem):
|
|
|
338
379
|
raise NotADirectoryError(path)
|
|
339
380
|
current = entry
|
|
340
381
|
|
|
382
|
+
def rename(
|
|
383
|
+
self,
|
|
384
|
+
oldpath: Union[str, os.PathLike[str]],
|
|
385
|
+
newpath: Union[str, os.PathLike[str]],
|
|
386
|
+
) -> None:
|
|
387
|
+
oldpath_str = resolve_path(oldpath)
|
|
388
|
+
newpath_str = resolve_path(newpath)
|
|
389
|
+
|
|
390
|
+
old_parent_dir, old_name = self._parent_and_name(oldpath_str)
|
|
391
|
+
entry = old_parent_dir.get(old_name)
|
|
392
|
+
|
|
393
|
+
if entry is None:
|
|
394
|
+
raise FileNotFoundError(oldpath_str)
|
|
395
|
+
|
|
396
|
+
new_entry = self._locate(newpath_str)
|
|
397
|
+
if new_entry is not None:
|
|
398
|
+
if isinstance(entry, dict): # oldpath is dir
|
|
399
|
+
if not isinstance(new_entry, dict):
|
|
400
|
+
raise NotADirectoryError(
|
|
401
|
+
f'Cannot rename directory {oldpath_str!r} '
|
|
402
|
+
f'to non-directory {newpath_str!r}')
|
|
403
|
+
elif new_entry:
|
|
404
|
+
raise OSError(f'Directory not empty: {newpath_str!r}')
|
|
405
|
+
else:
|
|
406
|
+
self.rmdir(newpath_str)
|
|
407
|
+
else: # oldpath is file
|
|
408
|
+
if isinstance(new_entry, dict):
|
|
409
|
+
raise IsADirectoryError(
|
|
410
|
+
f'Cannot rename non-directory {oldpath_str!r} '
|
|
411
|
+
f'to directory {newpath_str!r}')
|
|
412
|
+
else:
|
|
413
|
+
self.rm(newpath_str)
|
|
414
|
+
elif isinstance(entry, dict) and newpath_str.startswith(oldpath_str + '/'):
|
|
415
|
+
raise OSError(f'Cannot move directory {oldpath_str!r} '
|
|
416
|
+
f'to a subdirectory of itself {newpath_str!r}')
|
|
417
|
+
|
|
418
|
+
new_parent_dir, new_name = self._parent_and_name(newpath_str)
|
|
419
|
+
new_parent_dir[new_name] = entry
|
|
420
|
+
del old_parent_dir[old_name]
|
|
421
|
+
|
|
341
422
|
def rm(self, path: Union[str, os.PathLike[str]]) -> None:
|
|
342
423
|
parent_dir, name = self._parent_and_name(path)
|
|
343
424
|
entry = parent_dir.get(name)
|
|
@@ -412,6 +493,205 @@ def add_file_system(prefix: str, fs: FileSystem) -> None:
|
|
|
412
493
|
add_file_system('/mem/', MemoryFileSystem('/mem/'))
|
|
413
494
|
|
|
414
495
|
|
|
496
|
+
try:
|
|
497
|
+
# pylint: disable=g-import-not-at-top
|
|
498
|
+
# pytype: disable=import-error
|
|
499
|
+
import fsspec
|
|
500
|
+
# pytype: enable=import-error
|
|
501
|
+
# pylint: enable=g-import-not-at-top
|
|
502
|
+
except ImportError:
|
|
503
|
+
fsspec = None
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
class FsspecFile(File):
|
|
507
|
+
"""File object based on fsspec."""
|
|
508
|
+
|
|
509
|
+
def __init__(self, f):
|
|
510
|
+
self._f = f
|
|
511
|
+
|
|
512
|
+
def read(self, size: Optional[int] = None) -> Union[str, bytes]:
|
|
513
|
+
return self._f.read(size)
|
|
514
|
+
|
|
515
|
+
def readline(self) -> Union[str, bytes]:
|
|
516
|
+
return self._f.readline()
|
|
517
|
+
|
|
518
|
+
def write(self, content: Union[str, bytes]) -> None:
|
|
519
|
+
self._f.write(content)
|
|
520
|
+
|
|
521
|
+
def seek(self, offset: int, whence: Literal[0, 1, 2] = 0) -> int:
|
|
522
|
+
return self._f.seek(offset, whence)
|
|
523
|
+
|
|
524
|
+
def tell(self) -> int:
|
|
525
|
+
return self._f.tell()
|
|
526
|
+
|
|
527
|
+
def flush(self) -> None:
|
|
528
|
+
self._f.flush()
|
|
529
|
+
|
|
530
|
+
def close(self) -> None:
|
|
531
|
+
self._f.close()
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
class FsspecFileSystem(FileSystem):
|
|
535
|
+
"""File system based on fsspec."""
|
|
536
|
+
|
|
537
|
+
def open(
|
|
538
|
+
self, path: Union[str, os.PathLike[str]], mode: str = 'r', **kwargs
|
|
539
|
+
) -> File:
|
|
540
|
+
assert fsspec is not None, '`fsspec` is not installed.'
|
|
541
|
+
return FsspecFile(fsspec.open(path, mode, **kwargs).open())
|
|
542
|
+
|
|
543
|
+
def chmod(self, path: Union[str, os.PathLike[str]], mode: int) -> None:
|
|
544
|
+
assert fsspec is not None, '`fsspec` is not installed.'
|
|
545
|
+
fs, path = fsspec.core.url_to_fs(path)
|
|
546
|
+
if hasattr(fs, 'chmod'):
|
|
547
|
+
fs.chmod(path, mode)
|
|
548
|
+
|
|
549
|
+
def exists(self, path: Union[str, os.PathLike[str]]) -> bool:
|
|
550
|
+
assert fsspec is not None, '`fsspec` is not installed.'
|
|
551
|
+
fs, path = fsspec.core.url_to_fs(path)
|
|
552
|
+
return fs.exists(path)
|
|
553
|
+
|
|
554
|
+
def glob(self, pattern: Union[str, os.PathLike[str]]) -> list[str]:
|
|
555
|
+
assert fsspec is not None, '`fsspec` is not installed.'
|
|
556
|
+
fs, path = fsspec.core.url_to_fs(pattern)
|
|
557
|
+
protocol = fsspec.utils.get_protocol(pattern)
|
|
558
|
+
return [f'{protocol}:///{r.lstrip("/")}' for r in fs.glob(path)]
|
|
559
|
+
|
|
560
|
+
def listdir(self, path: Union[str, os.PathLike[str]]) -> list[str]:
|
|
561
|
+
assert fsspec is not None, '`fsspec` is not installed.'
|
|
562
|
+
fs, path = fsspec.core.url_to_fs(path)
|
|
563
|
+
return [os.path.basename(f) for f in fs.ls(path, detail=False)]
|
|
564
|
+
|
|
565
|
+
def isdir(self, path: Union[str, os.PathLike[str]]) -> bool:
|
|
566
|
+
assert fsspec is not None, '`fsspec` is not installed.'
|
|
567
|
+
fs, path = fsspec.core.url_to_fs(path)
|
|
568
|
+
return fs.isdir(path)
|
|
569
|
+
|
|
570
|
+
def mkdir(
|
|
571
|
+
self,
|
|
572
|
+
path: Union[str, os.PathLike[str]], mode: int = 0o777
|
|
573
|
+
) -> None:
|
|
574
|
+
assert fsspec is not None, '`fsspec` is not installed.'
|
|
575
|
+
fs, path = fsspec.core.url_to_fs(path)
|
|
576
|
+
fs.mkdir(path)
|
|
577
|
+
|
|
578
|
+
def mkdirs(
|
|
579
|
+
self,
|
|
580
|
+
path: Union[str, os.PathLike[str]],
|
|
581
|
+
mode: int = 0o777,
|
|
582
|
+
exist_ok: bool = True,
|
|
583
|
+
) -> None:
|
|
584
|
+
assert fsspec is not None, '`fsspec` is not installed.'
|
|
585
|
+
fs, path = fsspec.core.url_to_fs(path)
|
|
586
|
+
fs.makedirs(path, exist_ok=exist_ok)
|
|
587
|
+
|
|
588
|
+
def rm(self, path: Union[str, os.PathLike[str]]) -> None:
|
|
589
|
+
assert fsspec is not None, '`fsspec` is not installed.'
|
|
590
|
+
fs, path = fsspec.core.url_to_fs(path)
|
|
591
|
+
fs.rm(path)
|
|
592
|
+
|
|
593
|
+
def rename(
|
|
594
|
+
self,
|
|
595
|
+
oldpath: Union[str, os.PathLike[str]],
|
|
596
|
+
newpath: Union[str, os.PathLike[str]],
|
|
597
|
+
) -> None:
|
|
598
|
+
assert fsspec is not None, '`fsspec` is not installed.'
|
|
599
|
+
fs, old_path = fsspec.core.url_to_fs(oldpath)
|
|
600
|
+
fs2, new_path = fsspec.core.url_to_fs(newpath)
|
|
601
|
+
if fs.__class__ != fs2.__class__:
|
|
602
|
+
raise ValueError(
|
|
603
|
+
f'Rename across different filesystems is not supported: '
|
|
604
|
+
f'{type(fs)} vs {type(fs2)}'
|
|
605
|
+
)
|
|
606
|
+
fs.rename(old_path, new_path)
|
|
607
|
+
|
|
608
|
+
def rmdir(self, path: Union[str, os.PathLike[str]]) -> None: # pytype: disable=signature-mismatch
|
|
609
|
+
assert fsspec is not None, '`fsspec` is not installed.'
|
|
610
|
+
fs, path = fsspec.core.url_to_fs(path)
|
|
611
|
+
fs.rmdir(path)
|
|
612
|
+
|
|
613
|
+
def rmdirs(self, path: Union[str, os.PathLike[str]]) -> None:
|
|
614
|
+
assert fsspec is not None, '`fsspec` is not installed.'
|
|
615
|
+
fs, path = fsspec.core.url_to_fs(path)
|
|
616
|
+
fs.rm(path, recursive=True)
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
class _FsspecUriCatcher(FileSystem):
|
|
620
|
+
"""File system to catch URI paths and redirect to FsspecFileSystem."""
|
|
621
|
+
|
|
622
|
+
# Catch all paths that contains '://' but not registered by
|
|
623
|
+
# available_protocols.
|
|
624
|
+
_URI_PATTERN = re.compile(r'^[a-zA-Z][a-zA-Z0-9+-.]*://.*')
|
|
625
|
+
|
|
626
|
+
def __init__(self):
|
|
627
|
+
super().__init__()
|
|
628
|
+
self._std_fs = StdFileSystem()
|
|
629
|
+
self._fsspec_fs = FsspecFileSystem()
|
|
630
|
+
|
|
631
|
+
def get_fs(self, path: Union[str, os.PathLike[str]]) -> FileSystem:
|
|
632
|
+
if self._URI_PATTERN.match(resolve_path(path)):
|
|
633
|
+
return self._fsspec_fs
|
|
634
|
+
return self._std_fs
|
|
635
|
+
|
|
636
|
+
def open(
|
|
637
|
+
self, path: Union[str, os.PathLike[str]], mode: str = 'r', **kwargs
|
|
638
|
+
) -> File:
|
|
639
|
+
return self.get_fs(path).open(path, mode, **kwargs)
|
|
640
|
+
|
|
641
|
+
def chmod(self, path: Union[str, os.PathLike[str]], mode: int) -> None:
|
|
642
|
+
self.get_fs(path).chmod(path, mode)
|
|
643
|
+
|
|
644
|
+
def exists(self, path: Union[str, os.PathLike[str]]) -> bool:
|
|
645
|
+
return self.get_fs(path).exists(path)
|
|
646
|
+
|
|
647
|
+
def glob(self, pattern: Union[str, os.PathLike[str]]) -> list[str]:
|
|
648
|
+
return self.get_fs(pattern).glob(pattern)
|
|
649
|
+
|
|
650
|
+
def listdir(self, path: Union[str, os.PathLike[str]]) -> list[str]:
|
|
651
|
+
return self.get_fs(path).listdir(path)
|
|
652
|
+
|
|
653
|
+
def isdir(self, path: Union[str, os.PathLike[str]]) -> bool:
|
|
654
|
+
return self.get_fs(path).isdir(path)
|
|
655
|
+
|
|
656
|
+
def mkdir(
|
|
657
|
+
self,
|
|
658
|
+
path: Union[str, os.PathLike[str]],
|
|
659
|
+
mode: int = 0o777
|
|
660
|
+
) -> None:
|
|
661
|
+
self.get_fs(path).mkdir(path, mode)
|
|
662
|
+
|
|
663
|
+
def mkdirs(
|
|
664
|
+
self,
|
|
665
|
+
path: Union[str, os.PathLike[str]],
|
|
666
|
+
mode: int = 0o777,
|
|
667
|
+
exist_ok: bool = True,
|
|
668
|
+
) -> None:
|
|
669
|
+
self.get_fs(path).mkdirs(path, mode, exist_ok)
|
|
670
|
+
|
|
671
|
+
def rm(self, path: Union[str, os.PathLike[str]]) -> None:
|
|
672
|
+
self.get_fs(path).rm(path)
|
|
673
|
+
|
|
674
|
+
def rename(
|
|
675
|
+
self,
|
|
676
|
+
oldpath: Union[str, os.PathLike[str]],
|
|
677
|
+
newpath: Union[str, os.PathLike[str]],
|
|
678
|
+
) -> None:
|
|
679
|
+
self.get_fs(oldpath).rename(oldpath, newpath)
|
|
680
|
+
|
|
681
|
+
def rmdir(self, path: Union[str, os.PathLike[str]]) -> None: # pytype: disable=signature-mismatch
|
|
682
|
+
self.get_fs(path).rmdir(path)
|
|
683
|
+
|
|
684
|
+
def rmdirs(self, path: Union[str, os.PathLike[str]]) -> None:
|
|
685
|
+
self.get_fs(path).rmdirs(path)
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
if fsspec is not None:
|
|
689
|
+
fsspec_fs = FsspecFileSystem()
|
|
690
|
+
for p in fsspec.available_protocols():
|
|
691
|
+
add_file_system(p + '://', fsspec_fs)
|
|
692
|
+
add_file_system('', _FsspecUriCatcher())
|
|
693
|
+
|
|
694
|
+
|
|
415
695
|
#
|
|
416
696
|
# APIs for file IO.
|
|
417
697
|
#
|
|
@@ -458,6 +738,14 @@ def writefile(
|
|
|
458
738
|
chmod(path, perms)
|
|
459
739
|
|
|
460
740
|
|
|
741
|
+
def rename(
|
|
742
|
+
oldpath: Union[str, os.PathLike[str]],
|
|
743
|
+
newpath: Union[str, os.PathLike[str]],
|
|
744
|
+
) -> None:
|
|
745
|
+
"""Renames a file."""
|
|
746
|
+
_fs.get(oldpath).rename(oldpath, newpath)
|
|
747
|
+
|
|
748
|
+
|
|
461
749
|
def rm(path: Union[str, os.PathLike[str]]) -> None:
|
|
462
750
|
"""Removes a file."""
|
|
463
751
|
_fs.get(path).rm(path)
|
|
@@ -468,6 +756,11 @@ def path_exists(path: Union[str, os.PathLike[str]]) -> bool:
|
|
|
468
756
|
return _fs.get(path).exists(path)
|
|
469
757
|
|
|
470
758
|
|
|
759
|
+
def glob(pattern: Union[str, os.PathLike[str]]) -> list[str]:
|
|
760
|
+
"""Lists all files or sub-directories."""
|
|
761
|
+
return _fs.get(pattern).glob(pattern)
|
|
762
|
+
|
|
763
|
+
|
|
471
764
|
def listdir(
|
|
472
765
|
path: Union[str, os.PathLike[str]], fullpath: bool = False
|
|
473
766
|
) -> list[str]: # pylint: disable=redefined-builtin
|
|
@@ -16,6 +16,8 @@ import os
|
|
|
16
16
|
import pathlib
|
|
17
17
|
import tempfile
|
|
18
18
|
import unittest
|
|
19
|
+
from unittest import mock
|
|
20
|
+
import fsspec
|
|
19
21
|
from pyglove.core.io import file_system
|
|
20
22
|
|
|
21
23
|
|
|
@@ -82,6 +84,75 @@ class StdFileSystemTest(unittest.TestCase):
|
|
|
82
84
|
fs.rmdirs(os.path.join(dir_a, 'b/c'))
|
|
83
85
|
self.assertEqual(sorted(fs.listdir(dir_a)), ['file1']) # pylint: disable=g-generic-assert
|
|
84
86
|
|
|
87
|
+
def test_rename(self):
|
|
88
|
+
tmp_dir = tempfile.mkdtemp()
|
|
89
|
+
fs = file_system.StdFileSystem()
|
|
90
|
+
|
|
91
|
+
_ = fs.mkdirs(os.path.join(tmp_dir, 'a/b'))
|
|
92
|
+
file_foo = os.path.join(tmp_dir, 'a/foo.txt')
|
|
93
|
+
file_bar = os.path.join(tmp_dir, 'a/bar.txt')
|
|
94
|
+
|
|
95
|
+
with fs.open(file_foo, 'w') as f:
|
|
96
|
+
f.write('foo')
|
|
97
|
+
with fs.open(file_bar, 'w') as f:
|
|
98
|
+
f.write('bar')
|
|
99
|
+
|
|
100
|
+
# Rename file to a new name.
|
|
101
|
+
file_foo_new = os.path.join(tmp_dir, 'a/foo-new.txt')
|
|
102
|
+
fs.rename(file_foo, file_foo_new)
|
|
103
|
+
self.assertFalse(fs.exists(file_foo))
|
|
104
|
+
self.assertTrue(fs.exists(file_foo_new))
|
|
105
|
+
|
|
106
|
+
# Rename file to an existing file name.
|
|
107
|
+
fs.rename(file_foo_new, file_bar)
|
|
108
|
+
self.assertFalse(fs.exists(file_foo_new))
|
|
109
|
+
with fs.open(file_bar, 'r') as f:
|
|
110
|
+
self.assertEqual(f.read(), 'foo')
|
|
111
|
+
|
|
112
|
+
# Rename directory to a new name.
|
|
113
|
+
dir_b = os.path.join(tmp_dir, 'a/b')
|
|
114
|
+
dir_c = os.path.join(tmp_dir, 'a/c')
|
|
115
|
+
fs.rename(dir_b, dir_c)
|
|
116
|
+
self.assertFalse(fs.exists(dir_b))
|
|
117
|
+
self.assertTrue(fs.exists(dir_c))
|
|
118
|
+
self.assertTrue(fs.isdir(dir_c))
|
|
119
|
+
|
|
120
|
+
# Rename directory to an existing empty directory.
|
|
121
|
+
dir_d = os.path.join(tmp_dir, 'a/d')
|
|
122
|
+
fs.mkdirs(dir_d)
|
|
123
|
+
fs.rename(dir_c, dir_d)
|
|
124
|
+
self.assertFalse(fs.exists(dir_c))
|
|
125
|
+
self.assertTrue(fs.exists(dir_d))
|
|
126
|
+
|
|
127
|
+
# Rename directory to a non-empty directory.
|
|
128
|
+
dir_x = os.path.join(tmp_dir, 'x')
|
|
129
|
+
dir_a = os.path.join(tmp_dir, 'a')
|
|
130
|
+
fs.mkdirs(os.path.join(dir_x, 'y'))
|
|
131
|
+
with self.assertRaises(OSError):
|
|
132
|
+
fs.rename(dir_a, dir_x)
|
|
133
|
+
self.assertTrue(fs.exists(dir_a))
|
|
134
|
+
self.assertTrue(fs.exists(os.path.join(dir_x, 'y')))
|
|
135
|
+
|
|
136
|
+
# Errors
|
|
137
|
+
dir_u = os.path.join(tmp_dir, 'u')
|
|
138
|
+
dir_u_v = os.path.join(dir_u, 'v')
|
|
139
|
+
file_u_a = os.path.join(dir_u, 'a.txt')
|
|
140
|
+
fs.mkdirs(dir_u_v)
|
|
141
|
+
with fs.open(file_u_a, 'w') as f:
|
|
142
|
+
f.write('a')
|
|
143
|
+
|
|
144
|
+
with self.assertRaises((OSError, NotADirectoryError)):
|
|
145
|
+
fs.rename(dir_u, file_u_a)
|
|
146
|
+
|
|
147
|
+
with self.assertRaises(IsADirectoryError):
|
|
148
|
+
fs.rename(file_u_a, dir_u_v)
|
|
149
|
+
|
|
150
|
+
with self.assertRaises(FileNotFoundError):
|
|
151
|
+
fs.rename(
|
|
152
|
+
os.path.join(tmp_dir, 'non-existent'),
|
|
153
|
+
os.path.join(tmp_dir, 'y')
|
|
154
|
+
)
|
|
155
|
+
|
|
85
156
|
|
|
86
157
|
class MemoryFileSystemTest(unittest.TestCase):
|
|
87
158
|
|
|
@@ -180,6 +251,94 @@ class MemoryFileSystemTest(unittest.TestCase):
|
|
|
180
251
|
fs.rmdirs(os.path.join(dir_a, 'b/c'))
|
|
181
252
|
self.assertEqual(fs.listdir(dir_a), ['file1']) # pylint: disable=g-generic-assert
|
|
182
253
|
|
|
254
|
+
def test_glob(self):
|
|
255
|
+
fs = file_system.MemoryFileSystem()
|
|
256
|
+
fs.mkdirs('/mem/a/b/c')
|
|
257
|
+
with fs.open('/mem/a/foo.txt', 'w') as f:
|
|
258
|
+
f.write('foo')
|
|
259
|
+
with fs.open('/mem/a/bar.json', 'w') as f:
|
|
260
|
+
f.write('bar')
|
|
261
|
+
with fs.open('/mem/a/b/baz.txt', 'w') as f:
|
|
262
|
+
f.write('baz')
|
|
263
|
+
|
|
264
|
+
self.assertEqual(
|
|
265
|
+
sorted(fs.glob('/mem/a/*')),
|
|
266
|
+
['/mem/a/b', '/mem/a/b/baz.txt', '/mem/a/b/c',
|
|
267
|
+
'/mem/a/bar.json', '/mem/a/foo.txt'])
|
|
268
|
+
self.assertEqual(
|
|
269
|
+
sorted(fs.glob('/mem/a/*.txt')),
|
|
270
|
+
['/mem/a/b/baz.txt', '/mem/a/foo.txt'])
|
|
271
|
+
self.assertEqual(
|
|
272
|
+
sorted(fs.glob('/mem/a/b/*')),
|
|
273
|
+
['/mem/a/b/baz.txt', '/mem/a/b/c'])
|
|
274
|
+
self.assertEqual(fs.glob('/mem/a/b/*.txt'), ['/mem/a/b/baz.txt'])
|
|
275
|
+
self.assertEqual(fs.glob('/mem/a/b/c/*'), [])
|
|
276
|
+
self.assertEqual(fs.glob('/mem/a/???.txt'), ['/mem/a/foo.txt'])
|
|
277
|
+
self.assertEqual(fs.glob('/mem/a/bar.*'), ['/mem/a/bar.json'])
|
|
278
|
+
self.assertEqual(
|
|
279
|
+
sorted(fs.glob('/mem/a/*.*')),
|
|
280
|
+
['/mem/a/b/baz.txt', '/mem/a/bar.json', '/mem/a/foo.txt'])
|
|
281
|
+
|
|
282
|
+
def test_rename(self):
|
|
283
|
+
fs = file_system.MemoryFileSystem()
|
|
284
|
+
fs.mkdirs('/mem/a/b')
|
|
285
|
+
with fs.open('/mem/a/foo.txt', 'w') as f:
|
|
286
|
+
f.write('foo')
|
|
287
|
+
with fs.open('/mem/a/bar.txt', 'w') as f:
|
|
288
|
+
f.write('bar')
|
|
289
|
+
|
|
290
|
+
# Rename file to a new name.
|
|
291
|
+
fs.rename('/mem/a/foo.txt', '/mem/a/foo-new.txt')
|
|
292
|
+
self.assertFalse(fs.exists('/mem/a/foo.txt'))
|
|
293
|
+
self.assertTrue(fs.exists('/mem/a/foo-new.txt'))
|
|
294
|
+
|
|
295
|
+
# Rename file to an existing file name.
|
|
296
|
+
fs.rename('/mem/a/foo-new.txt', '/mem/a/bar.txt')
|
|
297
|
+
self.assertFalse(fs.exists('/mem/a/foo-new.txt'))
|
|
298
|
+
with fs.open('/mem/a/bar.txt', 'r') as f:
|
|
299
|
+
self.assertEqual(f.read(), 'foo')
|
|
300
|
+
|
|
301
|
+
# Rename directory to a new name.
|
|
302
|
+
fs.rename('/mem/a/b', '/mem/a/c')
|
|
303
|
+
self.assertFalse(fs.exists('/mem/a/b'))
|
|
304
|
+
self.assertTrue(fs.exists('/mem/a/c'))
|
|
305
|
+
self.assertTrue(fs.isdir('/mem/a/c'))
|
|
306
|
+
|
|
307
|
+
# Rename directory to an existing empty directory.
|
|
308
|
+
fs.mkdirs('/mem/a/d')
|
|
309
|
+
fs.rename('/mem/a/c', '/mem/a/d')
|
|
310
|
+
self.assertFalse(fs.exists('/mem/a/c'))
|
|
311
|
+
self.assertTrue(fs.exists('/mem/a/d'))
|
|
312
|
+
|
|
313
|
+
# Rename directory to a non-empty directory.
|
|
314
|
+
fs.mkdirs('/mem/x/y')
|
|
315
|
+
with self.assertRaisesRegex(OSError, "Directory not empty: '/mem/x'"):
|
|
316
|
+
fs.rename('/mem/a', '/mem/x')
|
|
317
|
+
self.assertTrue(fs.exists('/mem/a'))
|
|
318
|
+
self.assertTrue(fs.exists('/mem/x/y'))
|
|
319
|
+
|
|
320
|
+
# Errors
|
|
321
|
+
fs.mkdirs('/mem/u/v')
|
|
322
|
+
with fs.open('/mem/u/a.txt', 'w') as f:
|
|
323
|
+
f.write('a')
|
|
324
|
+
|
|
325
|
+
with self.assertRaisesRegex(
|
|
326
|
+
OSError, "Cannot move directory '/mem/u' to a subdirectory of itself"):
|
|
327
|
+
fs.rename('/mem/u', '/mem/u/v/w')
|
|
328
|
+
|
|
329
|
+
with self.assertRaisesRegex(
|
|
330
|
+
NotADirectoryError,
|
|
331
|
+
"Cannot rename directory '/mem/u' to non-directory '/mem/u/a.txt'"):
|
|
332
|
+
fs.rename('/mem/u', '/mem/u/a.txt')
|
|
333
|
+
|
|
334
|
+
with self.assertRaisesRegex(
|
|
335
|
+
IsADirectoryError,
|
|
336
|
+
"Cannot rename non-directory '/mem/u/a.txt' to directory '/mem/u/v'"):
|
|
337
|
+
fs.rename('/mem/u/a.txt', '/mem/u/v')
|
|
338
|
+
|
|
339
|
+
with self.assertRaises(FileNotFoundError):
|
|
340
|
+
fs.rename('/mem/non-existent', '/mem/y')
|
|
341
|
+
|
|
183
342
|
|
|
184
343
|
class FileIoApiTest(unittest.TestCase):
|
|
185
344
|
|
|
@@ -217,6 +376,14 @@ class FileIoApiTest(unittest.TestCase):
|
|
|
217
376
|
file_system.rm(file2)
|
|
218
377
|
self.assertFalse(file_system.path_exists(file2))
|
|
219
378
|
|
|
379
|
+
# Test glob with standard file system.
|
|
380
|
+
glob_dir = os.path.join(tempfile.mkdtemp(), 'glob')
|
|
381
|
+
file_system.mkdirs(os.path.join(glob_dir, 'a/b'))
|
|
382
|
+
file_system.writefile(os.path.join(glob_dir, 'a/foo.txt'), 'foo')
|
|
383
|
+
self.assertEqual(
|
|
384
|
+
sorted(file_system.glob(os.path.join(glob_dir, 'a/*'))),
|
|
385
|
+
[os.path.join(glob_dir, 'a/b'), os.path.join(glob_dir, 'a/foo.txt')])
|
|
386
|
+
|
|
220
387
|
def test_memory_filesystem(self):
|
|
221
388
|
file1 = pathlib.Path('/mem/file1')
|
|
222
389
|
with self.assertRaises(FileNotFoundError):
|
|
@@ -248,6 +415,130 @@ class FileIoApiTest(unittest.TestCase):
|
|
|
248
415
|
file_system.rm(file2)
|
|
249
416
|
self.assertFalse(file_system.path_exists(file2))
|
|
250
417
|
|
|
418
|
+
# Test glob with memory file system.
|
|
419
|
+
file_system.mkdirs('/mem/g/a/b')
|
|
420
|
+
file_system.writefile('/mem/g/a/foo.txt', 'foo')
|
|
421
|
+
file_system.rename('/mem/g/a/foo.txt', '/mem/g/a/foo2.txt')
|
|
422
|
+
file_system.writefile('/mem/g/a/b/bar.txt', 'bar')
|
|
423
|
+
self.assertEqual(
|
|
424
|
+
sorted(file_system.glob('/mem/g/a/*')),
|
|
425
|
+
['/mem/g/a/b', '/mem/g/a/b/bar.txt', '/mem/g/a/foo2.txt'])
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
class FsspecFileSystemTest(unittest.TestCase):
|
|
429
|
+
|
|
430
|
+
def setUp(self):
|
|
431
|
+
super().setUp()
|
|
432
|
+
self.fs = fsspec.filesystem('memory')
|
|
433
|
+
self.fs.pipe('memory:///a/b/c', b'abc')
|
|
434
|
+
self.fs.pipe('memory:///a/b/d', b'abd')
|
|
435
|
+
self.fs.mkdir('memory:///a/e')
|
|
436
|
+
|
|
437
|
+
def tearDown(self):
|
|
438
|
+
super().tearDown()
|
|
439
|
+
fsspec.filesystem('memory').rm('/', recursive=True)
|
|
440
|
+
|
|
441
|
+
def test_read_file(self):
|
|
442
|
+
self.assertEqual(file_system.readfile('memory:///a/b/c', mode='rb'), b'abc')
|
|
443
|
+
with file_system.open('memory:///a/b/d', 'rb') as f:
|
|
444
|
+
self.assertEqual(f.read(), b'abd')
|
|
445
|
+
|
|
446
|
+
def test_fsspec_file_ops(self):
|
|
447
|
+
file_system.writefile('memory:///f', b'hello\nworld\n', mode='wb')
|
|
448
|
+
with file_system.open('memory:///f', 'rb') as f:
|
|
449
|
+
self.assertIsInstance(f, file_system.FsspecFile)
|
|
450
|
+
self.assertEqual(f.readline(), b'hello\n')
|
|
451
|
+
self.assertEqual(f.tell(), 6)
|
|
452
|
+
self.assertEqual(f.seek(8), 8)
|
|
453
|
+
self.assertEqual(f.read(), b'rld\n')
|
|
454
|
+
f.flush()
|
|
455
|
+
|
|
456
|
+
def test_write_file(self):
|
|
457
|
+
file_system.writefile('memory:///a/b/e', b'abe', mode='wb')
|
|
458
|
+
self.assertTrue(self.fs.exists('memory:///a/b/e'))
|
|
459
|
+
self.assertEqual(self.fs.cat('memory:///a/b/e'), b'abe')
|
|
460
|
+
|
|
461
|
+
def test_exists(self):
|
|
462
|
+
self.assertTrue(file_system.path_exists('memory:///a/b/c'))
|
|
463
|
+
self.assertFalse(file_system.path_exists('memory:///a/b/nonexist'))
|
|
464
|
+
|
|
465
|
+
def test_isdir(self):
|
|
466
|
+
self.assertTrue(file_system.isdir('memory:///a/b'))
|
|
467
|
+
self.assertTrue(file_system.isdir('memory:///a/e'))
|
|
468
|
+
self.assertFalse(file_system.isdir('memory:///a/b/c'))
|
|
469
|
+
|
|
470
|
+
def test_listdir(self):
|
|
471
|
+
self.assertCountEqual(file_system.listdir('memory:///a'), ['b', 'e'])
|
|
472
|
+
self.assertCountEqual(file_system.listdir('memory:///a/b'), ['c', 'd'])
|
|
473
|
+
|
|
474
|
+
def test_glob(self):
|
|
475
|
+
self.assertCountEqual(
|
|
476
|
+
file_system.glob('memory:///a/b/*'),
|
|
477
|
+
['memory:///a/b/c', 'memory:///a/b/d']
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
def test_mkdir(self):
|
|
481
|
+
file_system.mkdir('memory:///a/f')
|
|
482
|
+
self.assertTrue(self.fs.isdir('memory:///a/f'))
|
|
483
|
+
|
|
484
|
+
def test_mkdirs(self):
|
|
485
|
+
file_system.mkdirs('memory:///g/h/i')
|
|
486
|
+
self.assertTrue(self.fs.isdir('memory:///g/h/i'))
|
|
487
|
+
|
|
488
|
+
def test_rm(self):
|
|
489
|
+
file_system.rm('memory:///a/b/c')
|
|
490
|
+
self.assertFalse(self.fs.exists('memory:///a/b/c'))
|
|
491
|
+
|
|
492
|
+
def test_rename(self):
|
|
493
|
+
file_system.rename('memory:///a/b/c', 'memory:///a/b/c_new')
|
|
494
|
+
self.assertFalse(self.fs.exists('memory:///a/b/c'))
|
|
495
|
+
self.assertTrue(self.fs.exists('memory:///a/b/c_new'))
|
|
496
|
+
with self.assertRaisesRegex(ValueError, 'Rename across different'):
|
|
497
|
+
file_system.rename('memory:///a/b/c_new', 'file:///a/b/c_d')
|
|
498
|
+
|
|
499
|
+
def test_chmod(self):
|
|
500
|
+
mock_fs = mock.Mock()
|
|
501
|
+
mock_fs.chmod = mock.Mock()
|
|
502
|
+
with mock.patch('fsspec.core.url_to_fs', return_value=(mock_fs, 'path')):
|
|
503
|
+
file_system.chmod('protocol:///path', 0o777)
|
|
504
|
+
mock_fs.chmod.assert_called_once_with('path', 0o777)
|
|
505
|
+
|
|
506
|
+
def test_rmdir(self):
|
|
507
|
+
file_system.rmdir('memory:///a/e')
|
|
508
|
+
self.assertFalse(self.fs.exists('memory:///a/e'))
|
|
509
|
+
|
|
510
|
+
def test_rmdirs(self):
|
|
511
|
+
file_system.mkdirs('memory:///x/y/z')
|
|
512
|
+
self.assertTrue(file_system.isdir('memory:///x/y/z'))
|
|
513
|
+
file_system.rmdirs('memory:///x')
|
|
514
|
+
self.assertFalse(file_system.path_exists('memory:///x'))
|
|
515
|
+
|
|
516
|
+
def test_fsspec_uri_catcher(self):
|
|
517
|
+
with mock.patch.object(
|
|
518
|
+
file_system.FsspecFileSystem, 'exists', return_value=True
|
|
519
|
+
) as mock_fsspec_exists:
|
|
520
|
+
# We use a protocol that is not registered in
|
|
521
|
+
# fsspec.available_protocols() to make sure _FsspecUriCatcher is used.
|
|
522
|
+
self.assertTrue(file_system.path_exists('some-proto://foo'))
|
|
523
|
+
mock_fsspec_exists.assert_called_once_with('some-proto://foo')
|
|
524
|
+
|
|
525
|
+
# For full coverage of _FsspecUriCatcher.get_fs returning StdFileSystem,
|
|
526
|
+
# we need to test a non-URI path that doesn't match other prefixes.
|
|
527
|
+
# We mock StdFileSystem.exists to check if it's called.
|
|
528
|
+
with mock.patch.object(
|
|
529
|
+
file_system.StdFileSystem, 'exists', return_value=True
|
|
530
|
+
) as mock_std_exists:
|
|
531
|
+
self.assertTrue(file_system.path_exists('/foo/bar/baz'))
|
|
532
|
+
mock_std_exists.assert_called_once_with('/foo/bar/baz')
|
|
533
|
+
|
|
534
|
+
with mock.patch.object(
|
|
535
|
+
file_system.FsspecFileSystem, 'rename', return_value=None
|
|
536
|
+
) as mock_fsspec_rename:
|
|
537
|
+
file_system.rename('some-proto://foo', 'some-proto://bar')
|
|
538
|
+
mock_fsspec_rename.assert_called_once_with(
|
|
539
|
+
'some-proto://foo', 'some-proto://bar'
|
|
540
|
+
)
|
|
541
|
+
|
|
251
542
|
|
|
252
543
|
if __name__ == '__main__':
|
|
253
544
|
unittest.main()
|