1
+ from __future__ import annotations
2
+ from typing import (
3
+ TYPE_CHECKING ,
4
+ MutableMapping ,
5
+ Literal ,
6
+ Iterable ,
7
+ Iterator ,
8
+ overload ,
9
+ TypeVar ,
10
+ )
11
+
1
12
import array
2
13
import posixpath
3
14
import warnings
4
- from collections .abc import MutableMapping
5
15
from functools import cached_property
6
16
7
17
from .core import url_to_fs
8
18
19
+ if TYPE_CHECKING :
20
+ from .spec import AbstractFileSystem
21
+ from .implementations .dirfs import DirFileSystem
22
+
23
+ T = TypeVar ("T" )
24
+
9
25
10
- class FSMap (MutableMapping ):
26
+ class FSMap (MutableMapping [ str , bytes ] ):
11
27
"""Wrap a FileSystem instance as a mutable wrapping.
12
28
13
29
The keys of the mapping become files under the given root, and the
@@ -35,7 +51,14 @@ class FSMap(MutableMapping):
35
51
b'Hello World'
36
52
"""
37
53
38
- def __init__ (self , root , fs , check = False , create = False , missing_exceptions = None ):
54
+ def __init__ (
55
+ self ,
56
+ root : str ,
57
+ fs : AbstractFileSystem ,
58
+ check : bool = False ,
59
+ create : bool = False ,
60
+ missing_exceptions : tuple [type [Exception ], ...] | None = None ,
61
+ ):
39
62
self .fs = fs
40
63
self .root = fs ._strip_protocol (root ).rstrip ("/" )
41
64
self ._root_key_to_str = fs ._strip_protocol (posixpath .join (root , "x" ))[:- 1 ]
@@ -61,28 +84,44 @@ def __init__(self, root, fs, check=False, create=False, missing_exceptions=None)
61
84
self .fs .rm (root + "/a" )
62
85
63
86
@cached_property
64
- def dirfs (self ):
87
+ def dirfs (self ) -> DirFileSystem :
65
88
"""dirfs instance that can be used with the same keys as the mapper"""
66
89
from .implementations .dirfs import DirFileSystem
67
90
68
91
return DirFileSystem (path = self ._root_key_to_str , fs = self .fs )
69
92
70
- def clear (self ):
93
+ def clear (self ) -> None :
71
94
"""Remove all keys below root - empties out mapping"""
72
95
try :
73
96
self .fs .rm (self .root , True )
74
97
self .fs .mkdir (self .root )
75
98
except : # noqa: E722
76
99
pass
77
100
78
- def getitems (self , keys , on_error = "raise" ):
101
+ @overload
102
+ def getitems (
103
+ self , keys : Iterable [str ], on_error : Literal ["raise" , "omit" ] = ...
104
+ ) -> dict [str , bytes ]:
105
+ pass
106
+
107
+ @overload
108
+ def getitems (
109
+ self , keys : Iterable [str ], on_error : Literal ["return" ]
110
+ ) -> dict [str , bytes | Exception ]:
111
+ pass
112
+
113
+ def getitems (
114
+ self ,
115
+ keys : Iterable [str ],
116
+ on_error : Literal ["raise" , "omit" , "return" ] = "raise" ,
117
+ ) -> dict [str , bytes | Exception ] | dict [str , bytes ]:
79
118
"""Fetch multiple items from the store
80
119
81
120
If the backend is async-able, this might proceed concurrently
82
121
83
122
Parameters
84
123
----------
85
- keys: list (str)
124
+ keys: iterable (str)
86
125
They keys to be fetched
87
126
on_error : "raise", "omit", "return"
88
127
If raise, an underlying exception will be raised (converted to KeyError
@@ -113,7 +152,7 @@ def getitems(self, keys, on_error="raise"):
113
152
if on_error == "return" or not isinstance (out [k2 ], BaseException )
114
153
}
115
154
116
- def setitems (self , values_dict ) :
155
+ def setitems (self , values_dict : dict [ str , bytes ]) -> None :
117
156
"""Set the values of multiple items in the store
118
157
119
158
Parameters
@@ -123,11 +162,11 @@ def setitems(self, values_dict):
123
162
values = {self ._key_to_str (k ): maybe_convert (v ) for k , v in values_dict .items ()}
124
163
self .fs .pipe (values )
125
164
126
- def delitems (self , keys ) :
165
+ def delitems (self , keys : Iterable [ str ]) -> None :
127
166
"""Remove multiple keys from the store"""
128
167
self .fs .rm ([self ._key_to_str (k ) for k in keys ])
129
168
130
- def _key_to_str (self , key ) :
169
+ def _key_to_str (self , key : str ) -> str :
131
170
"""Generate full path for the key"""
132
171
if not isinstance (key , str ):
133
172
# raise TypeError("key must be of type `str`, got `{type(key).__name__}`"
@@ -140,11 +179,11 @@ def _key_to_str(self, key):
140
179
key = str (key )
141
180
return f"{ self ._root_key_to_str } { key } "
142
181
143
- def _str_to_key (self , s ) :
182
+ def _str_to_key (self , s : str ) -> str :
144
183
"""Strip path of to leave key name"""
145
184
return s [len (self .root ) :].lstrip ("/" )
146
185
147
- def __getitem__ (self , key , default = None ):
186
+ def __getitem__ (self , key : str , default : bytes | None = None ) -> bytes :
148
187
"""Retrieve data"""
149
188
k = self ._key_to_str (key )
150
189
try :
@@ -155,7 +194,7 @@ def __getitem__(self, key, default=None):
155
194
raise KeyError (key )
156
195
return result
157
196
158
- def pop (self , key , default = None ):
197
+ def pop (self , key : str , default : bytes | None = None ) -> bytes : # type: ignore[override]
159
198
"""Pop data"""
160
199
result = self .__getitem__ (key , default )
161
200
try :
@@ -164,26 +203,26 @@ def pop(self, key, default=None):
164
203
pass
165
204
return result
166
205
167
- def __setitem__ (self , key , value ) :
206
+ def __setitem__ (self , key : str , value : bytes ) -> None :
168
207
"""Store value in key"""
169
208
key = self ._key_to_str (key )
170
209
self .fs .mkdirs (self .fs ._parent (key ), exist_ok = True )
171
210
self .fs .pipe_file (key , maybe_convert (value ))
172
211
173
- def __iter__ (self ):
212
+ def __iter__ (self ) -> Iterator [ str ] :
174
213
return (self ._str_to_key (x ) for x in self .fs .find (self .root ))
175
214
176
- def __len__ (self ):
215
+ def __len__ (self ) -> int :
177
216
return len (self .fs .find (self .root ))
178
217
179
- def __delitem__ (self , key ) :
218
+ def __delitem__ (self , key : str ) -> None :
180
219
"""Remove key"""
181
220
try :
182
221
self .fs .rm (self ._key_to_str (key ))
183
222
except : # noqa: E722
184
223
raise KeyError
185
224
186
- def __contains__ (self , key ):
225
+ def __contains__ (self , key : str ) -> bool : # type: ignore[override]
187
226
"""Does key exist in mapping?"""
188
227
path = self ._key_to_str (key )
189
228
return self .fs .exists (path ) and self .fs .isfile (path )
@@ -204,13 +243,13 @@ def maybe_convert(value):
204
243
205
244
206
245
def get_mapper (
207
- url = "" ,
208
- check = False ,
209
- create = False ,
210
- missing_exceptions = None ,
211
- alternate_root = None ,
246
+ url : str = "" ,
247
+ check : bool = False ,
248
+ create : bool = False ,
249
+ missing_exceptions : tuple [ type [ Exception ], ...] | None = None ,
250
+ alternate_root : str | None = None ,
212
251
** kwargs ,
213
- ):
252
+ ) -> FSMap :
214
253
"""Create key-value interface for given URL and options
215
254
216
255
The URL will be of the form "protocol://location" and point to the root
0 commit comments