@@ -937,6 +937,128 @@ def atexit_rmglob(path,
937
937
rmtree (p )
938
938
939
939
940
+ class FSStore (MutableMapping ):
941
+ """Wraps an fsspec.FSMap to give access to arbitrary filesystems
942
+
943
+ Requires that ``fsspec`` is installed, as well as any additional
944
+ requirements for the protocol chosen.
945
+
946
+ Parameters
947
+ ----------
948
+ url : str
949
+ The destination to map. Should include protocol and path,
950
+ like "s3://bucket/root"
951
+ normalize_keys : bool
952
+ key_separator : str
953
+ Character to use when constructing the target path strings
954
+ for data keys
955
+ mode : str
956
+ "w" for writable, "r" for read-only
957
+ exceptions : list of Exception subclasses
958
+ When accessing data, any of these exceptions will be treated
959
+ as a missing key
960
+ storage_options : passed to the fsspec implementation
961
+ """
962
+
963
+ def __init__ (self , url , normalize_keys = True , key_separator = '.' ,
964
+ mode = 'w' ,
965
+ exceptions = (KeyError , PermissionError , IOError ),
966
+ ** storage_options ):
967
+ import fsspec
968
+ self .path = url
969
+ self .normalize_keys = normalize_keys
970
+ self .key_separator = key_separator
971
+ self .map = fsspec .get_mapper (url , ** storage_options )
972
+ self .fs = self .map .fs # for direct operations
973
+ self .mode = mode
974
+ self .exceptions = exceptions
975
+ if self .fs .exists (url ) and not self .fs .isdir (url ):
976
+ err_fspath_exists_notdir (url )
977
+
978
+ def _normalize_key (self , key ):
979
+ key = normalize_storage_path (key ).lstrip ('/' )
980
+ if key :
981
+ * bits , end = key .split ('/' )
982
+ key = '/' .join (bits + [end .replace ('.' , self .key_separator )])
983
+ return key .lower () if self .normalize_keys else key
984
+
985
+ def __getitem__ (self , key ):
986
+ key = self ._normalize_key (key )
987
+ try :
988
+ return self .map [key ]
989
+ except self .exceptions as e :
990
+ raise KeyError (key ) from e
991
+
992
+ def __setitem__ (self , key , value ):
993
+ if self .mode == 'r' :
994
+ err_read_only ()
995
+ key = self ._normalize_key (key )
996
+ path = self .dir_path (key )
997
+ value = ensure_contiguous_ndarray (value )
998
+ try :
999
+ if self .fs .isdir (path ):
1000
+ self .fs .rm (path , recursive = True )
1001
+ self .map [key ] = value
1002
+ except self .exceptions as e :
1003
+ raise KeyError (key ) from e
1004
+
1005
+ def __delitem__ (self , key ):
1006
+ if self .mode == 'r' :
1007
+ err_read_only ()
1008
+ key = self ._normalize_key (key )
1009
+ path = self .dir_path (key )
1010
+ if self .fs .isdir (path ):
1011
+ self .fs .rm (path , recursive = True )
1012
+ else :
1013
+ del self .map [key ]
1014
+
1015
+ def __contains__ (self , key ):
1016
+ key = self ._normalize_key (key )
1017
+ return key in self .map
1018
+
1019
+ def __eq__ (self , other ):
1020
+ return (type (self ) == type (other ) and self .map == other .map
1021
+ and self .mode == other .mode )
1022
+
1023
+ def keys (self ):
1024
+ return iter (self .map )
1025
+
1026
+ def __iter__ (self ):
1027
+ return self .keys ()
1028
+
1029
+ def __len__ (self ):
1030
+ return len (list (self .keys ()))
1031
+
1032
+ def dir_path (self , path = None ):
1033
+ store_path = normalize_storage_path (path )
1034
+ return self .map ._key_to_str (store_path )
1035
+
1036
+ def listdir (self , path = None ):
1037
+ dir_path = self .dir_path (path )
1038
+ try :
1039
+ out = sorted (p .rstrip ('/' ).rsplit ('/' , 1 )[- 1 ]
1040
+ for p in self .fs .ls (dir_path , detail = False ))
1041
+ return out
1042
+ except IOError :
1043
+ return []
1044
+
1045
+ def rmdir (self , path = None ):
1046
+ if self .mode == 'r' :
1047
+ err_read_only ()
1048
+ store_path = self .dir_path (path )
1049
+ if self .fs .isdir (store_path ):
1050
+ self .fs .rm (store_path , recursive = True )
1051
+
1052
+ def getsize (self , path = None ):
1053
+ store_path = self .dir_path (path )
1054
+ return self .fs .du (store_path , True , True )
1055
+
1056
+ def clear (self ):
1057
+ if self .mode == 'r' :
1058
+ err_read_only ()
1059
+ self .map .clear ()
1060
+
1061
+
940
1062
class TempStore (DirectoryStore ):
941
1063
"""Directory store using a temporary directory for storage.
942
1064
0 commit comments