|
37 | 37 | normalize_storage_path, buffer_size, |
38 | 38 | normalize_fill_value, nolock, normalize_dtype) |
39 | 39 | from zarr.meta import encode_array_metadata, encode_group_metadata |
40 | | -from zarr.compat import PY2, OrderedDict_move_to_end |
| 40 | +from zarr.compat import PY2, OrderedDict_move_to_end, binary_type |
41 | 41 | from numcodecs.registry import codec_registry |
42 | 42 | from numcodecs.compat import ensure_bytes, ensure_contiguous_ndarray |
43 | 43 | from zarr.errors import (err_contains_group, err_contains_array, err_bad_compressor, |
@@ -2084,6 +2084,188 @@ def clear(self): |
2084 | 2084 | ) |
2085 | 2085 |
|
2086 | 2086 |
|
| 2087 | +class MongoDBStore(MutableMapping): |
| 2088 | + """Storage class using MongoDB. |
| 2089 | +
|
| 2090 | + .. note:: This is an experimental feature. |
| 2091 | +
|
| 2092 | + Requires the `pymongo <https://api.mongodb.com/python/current/>`_ |
| 2093 | + package to be installed. |
| 2094 | +
|
| 2095 | + Parameters |
| 2096 | + ---------- |
| 2097 | + database : string |
| 2098 | + Name of database |
| 2099 | + collection : string |
| 2100 | + Name of collection |
| 2101 | + **kwargs |
| 2102 | + Keyword arguments passed through to the `pymongo.MongoClient` function. |
| 2103 | +
|
| 2104 | + Examples |
| 2105 | + -------- |
| 2106 | + Store a single array:: |
| 2107 | +
|
| 2108 | + >>> import zarr |
| 2109 | + >>> store = zarr.MongoDBStore('localhost') |
| 2110 | + >>> z = zarr.zeros((10, 10), chunks=(5, 5), store=store, overwrite=True) |
| 2111 | + >>> z[...] = 42 |
| 2112 | + >>> store.close() |
| 2113 | +
|
| 2114 | + Store a group:: |
| 2115 | +
|
| 2116 | + >>> store = zarr.MongoDBStore('localhost') |
| 2117 | + >>> root = zarr.group(store=store, overwrite=True) |
| 2118 | + >>> foo = root.create_group('foo') |
| 2119 | + >>> bar = foo.zeros('bar', shape=(10, 10), chunks=(5, 5)) |
| 2120 | + >>> bar[...] = 42 |
| 2121 | + >>> store.close() |
| 2122 | +
|
| 2123 | + Notes |
| 2124 | + ----- |
| 2125 | + The maximum chunksize in MongoDB documents is 16 MB. |
| 2126 | +
|
| 2127 | + """ |
| 2128 | + |
| 2129 | + _key = 'key' |
| 2130 | + _value = 'value' |
| 2131 | + |
| 2132 | + def __init__(self, database='mongodb_zarr', collection='zarr_collection', |
| 2133 | + **kwargs): |
| 2134 | + import pymongo |
| 2135 | + |
| 2136 | + self._database = database |
| 2137 | + self._collection = collection |
| 2138 | + self._kwargs = kwargs |
| 2139 | + |
| 2140 | + self.client = pymongo.MongoClient(**self._kwargs) |
| 2141 | + self.db = self.client.get_database(self._database) |
| 2142 | + self.collection = self.db.get_collection(self._collection) |
| 2143 | + |
| 2144 | + def __getitem__(self, key): |
| 2145 | + doc = self.collection.find_one({self._key: key}) |
| 2146 | + |
| 2147 | + if doc is None: |
| 2148 | + raise KeyError(key) |
| 2149 | + else: |
| 2150 | + return binary_type(doc[self._value]) |
| 2151 | + |
| 2152 | + def __setitem__(self, key, value): |
| 2153 | + value = ensure_bytes(value) |
| 2154 | + self.collection.replace_one({self._key: key}, |
| 2155 | + {self._key: key, self._value: value}, |
| 2156 | + upsert=True) |
| 2157 | + |
| 2158 | + def __delitem__(self, key): |
| 2159 | + result = self.collection.delete_many({self._key: key}) |
| 2160 | + if not result.deleted_count == 1: |
| 2161 | + raise KeyError(key) |
| 2162 | + |
| 2163 | + def __iter__(self): |
| 2164 | + for f in self.collection.find({}): |
| 2165 | + yield f[self._key] |
| 2166 | + |
| 2167 | + def __len__(self): |
| 2168 | + return self.collection.count_documents({}) |
| 2169 | + |
| 2170 | + def __getstate__(self): |
| 2171 | + return self._database, self._collection, self._kwargs |
| 2172 | + |
| 2173 | + def __setstate__(self, state): |
| 2174 | + database, collection, kwargs = state |
| 2175 | + self.__init__(database=database, collection=collection, **kwargs) |
| 2176 | + |
| 2177 | + def close(self): |
| 2178 | + """Cleanup client resources and disconnect from MongoDB.""" |
| 2179 | + self.client.close() |
| 2180 | + |
| 2181 | + def clear(self): |
| 2182 | + """Remove all items from store.""" |
| 2183 | + self.collection.delete_many({}) |
| 2184 | + |
| 2185 | + |
| 2186 | +class RedisStore(MutableMapping): |
| 2187 | + """Storage class using Redis. |
| 2188 | +
|
| 2189 | + .. note:: This is an experimental feature. |
| 2190 | +
|
| 2191 | + Requires the `redis <https://redis-py.readthedocs.io/>`_ |
| 2192 | + package to be installed. |
| 2193 | +
|
| 2194 | + Parameters |
| 2195 | + ---------- |
| 2196 | + prefix : string |
| 2197 | + Name of prefix for Redis keys |
| 2198 | + **kwargs |
| 2199 | + Keyword arguments passed through to the `redis.Redis` function. |
| 2200 | +
|
| 2201 | + Examples |
| 2202 | + -------- |
| 2203 | + Store a single array:: |
| 2204 | +
|
| 2205 | + >>> import zarr |
| 2206 | + >>> store = zarr.RedisStore(port=6379) |
| 2207 | + >>> z = zarr.zeros((10, 10), chunks=(5, 5), store=store, overwrite=True) |
| 2208 | + >>> z[...] = 42 |
| 2209 | +
|
| 2210 | + Store a group:: |
| 2211 | +
|
| 2212 | + >>> store = zarr.RedisStore(port=6379) |
| 2213 | + >>> root = zarr.group(store=store, overwrite=True) |
| 2214 | + >>> foo = root.create_group('foo') |
| 2215 | + >>> bar = foo.zeros('bar', shape=(10, 10), chunks=(5, 5)) |
| 2216 | + >>> bar[...] = 42 |
| 2217 | +
|
| 2218 | + """ |
| 2219 | + def __init__(self, prefix='zarr', **kwargs): |
| 2220 | + import redis |
| 2221 | + self._prefix = prefix |
| 2222 | + self._kwargs = kwargs |
| 2223 | + |
| 2224 | + self.client = redis.Redis(**kwargs) |
| 2225 | + |
| 2226 | + def _key(self, key): |
| 2227 | + return '{prefix}:{key}'.format(prefix=self._prefix, key=key) |
| 2228 | + |
| 2229 | + def __getitem__(self, key): |
| 2230 | + return self.client[self._key(key)] |
| 2231 | + |
| 2232 | + def __setitem__(self, key, value): |
| 2233 | + value = ensure_bytes(value) |
| 2234 | + self.client[self._key(key)] = value |
| 2235 | + |
| 2236 | + def __delitem__(self, key): |
| 2237 | + count = self.client.delete(self._key(key)) |
| 2238 | + if not count: |
| 2239 | + raise KeyError(key) |
| 2240 | + |
| 2241 | + def keylist(self): |
| 2242 | + offset = len(self._key('')) # length of prefix |
| 2243 | + return [key[offset:].decode('utf-8') |
| 2244 | + for key in self.client.keys(self._key('*'))] |
| 2245 | + |
| 2246 | + def keys(self): |
| 2247 | + for key in self.keylist(): |
| 2248 | + yield key |
| 2249 | + |
| 2250 | + def __iter__(self): |
| 2251 | + for key in self.keys(): |
| 2252 | + yield key |
| 2253 | + |
| 2254 | + def __len__(self): |
| 2255 | + return len(self.keylist()) |
| 2256 | + |
| 2257 | + def __getstate__(self): |
| 2258 | + return self._prefix, self._kwargs |
| 2259 | + |
| 2260 | + def __setstate__(self, state): |
| 2261 | + prefix, kwargs = state |
| 2262 | + self.__init__(prefix=prefix, **kwargs) |
| 2263 | + |
| 2264 | + def clear(self): |
| 2265 | + for key in self.keys(): |
| 2266 | + del self[key] |
| 2267 | + |
| 2268 | + |
2087 | 2269 | class ConsolidatedMetadataStore(MutableMapping): |
2088 | 2270 | """A layer over other storage, where the metadata has been consolidated into |
2089 | 2271 | a single key. |
|
0 commit comments