blob: 3727270b90563820a0cde66412056b3837540a42 [file] [log] [blame]
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +00001#!/bin/env python
2#------------------------------------------------------------------------
3# Copyright (c) 1997-2001 by Total Control Software
4# All Rights Reserved
5#------------------------------------------------------------------------
6#
7# Module Name: dbShelve.py
8#
9# Description: A reimplementation of the standard shelve.py that
10# forces the use of cPickle, and DB.
11#
12# Creation Date: 11/3/97 3:39:04PM
13#
14# License: This is free software. You may use this software for any
15# purpose including modification/redistribution, so long as
16# this header remains intact and that you do not claim any
17# rights of ownership or authorship of this software. This
18# software has been tested, but no warranty is expressed or
19# implied.
20#
21# 13-Dec-2000: Updated to be used with the new bsddb3 package.
22# Added DBShelfCursor class.
23#
24#------------------------------------------------------------------------
25
Barry Warsaw9a0d7792002-12-30 20:53:52 +000026"""Manage shelves of pickled objects using bsddb database files for the
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000027storage.
28"""
29
30#------------------------------------------------------------------------
31
Martin v. Löwis918f49e2007-08-08 22:08:30 +000032import pickle
Guido van Rossum8ce8a782007-11-01 19:42:39 +000033import sys
34
Raymond Hettingeredf3b732008-02-04 22:07:15 +000035#At version 2.3 cPickle switched to using protocol instead of bin
Guido van Rossum8ce8a782007-11-01 19:42:39 +000036if sys.version_info[:3] >= (2, 3, 0):
37 HIGHEST_PROTOCOL = pickle.HIGHEST_PROTOCOL
38 def _dumps(object, protocol):
39 return pickle.dumps(object, protocol=protocol)
Raymond Hettingerd190f9c2008-02-04 21:26:27 +000040 from collections import MutableMapping
Guido van Rossum8ce8a782007-11-01 19:42:39 +000041else:
42 HIGHEST_PROTOCOL = None
43 def _dumps(object, protocol):
44 return pickle.dumps(object, bin=protocol)
Raymond Hettingerd190f9c2008-02-04 21:26:27 +000045 class MutableMapping: pass
Guido van Rossum8ce8a782007-11-01 19:42:39 +000046
Guido van Rossume2b70bc2006-08-18 22:13:04 +000047from . import db
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000048
Martin v. Löwiscccc58d2007-08-10 08:36:56 +000049_unspecified = object()
50
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000051#------------------------------------------------------------------------
52
53
Guido van Rossumcd16bf62007-06-13 18:07:49 +000054def open(filename, flags=db.DB_CREATE, mode=0o660, filetype=db.DB_HASH,
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000055 dbenv=None, dbname=None):
56 """
57 A simple factory function for compatibility with the standard
58 shleve.py module. It can be used like this, where key is a string
59 and data is a pickleable object:
60
Barry Warsaw9a0d7792002-12-30 20:53:52 +000061 from bsddb import dbshelve
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000062 db = dbshelve.open(filename)
63
64 db[key] = data
65
66 db.close()
67 """
68 if type(flags) == type(''):
69 sflag = flags
70 if sflag == 'r':
71 flags = db.DB_RDONLY
72 elif sflag == 'rw':
73 flags = 0
74 elif sflag == 'w':
75 flags = db.DB_CREATE
76 elif sflag == 'c':
77 flags = db.DB_CREATE
78 elif sflag == 'n':
79 flags = db.DB_TRUNCATE | db.DB_CREATE
80 else:
Collin Wintera65e94c2007-08-22 21:45:20 +000081 raise db.DBError("flags should be one of 'r', 'w', 'c' or 'n' or use the bsddb.db.DB_* flags")
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000082
83 d = DBShelf(dbenv)
84 d.open(filename, dbname, filetype, flags, mode)
85 return d
86
87#---------------------------------------------------------------------------
88
Gregory P. Smith5c5f1702007-10-12 19:13:19 +000089class DBShelveError(db.DBError): pass
90
91
Raymond Hettingerd190f9c2008-02-04 21:26:27 +000092class DBShelf(MutableMapping):
Barry Warsaw99142272003-02-08 03:18:58 +000093 """A shelf to hold pickled objects, built upon a bsddb DB object. It
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +000094 automatically pickles/unpickles data objects going to/from the DB.
95 """
96 def __init__(self, dbenv=None):
97 self.db = db.DB(dbenv)
Gregory P. Smith659e7f42007-10-13 23:23:58 +000098 self._closed = True
Guido van Rossum8ce8a782007-11-01 19:42:39 +000099 if HIGHEST_PROTOCOL:
100 self.protocol = HIGHEST_PROTOCOL
101 else:
102 self.protocol = 1
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000103
104
105 def __del__(self):
106 self.close()
107
108
109 def __getattr__(self, name):
Barry Warsaw99142272003-02-08 03:18:58 +0000110 """Many methods we can just pass through to the DB object.
111 (See below)
112 """
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000113 return getattr(self.db, name)
114
115
116 #-----------------------------------
117 # Dictionary access methods
118
119 def __len__(self):
120 return len(self.db)
121
122
123 def __getitem__(self, key):
124 data = self.db[key]
Martin v. Löwis918f49e2007-08-08 22:08:30 +0000125 return pickle.loads(data)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000126
127
128 def __setitem__(self, key, value):
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000129 data = _dumps(value, self.protocol)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000130 self.db[key] = data
131
132
133 def __delitem__(self, key):
134 del self.db[key]
135
136
137 def keys(self, txn=None):
138 if txn != None:
139 return self.db.keys(txn)
140 else:
141 return self.db.keys()
142
Raymond Hettingerd190f9c2008-02-04 21:26:27 +0000143 def __iter__(self):
144 return iter(self.keys())
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000145
Gregory P. Smith659e7f42007-10-13 23:23:58 +0000146 def open(self, *args, **kwargs):
147 self.db.open(*args, **kwargs)
148 self._closed = False
149
150
151 def close(self, *args, **kwargs):
152 self.db.close(*args, **kwargs)
153 self._closed = True
154
155
156 def __repr__(self):
157 if self._closed:
158 return '<DBShelf @ 0x%x - closed>' % (id(self))
159 else:
160 return repr(dict(self.iteritems()))
161
162
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000163 def items(self, txn=None):
164 if txn != None:
165 items = self.db.items(txn)
166 else:
167 items = self.db.items()
168 newitems = []
169
170 for k, v in items:
Martin v. Löwis918f49e2007-08-08 22:08:30 +0000171 newitems.append( (k, pickle.loads(v)) )
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000172 return newitems
173
174 def values(self, txn=None):
175 if txn != None:
176 values = self.db.values(txn)
177 else:
178 values = self.db.values()
179
Martin v. Löwis918f49e2007-08-08 22:08:30 +0000180 return map(pickle.loads, values)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000181
182 #-----------------------------------
183 # Other methods
184
Gregory P. Smith1281f762004-03-16 18:50:26 +0000185 def __append(self, value, txn=None):
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000186 data = _dumps(value, self.protocol)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000187 return self.db.append(data, txn)
188
Gregory P. Smith1281f762004-03-16 18:50:26 +0000189 def append(self, value, txn=None):
Gregory P. Smith5c5f1702007-10-12 19:13:19 +0000190 if self.get_type() == db.DB_RECNO:
Gregory P. Smith659e7f42007-10-13 23:23:58 +0000191 return self.__append(value, txn=txn)
Gregory P. Smith5c5f1702007-10-12 19:13:19 +0000192 raise DBShelveError("append() only supported when dbshelve opened with filetype=dbshelve.db.DB_RECNO")
Gregory P. Smith1281f762004-03-16 18:50:26 +0000193
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000194
195 def associate(self, secondaryDB, callback, flags=0):
196 def _shelf_callback(priKey, priData, realCallback=callback):
Martin v. Löwis918f49e2007-08-08 22:08:30 +0000197 data = pickle.loads(priData)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000198 return realCallback(priKey, data)
199 return self.db.associate(secondaryDB, _shelf_callback, flags)
200
201
Martin v. Löwiscccc58d2007-08-10 08:36:56 +0000202 def get(self, key, default=_unspecified, txn=None, flags=0):
203 # If no default is given, we must not pass one to the
204 # extension module, so that an exception can be raised if
205 # set_get_returns_none is turned off.
206 if default is _unspecified:
207 data = self.db.get(key, txn=txn, flags=flags)
208 # if this returns, the default value would be None
209 default = None
210 else:
211 data = self.db.get(key, default, txn=txn, flags=flags)
212 if data is default:
213 return data
214 return pickle.loads(data)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000215
216 def get_both(self, key, value, txn=None, flags=0):
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000217 data = _dumps(value, self.protocol)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000218 data = self.db.get(key, data, txn, flags)
Martin v. Löwis918f49e2007-08-08 22:08:30 +0000219 return pickle.loads(data)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000220
221
222 def cursor(self, txn=None, flags=0):
223 c = DBShelfCursor(self.db.cursor(txn, flags))
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000224 c.protocol = self.protocol
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000225 return c
226
227
228 def put(self, key, value, txn=None, flags=0):
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000229 data = _dumps(value, self.protocol)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000230 return self.db.put(key, data, txn, flags)
231
232
233 def join(self, cursorList, flags=0):
234 raise NotImplementedError
235
236
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000237 def __contains__(self, key):
Guido van Rossum20435132006-08-21 00:21:47 +0000238 return self.db.has_key(key)
Guido van Rossume2b70bc2006-08-18 22:13:04 +0000239
240
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000241 #----------------------------------------------
242 # Methods allowed to pass-through to self.db
243 #
244 # close, delete, fd, get_byteswapped, get_type, has_key,
245 # key_range, open, remove, rename, stat, sync,
246 # upgrade, verify, and all set_* methods.
247
248
249#---------------------------------------------------------------------------
250
251class DBShelfCursor:
252 """
253 """
254 def __init__(self, cursor):
255 self.dbc = cursor
256
257 def __del__(self):
258 self.close()
259
260
261 def __getattr__(self, name):
262 """Some methods we can just pass through to the cursor object. (See below)"""
263 return getattr(self.dbc, name)
264
265
266 #----------------------------------------------
267
268 def dup(self, flags=0):
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000269 c = DBShelfCursor(self.dbc.dup(flags))
270 c.protocol = self.protocol
271 return c
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000272
273
274 def put(self, key, value, flags=0):
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000275 data = _dumps(value, self.protocol)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000276 return self.dbc.put(key, data, flags)
277
278
279 def get(self, *args):
280 count = len(args) # a method overloading hack
281 method = getattr(self, 'get_%d' % count)
Neal Norwitzd9108552006-03-17 08:00:19 +0000282 method(*args)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000283
284 def get_1(self, flags):
285 rec = self.dbc.get(flags)
286 return self._extract(rec)
287
288 def get_2(self, key, flags):
289 rec = self.dbc.get(key, flags)
290 return self._extract(rec)
291
292 def get_3(self, key, value, flags):
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000293 data = _dumps(value, self.protocol)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000294 rec = self.dbc.get(key, flags)
295 return self._extract(rec)
296
297
298 def current(self, flags=0): return self.get_1(flags|db.DB_CURRENT)
299 def first(self, flags=0): return self.get_1(flags|db.DB_FIRST)
300 def last(self, flags=0): return self.get_1(flags|db.DB_LAST)
301 def next(self, flags=0): return self.get_1(flags|db.DB_NEXT)
302 def prev(self, flags=0): return self.get_1(flags|db.DB_PREV)
303 def consume(self, flags=0): return self.get_1(flags|db.DB_CONSUME)
304 def next_dup(self, flags=0): return self.get_1(flags|db.DB_NEXT_DUP)
305 def next_nodup(self, flags=0): return self.get_1(flags|db.DB_NEXT_NODUP)
306 def prev_nodup(self, flags=0): return self.get_1(flags|db.DB_PREV_NODUP)
307
308
309 def get_both(self, key, value, flags=0):
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000310 data = _dumps(value, self.protocol)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000311 rec = self.dbc.get_both(key, flags)
312 return self._extract(rec)
313
314
315 def set(self, key, flags=0):
316 rec = self.dbc.set(key, flags)
317 return self._extract(rec)
318
319 def set_range(self, key, flags=0):
320 rec = self.dbc.set_range(key, flags)
321 return self._extract(rec)
322
323 def set_recno(self, recno, flags=0):
324 rec = self.dbc.set_recno(recno, flags)
325 return self._extract(rec)
326
327 set_both = get_both
328
329 def _extract(self, rec):
330 if rec is None:
331 return None
332 else:
333 key, data = rec
Martin v. Löwis918f49e2007-08-08 22:08:30 +0000334 return key, pickle.loads(data)
Martin v. Löwis6aa4a1f2002-11-19 08:09:52 +0000335
336 #----------------------------------------------
337 # Methods allowed to pass-through to self.dbc
338 #
339 # close, count, delete, get_recno, join_item
340
341
342#---------------------------------------------------------------------------