Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/fsspec/mapping.py: 27%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

108 statements  

1import array 

2import logging 

3import posixpath 

4import uuid 

5import warnings 

6from collections.abc import MutableMapping 

7from functools import cached_property 

8 

9from fsspec.core import url_to_fs 

10 

11logger = logging.getLogger("fsspec.mapping") 

12 

13 

14class FSMap(MutableMapping): 

15 """Wrap a FileSystem instance as a mutable wrapping. 

16 

17 The keys of the mapping become files under the given root, and the 

18 values (which must be bytes) the contents of those files. 

19 

20 Parameters 

21 ---------- 

22 root: string 

23 prefix for all the files 

24 fs: FileSystem instance 

25 check: bool (=True) 

26 performs a touch at the location, to check for write access. 

27 

28 Examples 

29 -------- 

30 >>> fs = FileSystem(**parameters) # doctest: +SKIP 

31 >>> d = FSMap('my-data/path/', fs) # doctest: +SKIP 

32 or, more likely 

33 >>> d = fs.get_mapper('my-data/path/') 

34 

35 >>> d['loc1'] = b'Hello World' # doctest: +SKIP 

36 >>> list(d.keys()) # doctest: +SKIP 

37 ['loc1'] 

38 >>> d['loc1'] # doctest: +SKIP 

39 b'Hello World' 

40 """ 

41 

42 def __init__(self, root, fs, check=False, create=False, missing_exceptions=None): 

43 self.fs = fs 

44 self.root = fs._strip_protocol(root) 

45 self._root_key_to_str = fs._strip_protocol(posixpath.join(root, "x"))[:-1] 

46 if missing_exceptions is None: 

47 missing_exceptions = ( 

48 FileNotFoundError, 

49 IsADirectoryError, 

50 NotADirectoryError, 

51 ) 

52 self.missing_exceptions = missing_exceptions 

53 self.check = check 

54 self.create = create 

55 if create: 

56 if not self.fs.exists(root): 

57 self.fs.mkdir(root) 

58 if check: 

59 if not self.fs.exists(root): 

60 raise ValueError( 

61 f"Path {root} does not exist. Create " 

62 f" with the ``create=True`` keyword" 

63 ) 

64 check_path = f"{root}/{uuid.uuid4().hex}" 

65 self.fs.touch(check_path) 

66 self.fs.rm(check_path) 

67 

68 @cached_property 

69 def dirfs(self): 

70 """dirfs instance that can be used with the same keys as the mapper""" 

71 from .implementations.dirfs import DirFileSystem 

72 

73 return DirFileSystem(path=self._root_key_to_str, fs=self.fs) 

74 

75 def clear(self): 

76 """Remove all keys below root - empties out mapping""" 

77 logger.info("Clear mapping at %s", self.root) 

78 try: 

79 self.fs.rm(self.root, True) 

80 self.fs.mkdir(self.root) 

81 except: # noqa: E722 

82 pass 

83 

84 def getitems(self, keys, on_error="raise"): 

85 """Fetch multiple items from the store 

86 

87 If the backend is async-able, this might proceed concurrently 

88 

89 Parameters 

90 ---------- 

91 keys: list(str) 

92 They keys to be fetched 

93 on_error : "raise", "omit", "return" 

94 If raise, an underlying exception will be raised (converted to KeyError 

95 if the type is in self.missing_exceptions); if omit, keys with exception 

96 will simply not be included in the output; if "return", all keys are 

97 included in the output, but the value will be bytes or an exception 

98 instance. 

99 

100 Returns 

101 ------- 

102 dict(key, bytes|exception) 

103 """ 

104 keys2 = [self._key_to_str(k) for k in keys] 

105 oe = on_error if on_error == "raise" else "return" 

106 try: 

107 out = self.fs.cat(keys2, on_error=oe) 

108 if isinstance(out, bytes): 

109 out = {keys2[0]: out} 

110 except self.missing_exceptions as e: 

111 raise KeyError from e 

112 out = { 

113 k: (KeyError() if isinstance(v, self.missing_exceptions) else v) 

114 for k, v in out.items() 

115 } 

116 return { 

117 key: out[k2] if on_error == "raise" else out.get(k2, KeyError(k2)) 

118 for key, k2 in zip(keys, keys2) 

119 if on_error == "return" or not isinstance(out[k2], BaseException) 

120 } 

121 

122 def setitems(self, values_dict): 

123 """Set the values of multiple items in the store 

124 

125 Parameters 

126 ---------- 

127 values_dict: dict(str, bytes) 

128 """ 

129 values = {self._key_to_str(k): maybe_convert(v) for k, v in values_dict.items()} 

130 self.fs.pipe(values) 

131 

132 def delitems(self, keys): 

133 """Remove multiple keys from the store""" 

134 self.fs.rm([self._key_to_str(k) for k in keys]) 

135 

136 def _key_to_str(self, key): 

137 """Generate full path for the key""" 

138 if not isinstance(key, str): 

139 # raise TypeError("key must be of type `str`, got `{type(key).__name__}`" 

140 warnings.warn( 

141 "from fsspec 2023.5 onward FSMap non-str keys will raise TypeError", 

142 DeprecationWarning, 

143 ) 

144 if isinstance(key, list): 

145 key = tuple(key) 

146 key = str(key) 

147 return f"{self._root_key_to_str}{key}".rstrip("/") 

148 

149 def _str_to_key(self, s): 

150 """Strip path of to leave key name""" 

151 return s[len(self.root) :].lstrip("/") 

152 

153 def __getitem__(self, key, default=None): 

154 """Retrieve data""" 

155 k = self._key_to_str(key) 

156 try: 

157 result = self.fs.cat(k) 

158 except self.missing_exceptions as exc: 

159 if default is not None: 

160 return default 

161 raise KeyError(key) from exc 

162 return result 

163 

164 def pop(self, key, default=None): 

165 """Pop data""" 

166 result = self.__getitem__(key, default) 

167 try: 

168 del self[key] 

169 except KeyError: 

170 pass 

171 return result 

172 

173 def __setitem__(self, key, value): 

174 """Store value in key""" 

175 key = self._key_to_str(key) 

176 self.fs.mkdirs(self.fs._parent(key), exist_ok=True) 

177 self.fs.pipe_file(key, maybe_convert(value)) 

178 

179 def __iter__(self): 

180 return (self._str_to_key(x) for x in self.fs.find(self.root)) 

181 

182 def __len__(self): 

183 return len(self.fs.find(self.root)) 

184 

185 def __delitem__(self, key): 

186 """Remove key""" 

187 try: 

188 self.fs.rm(self._key_to_str(key)) 

189 except Exception as exc: 

190 raise KeyError from exc 

191 

192 def __contains__(self, key): 

193 """Does key exist in mapping?""" 

194 path = self._key_to_str(key) 

195 return self.fs.isfile(path) 

196 

197 def __reduce__(self): 

198 return FSMap, (self.root, self.fs, False, False, self.missing_exceptions) 

199 

200 

201def maybe_convert(value): 

202 if isinstance(value, array.array) or hasattr(value, "__array__"): 

203 # bytes-like things 

204 if hasattr(value, "dtype") and value.dtype.kind in "Mm": 

205 # The buffer interface doesn't support datetime64/timdelta64 numpy 

206 # arrays 

207 value = value.view("int64") 

208 value = bytes(memoryview(value)) 

209 return value 

210 

211 

212def get_mapper( 

213 url="", 

214 check=False, 

215 create=False, 

216 missing_exceptions=None, 

217 alternate_root=None, 

218 **kwargs, 

219): 

220 """Create key-value interface for given URL and options 

221 

222 The URL will be of the form "protocol://location" and point to the root 

223 of the mapper required. All keys will be file-names below this location, 

224 and their values the contents of each key. 

225 

226 Also accepts compound URLs like zip::s3://bucket/file.zip , see ``fsspec.open``. 

227 

228 Parameters 

229 ---------- 

230 url: str 

231 Root URL of mapping 

232 check: bool 

233 Whether to attempt to read from the location before instantiation, to 

234 check that the mapping does exist 

235 create: bool 

236 Whether to make the directory corresponding to the root before 

237 instantiating 

238 missing_exceptions: None or tuple 

239 If given, these exception types will be regarded as missing keys and 

240 return KeyError when trying to read data. By default, you get 

241 (FileNotFoundError, IsADirectoryError, NotADirectoryError) 

242 alternate_root: None or str 

243 In cases of complex URLs, the parser may fail to pick the correct part 

244 for the mapper root, so this arg can override 

245 

246 Returns 

247 ------- 

248 ``FSMap`` instance, the dict-like key-value store. 

249 """ 

250 # Removing protocol here - could defer to each open() on the backend 

251 fs, urlpath = url_to_fs(url, **kwargs) 

252 root = alternate_root if alternate_root is not None else urlpath 

253 return FSMap(root, fs, check, create, missing_exceptions=missing_exceptions)