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

98 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:56 +0000

1import array 

2import posixpath 

3import warnings 

4from collections.abc import MutableMapping 

5 

6from .core import url_to_fs 

7 

8 

9class FSMap(MutableMapping): 

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

11 

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

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

14 

15 Parameters 

16 ---------- 

17 root: string 

18 prefix for all the files 

19 fs: FileSystem instance 

20 check: bool (=True) 

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

22 

23 Examples 

24 -------- 

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

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

27 or, more likely 

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

29 

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

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

32 ['loc1'] 

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

34 b'Hello World' 

35 """ 

36 

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

38 self.fs = fs 

39 self.root = fs._strip_protocol(root).rstrip("/") 

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

41 if missing_exceptions is None: 

42 missing_exceptions = ( 

43 FileNotFoundError, 

44 IsADirectoryError, 

45 NotADirectoryError, 

46 ) 

47 self.missing_exceptions = missing_exceptions 

48 self.check = check 

49 self.create = create 

50 if create: 

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

52 self.fs.mkdir(root) 

53 if check: 

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

55 raise ValueError( 

56 "Path %s does not exist. Create " 

57 " with the ``create=True`` keyword" % root 

58 ) 

59 self.fs.touch(root + "/a") 

60 self.fs.rm(root + "/a") 

61 

62 def clear(self): 

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

64 try: 

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

66 self.fs.mkdir(self.root) 

67 except: # noqa: E722 

68 pass 

69 

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

71 """Fetch multiple items from the store 

72 

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

74 

75 Parameters 

76 ---------- 

77 keys: list(str) 

78 They keys to be fetched 

79 on_error : "raise", "omit", "return" 

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

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

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

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

84 instance. 

85 

86 Returns 

87 ------- 

88 dict(key, bytes|exception) 

89 """ 

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

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

92 try: 

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

94 if isinstance(out, bytes): 

95 out = {keys2[0]: out} 

96 except self.missing_exceptions as e: 

97 raise KeyError from e 

98 out = { 

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

100 for k, v in out.items() 

101 } 

102 return { 

103 key: out[k2] 

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

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

106 } 

107 

108 def setitems(self, values_dict): 

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

110 

111 Parameters 

112 ---------- 

113 values_dict: dict(str, bytes) 

114 """ 

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

116 self.fs.pipe(values) 

117 

118 def delitems(self, keys): 

119 """Remove multiple keys from the store""" 

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

121 

122 def _key_to_str(self, key): 

123 """Generate full path for the key""" 

124 if not isinstance(key, str): 

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

126 warnings.warn( 

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

128 DeprecationWarning, 

129 ) 

130 if isinstance(key, list): 

131 key = tuple(key) 

132 key = str(key) 

133 return f"{self._root_key_to_str}{key}" 

134 

135 def _str_to_key(self, s): 

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

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

138 

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

140 """Retrieve data""" 

141 k = self._key_to_str(key) 

142 try: 

143 result = self.fs.cat(k) 

144 except self.missing_exceptions: 

145 if default is not None: 

146 return default 

147 raise KeyError(key) 

148 return result 

149 

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

151 """Pop data""" 

152 result = self.__getitem__(key, default) 

153 try: 

154 del self[key] 

155 except KeyError: 

156 pass 

157 return result 

158 

159 def __setitem__(self, key, value): 

160 """Store value in key""" 

161 key = self._key_to_str(key) 

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

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

164 

165 def __iter__(self): 

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

167 

168 def __len__(self): 

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

170 

171 def __delitem__(self, key): 

172 """Remove key""" 

173 try: 

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

175 except: # noqa: E722 

176 raise KeyError 

177 

178 def __contains__(self, key): 

179 """Does key exist in mapping?""" 

180 path = self._key_to_str(key) 

181 return self.fs.exists(path) and self.fs.isfile(path) 

182 

183 def __reduce__(self): 

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

185 

186 

187def maybe_convert(value): 

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

189 # bytes-like things 

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

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

192 # arrays 

193 value = value.view("int64") 

194 value = bytes(memoryview(value)) 

195 return value 

196 

197 

198def get_mapper( 

199 url="", 

200 check=False, 

201 create=False, 

202 missing_exceptions=None, 

203 alternate_root=None, 

204 **kwargs, 

205): 

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

207 

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

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

210 and their values the contents of each key. 

211 

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

213 

214 Parameters 

215 ---------- 

216 url: str 

217 Root URL of mapping 

218 check: bool 

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

220 check that the mapping does exist 

221 create: bool 

222 Whether to make the directory corresponding to the root before 

223 instantiating 

224 missing_exceptions: None or tuple 

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

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

227 (FileNotFoundError, IsADirectoryError, NotADirectoryError) 

228 alternate_root: None or str 

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

230 for the mapper root, so this arg can override 

231 

232 Returns 

233 ------- 

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

235 """ 

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

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

238 root = alternate_root if alternate_root is not None else urlpath 

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