2 import shelve
, anydbm
, sys
, UserDict
5 class WrongFormatError(IOError):
6 'attempted to open db with the wrong format e.g. btree vs. hash'
8 class NoSuchFileError(IOError):
11 class PermissionsError(IOError):
12 'inadequate permissions for requested file'
14 class ReadOnlyError(PermissionsError
):
15 'attempted to open a file for writing, but no write permission'
18 def open_anydbm(*args
, **kwargs
):
19 'trap anydbm.error message and transform to our consistent exception types'
21 return anydbm
.open(*args
, **kwargs
)
22 except anydbm
.error
, e
:
24 if msg
.endswith('new db'):
25 raise NoSuchFileError(msg
)
26 elif msg
.startswith('db type'):
27 raise WrongFormatError(msg
)
30 try: # detect whether bsddb module available and working...
34 except AttributeError:
39 def open_bsddb(filename
, flag
='r', useHash
=False, mode
=0666):
40 """open bsddb index instead of hash by default.
41 useHash=True forces it to use anydbm default (i.e. hash) instead.
42 Also gives more meaningful error messages."""
43 try: # 1ST OPEN AS BTREE
44 if useHash
: # FORCE IT TO USE HASH INSTEAD OF BTREE
45 return open_anydbm(filename
, flag
)
47 return bsddb
.btopen(filename
, flag
, mode
)
48 except bsddb
.db
.DBAccessError
: # HMM, BLOCKED BY PERMISSIONS
49 if flag
=='c' or flag
=='w': # TRY OPENING READ-ONLY
51 ifile
= file(filename
)
52 except IOError: # HMM, NOT EVEN READABLE. RAISE GENERIC PERMISSIONS ERROR
53 raise PermissionsError('insufficient permissions to open file: '
55 ifile
.close() # OK, WE CAN READ THE FILE, SO RAISE EXCEPTION WITH
56 raise ReadOnlyError('file is read-only: '+filename
) # VERY SPECIFIC MEANING!
57 else: # r OR n FLAG: JUST RAISE EXCEPTION
58 raise PermissionsError('insufficient permissions to open file: '
60 except bsddb
.db
.DBNoSuchFileError
:
61 raise NoSuchFileError('no file named: '+filename
)
62 except bsddb
.db
.DBInvalidArgError
: # NOT A BTREE FILE...
64 if useHash
: # NO POINT IN TRYING HASH YET AGAIN...
65 raise bsddb
.db
.DBInvalidArgError
66 # fallback to using default: hash file
67 return open_anydbm(filename
, flag
)
68 except bsddb
.db
.DBInvalidArgError
:
69 raise WrongFormatError('file does not match expected shelve format: '+filename
)
71 def open_index(filename
, flag
='r', useHash
=False, mode
=0666):
73 d
= open_anydbm(filename
, flag
)
75 logger
.warn('Falling back to hash index: unable to import bsddb')
77 return open_bsddb(filename
, flag
, useHash
, mode
)
80 'iterator for gdbm objects'
86 class _ClosedDict(UserDict
.DictMixin
):
87 """This dummy class exists solely to raise a clear error msg if accessed.
88 Copied from the Python 2.6 shelve.py """
89 def closed(self
, *args
):
90 raise ValueError('invalid operation on closed shelf')
91 __getitem__
= __setitem__
= __delitem__
= keys
= closed
94 return '<Closed Dictionary>'
98 class BetterShelf(shelve
.Shelf
):
99 """Shelf subclass that fixes its horrible iter implementation.
102 'avoid using iter provided by shelve/DictMixin, which loads all keys!'
104 return iter(self
.dict)
105 except TypeError: # gdbm lacks __iter__ method, so try iter_gdbm()
106 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
109 except AttributeError: # evidently not a gdbm dict
110 raise exc_value
, None, exc_traceback
# re-raise original error
111 else: # iterate using gdbm-specific method
112 return iter_gdbm(self
.dict)
114 if sys
.version_info
< (2, 6): # Python finally added good err msg in 2.6
116 if isinstance(self
.dict, _ClosedDict
):
117 return # if already closed, nothing further to do...
118 shelve
.Shelf
.close(self
) # close Shelf as usual
119 self
.dict = _ClosedDict() # raises sensible error msg if accessed
121 def shelve_open(filename
, flag
='c', protocol
=None, writeback
=False,
122 useHash
=True, mode
=0666, *args
, **kwargs
):
123 """improved implementation of shelve.open() that won't generate
124 bogus __del__ warning messages like Python's version does."""
125 d
= open_index(filename
, flag
, useHash
, mode
) # construct Shelf only if OK
126 return BetterShelf(d
, protocol
, writeback
, *args
, **kwargs
)