@@ -67,42 +67,60 @@ class FileSystem(metaclass=abc.ABCMeta):
67
67
"""Interface for a file system."""
68
68
69
69
@abc .abstractmethod
70
- def open (self , path : str , mode : str = 'r' , ** kwargs ) -> File :
70
+ def open (
71
+ self , path : Union [str , os .PathLike [str ]], mode : str = 'r' , ** kwargs
72
+ ) -> File :
71
73
"""Opens a file with a path."""
72
74
73
75
@abc .abstractmethod
74
- def exists (self , path : str ) -> bool :
76
+ def exists (self , path : Union [ str , os . PathLike [ str ]] ) -> bool :
75
77
"""Returns True if a path exists."""
76
78
77
79
@abc .abstractmethod
78
- def listdir (self , path : str ) -> list [str ]:
80
+ def listdir (self , path : Union [ str , os . PathLike [ str ]] ) -> list [str ]:
79
81
"""Lists all files or sub-directories."""
80
82
81
83
@abc .abstractmethod
82
- def isdir (self , path : str ) -> bool :
84
+ def isdir (self , path : Union [ str , os . PathLike [ str ]] ) -> bool :
83
85
"""Returns True if a path is a directory."""
84
86
85
87
@abc .abstractmethod
86
- def mkdir (self , path : str , mode : int = 0o777 ) -> None :
88
+ def mkdir (
89
+ self , path : Union [str , os .PathLike [str ]], mode : int = 0o777
90
+ ) -> None :
87
91
"""Makes a directory based on a path."""
88
92
89
93
@abc .abstractmethod
90
94
def mkdirs (
91
- self , path : str , mode : int = 0o777 , exist_ok : bool = False ) -> None :
95
+ self ,
96
+ path : Union [str , os .PathLike [str ]],
97
+ mode : int = 0o777 ,
98
+ exist_ok : bool = False ,
99
+ ) -> None :
92
100
"""Makes a directory chain based on a path."""
93
101
94
102
@abc .abstractmethod
95
- def rm (self , path : str ) -> None :
103
+ def rm (self , path : Union [ str , os . PathLike [ str ]] ) -> None :
96
104
"""Removes a file based on a path."""
97
105
98
106
@abc .abstractmethod
99
- def rmdir (self , path : str ) -> bool :
107
+ def rmdir (self , path : Union [ str , os . PathLike [ str ]] ) -> bool :
100
108
"""Removes a directory based on a path."""
101
109
102
110
@abc .abstractmethod
103
- def rmdirs (self , path : str ) -> None :
111
+ def rmdirs (self , path : Union [ str , os . PathLike [ str ]] ) -> None :
104
112
"""Removes a directory chain based on a path."""
105
113
114
+
115
+ def _resolve_path (path : Union [str , os .PathLike [str ]]) -> str :
116
+ if isinstance (path , str ):
117
+ return path
118
+ elif hasattr (path , '__fspath__' ):
119
+ return path .__fspath__ ()
120
+ else :
121
+ raise ValueError (f'Unsupported path: { path !r} .' )
122
+
123
+
106
124
#
107
125
# The standard file system.
108
126
#
@@ -137,32 +155,40 @@ def close(self) -> None:
137
155
class StdFileSystem (FileSystem ):
138
156
"""The standard file system."""
139
157
140
- def open (self , path : str , mode : str = 'r' , ** kwargs ) -> File :
158
+ def open (
159
+ self , path : Union [str , os .PathLike [str ]], mode : str = 'r' , ** kwargs
160
+ ) -> File :
141
161
return StdFile (io .open (path , mode , ** kwargs ))
142
162
143
- def exists (self , path : str ) -> bool :
163
+ def exists (self , path : Union [ str , os . PathLike [ str ]] ) -> bool :
144
164
return os .path .exists (path )
145
165
146
- def listdir (self , path : str ) -> list [str ]:
166
+ def listdir (self , path : Union [ str , os . PathLike [ str ]] ) -> list [str ]:
147
167
return os .listdir (path )
148
168
149
- def isdir (self , path : str ) -> bool :
169
+ def isdir (self , path : Union [ str , os . PathLike [ str ]] ) -> bool :
150
170
return os .path .isdir (path )
151
171
152
- def mkdir (self , path : str , mode : int = 0o777 ) -> None :
172
+ def mkdir (
173
+ self , path : Union [str , os .PathLike [str ]], mode : int = 0o777
174
+ ) -> None :
153
175
os .mkdir (path , mode )
154
176
155
177
def mkdirs (
156
- self , path : str , mode : int = 0o777 , exist_ok : bool = False ) -> None :
178
+ self ,
179
+ path : Union [str , os .PathLike [str ]],
180
+ mode : int = 0o777 ,
181
+ exist_ok : bool = False ,
182
+ ) -> None :
157
183
os .makedirs (path , mode , exist_ok )
158
184
159
- def rm (self , path : str ) -> None :
185
+ def rm (self , path : Union [ str , os . PathLike [ str ]] ) -> None :
160
186
os .remove (path )
161
187
162
- def rmdir (self , path : str ) -> None :
188
+ def rmdir (self , path : Union [ str , os . PathLike [ str ]] ) -> None :
163
189
os .rmdir (path )
164
190
165
- def rmdirs (self , path : str ) -> None :
191
+ def rmdirs (self , path : Union [ str , os . PathLike [ str ]] ) -> None :
166
192
os .removedirs (path )
167
193
168
194
@@ -206,10 +232,10 @@ def __init__(self, prefix: str = '/mem/'):
206
232
self ._root = {}
207
233
self ._prefix = prefix
208
234
209
- def _internal_path (self , path : str ) -> str :
210
- return '/' + path .lstrip (self ._prefix )
235
+ def _internal_path (self , path : Union [ str , os . PathLike [ str ]] ) -> str :
236
+ return '/' + _resolve_path ( path ) .lstrip (self ._prefix )
211
237
212
- def _locate (self , path : str ) -> Any :
238
+ def _locate (self , path : Union [ str , os . PathLike [ str ]] ) -> Any :
213
239
current = self ._root
214
240
for x in self ._internal_path (path ).split ('/' ):
215
241
if not x :
@@ -219,7 +245,9 @@ def _locate(self, path: str) -> Any:
219
245
current = current [x ]
220
246
return current
221
247
222
- def open (self , path : str , mode : str = 'r' , ** kwargs ) -> File :
248
+ def open (
249
+ self , path : Union [str , os .PathLike [str ]], mode : str = 'r' , ** kwargs
250
+ ) -> File :
223
251
file = self ._locate (path )
224
252
if isinstance (file , dict ):
225
253
raise IsADirectoryError (path )
@@ -234,19 +262,22 @@ def open(self, path: str, mode: str = 'r', **kwargs) -> File:
234
262
raise FileNotFoundError (path )
235
263
return file
236
264
237
- def exists (self , path : str ) -> bool :
265
+ def exists (self , path : Union [ str , os . PathLike [ str ]] ) -> bool :
238
266
return self ._locate (path ) is not None
239
267
240
- def listdir (self , path : str ) -> list [str ]:
268
+ def listdir (self , path : Union [ str , os . PathLike [ str ]] ) -> list [str ]:
241
269
d = self ._locate (path )
242
270
if not isinstance (d , dict ):
243
271
raise FileNotFoundError (path )
244
272
return list (d .keys ())
245
273
246
- def isdir (self , path : str ) -> bool :
274
+ def isdir (self , path : Union [ str , os . PathLike [ str ]] ) -> bool :
247
275
return isinstance (self ._locate (path ), dict )
248
276
249
- def _parent_and_name (self , path : str ) -> tuple [dict [str , Any ], str ]:
277
+ def _parent_and_name (
278
+ self , path : Union [str , os .PathLike [str ]]
279
+ ) -> tuple [dict [str , Any ], str ]:
280
+ path = _resolve_path (path )
250
281
rpos = path .rfind ('/' )
251
282
assert rpos >= 0 , path
252
283
name = path [rpos + 1 :]
@@ -255,15 +286,21 @@ def _parent_and_name(self, path: str) -> tuple[dict[str, Any], str]:
255
286
raise FileNotFoundError (path )
256
287
return parent_dir , name
257
288
258
- def mkdir (self , path : str , mode : int = 0o777 ) -> None :
289
+ def mkdir (
290
+ self , path : Union [str , os .PathLike [str ]], mode : int = 0o777
291
+ ) -> None :
259
292
del mode
260
293
parent_dir , name = self ._parent_and_name (path )
261
294
if name in parent_dir :
262
295
raise FileExistsError (path )
263
296
parent_dir [name ] = {}
264
297
265
298
def mkdirs (
266
- self , path : str , mode : int = 0o777 , exist_ok : bool = False ) -> None :
299
+ self ,
300
+ path : Union [str , os .PathLike [str ]],
301
+ mode : int = 0o777 ,
302
+ exist_ok : bool = False ,
303
+ ) -> None :
267
304
del mode
268
305
current = self ._root
269
306
dirs = self ._internal_path (path ).split ('/' )
@@ -280,7 +317,7 @@ def mkdirs(
280
317
raise NotADirectoryError (path )
281
318
current = entry
282
319
283
- def rm (self , path : str ) -> None :
320
+ def rm (self , path : Union [ str , os . PathLike [ str ]] ) -> None :
284
321
parent_dir , name = self ._parent_and_name (path )
285
322
entry = parent_dir .get (name )
286
323
if entry is None :
@@ -289,7 +326,7 @@ def rm(self, path: str) -> None:
289
326
raise IsADirectoryError (path )
290
327
del parent_dir [name ]
291
328
292
- def rmdir (self , path : str ) -> None :
329
+ def rmdir (self , path : Union [ str , os . PathLike [ str ]] ) -> None :
293
330
parent_dir , name = self ._parent_and_name (path )
294
331
entry = parent_dir .get (name )
295
332
if entry is None :
@@ -300,7 +337,7 @@ def rmdir(self, path: str) -> None:
300
337
raise OSError (f'Directory not empty: { path !r} ' )
301
338
del parent_dir [name ]
302
339
303
- def rmdirs (self , path : str ) -> None :
340
+ def rmdirs (self , path : Union [ str , os . PathLike [ str ]] ) -> None :
304
341
def _rmdir (dir_dict , subpath : str ) -> bool :
305
342
if not subpath :
306
343
if dir_dict :
@@ -333,9 +370,9 @@ def add(self, prefix, fs: FileSystem) -> None:
333
370
self ._filesystems .append ((prefix , fs ))
334
371
self ._filesystems .sort (key = lambda x : x [0 ], reverse = True )
335
372
336
- def get (self , path : str ) -> FileSystem :
373
+ def get (self , path : Union [ str , os . PathLike [ str ]] ) -> FileSystem :
337
374
"""Gets the file system for a path."""
338
- path = path . lower ( )
375
+ path = _resolve_path ( path )
339
376
for prefix , fs in self ._filesystems :
340
377
if path .startswith (prefix ):
341
378
return fs
@@ -359,16 +396,17 @@ def add_file_system(prefix: str, fs: FileSystem) -> None:
359
396
#
360
397
361
398
362
- def open (path : str , mode : str = 'r' , ** kwargs ) -> File : # pylint:disable=redefined-builtin
399
+ def open (path : Union [ str , os . PathLike [ str ]] , mode : str = 'r' , ** kwargs ) -> File : # pylint:disable=redefined-builtin
363
400
"""Opens a file with a path."""
364
401
return _fs .get (path ).open (path , mode , ** kwargs )
365
402
366
403
367
404
def readfile (
368
- path : str ,
405
+ path : Union [ str , os . PathLike [ str ]] ,
369
406
mode : str = 'r' ,
370
407
nonexist_ok : bool = False ,
371
- ** kwargs ) -> Union [bytes , str , None ]:
408
+ ** kwargs ,
409
+ ) -> Union [bytes , str , None ]:
372
410
"""Reads content from a file."""
373
411
try :
374
412
with _fs .get (path ).open (path , mode = mode , ** kwargs ) as f :
@@ -380,54 +418,61 @@ def readfile(
380
418
381
419
382
420
def writefile (
383
- path : str ,
421
+ path : Union [ str , os . PathLike [ str ]] ,
384
422
content : Union [str , bytes ],
385
423
* ,
386
424
mode : str = 'w' ,
387
- ** kwargs ) -> None :
425
+ ** kwargs ,
426
+ ) -> None :
388
427
"""Writes content to a file."""
389
428
with _fs .get (path ).open (path , mode = mode , ** kwargs ) as f :
390
429
f .write (content )
391
430
392
431
393
- def rm (path : str ) -> None :
432
+ def rm (path : Union [ str , os . PathLike [ str ]] ) -> None :
394
433
"""Removes a file."""
395
434
_fs .get (path ).rm (path )
396
435
397
436
398
- def path_exists (path : str ) -> bool :
437
+ def path_exists (path : Union [ str , os . PathLike [ str ]] ) -> bool :
399
438
"""Returns True if path exists."""
400
439
return _fs .get (path ).exists (path )
401
440
402
441
403
- def listdir (path : str , fullpath : bool = False ) -> list [str ]: # pylint: disable=redefined-builtin
442
+ def listdir (
443
+ path : Union [str , os .PathLike [str ]], fullpath : bool = False
444
+ ) -> list [str ]: # pylint: disable=redefined-builtin
404
445
"""Lists all files or sub-directories under a dir."""
405
446
entries = _fs .get (path ).listdir (path )
406
447
if fullpath :
407
448
return [os .path .join (path , entry ) for entry in entries ]
408
449
return entries
409
450
410
451
411
- def isdir (path : str ) -> bool :
452
+ def isdir (path : Union [ str , os . PathLike [ str ]] ) -> bool :
412
453
"""Returns True if path is a directory."""
413
454
return _fs .get (path ).isdir (path )
414
455
415
456
416
- def mkdir (path : str , mode : int = 0o777 ) -> None :
457
+ def mkdir (path : Union [ str , os . PathLike [ str ]] , mode : int = 0o777 ) -> None :
417
458
"""Makes a directory."""
418
459
_fs .get (path ).mkdir (path , mode = mode )
419
460
420
461
421
- def mkdirs (path : str , mode : int = 0o777 , exist_ok : bool = False ) -> None :
462
+ def mkdirs (
463
+ path : Union [str , os .PathLike [str ]],
464
+ mode : int = 0o777 ,
465
+ exist_ok : bool = False ,
466
+ ) -> None :
422
467
"""Makes a directory chain."""
423
468
_fs .get (path ).mkdirs (path , mode = mode , exist_ok = exist_ok )
424
469
425
470
426
- def rmdir (path : str ) -> bool :
471
+ def rmdir (path : Union [ str , os . PathLike [ str ]] ) -> bool :
427
472
"""Removes a directory."""
428
473
return _fs .get (path ).rmdir (path )
429
474
430
475
431
- def rmdirs (path : str ) -> bool :
476
+ def rmdirs (path : Union [ str , os . PathLike [ str ]] ) -> bool :
432
477
"""Removes a directory chain until a parent directory is not empty."""
433
478
return _fs .get (path ).rmdirs (path )
0 commit comments