| изготовление сайтов моква | Продадим современную замечательную стильную женскую одежду лучших производителей |

source: LinkExchange/trunk/linkexchange/db_drivers.py @ 96

Revision 96, 9.7 KB checked in by lostclus, 3 weeks ago (diff)

A workaround code was added to prevent errors when db module appends '.db' suffix to the filename when saving database.

Line 
1# LinkExchange - Universal link exchange service client
2# Copyright (C) 2009 Konstantin Korikov
3#
4# This library is free software; you can redistribute it and/or
5# modify it under the terms of the GNU Lesser General Public
6# License as published by the Free Software Foundation; either
7# version 2.1 of the License, or (at your option) any later version.
8#
9# This library is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12# Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public
15# License along with this library; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17#
18# NOTE: In the context of the Python environment, I interpret "dynamic
19# linking" as importing -- thus the LGPL applies to the contents of
20# the modules, but make no requirements on code importing these
21# modules.
22
23import shelve
24import shutil
25import anydbm
26import datetime
27import os
28import os.path
29import select
30import threading
31
32class BaseMultiHashDriver(object):
33    """
34    Base multihash driver class. Multihash driver is like dictionary of
35    dictionaries, it stores multiple dictionaries (hashes) accessible by key
36    string. Each hash is read only, to modify use save() method passing new
37    dictionary or sequence of (key, value) to it.
38    """
39
40    def load(self, key):
41        """
42        Loads hash associated with key. If no hash found raises KeyError.
43
44        @param key: key string
45        @return: read only dictionary like object
46        """
47        raise KeyError(key)
48
49    def get_mtime(self, key):
50        """
51        Returns hash modification time (last save time). Raises KeyError if no
52        hash found.
53
54        @param key: key string
55        @return: datetime.datetime object of last modification
56
57        """
58        raise KeyError(key)
59
60    def save(self, key, value, blocking = True):
61        """
62        Creates new hash or overrides existing. New hash initialized by value
63        that is dictionary or sequence of (key, value).
64
65        If any concurrent thread/process already updates hash and blocking is
66        True, then call to this method sleeps until other thread/process finish
67        hash updating. If blocking is False and concurrent write access was
68        detected, returns False.
69
70        @param key: key string
71        @param value: dictionary or sequence of (key, value) to initialize new hash
72        @keyword blocking: use blocking or unblocking call
73        @return: True if hash was saved, otherwise False
74        """
75        raise KeyError(key)
76
77class MultiHashInFilesMixin:
78    """
79    Mixin class for multihash drivers that use files to store hash objects.
80    """
81    def __init__(self, filename, max_lock_time = None, no_excl = False):
82        """
83        @param filename: file name to use for hash files (string or callable)
84        @param max_lock_time: maximum lock time in seconds or as
85                              datetime.timedelta object
86        @param no_excl: disable usage of O_EXCL flag when locking
87        """
88        self.filename = filename
89        if max_lock_time is None:
90            max_lock_time = datetime.timedelta(seconds = 600)
91        elif type(max_lock_time) in (int, long, float):
92            max_lock_time = datetime.timedelta(seconds = max_lock_time)
93        self.max_lock_time = max_lock_time
94        self.no_excl = no_excl
95
96    def get_filename(self, key):
97        filename = self.filename
98        if callable(filename):
99            filename = filename(key)
100        elif 'XXX' in filename:
101            filename = filename.replace('XXX', key)
102        else:
103            filename += key
104        return filename
105
106    def get_new_filename(self, real_filename):
107        return real_filename + '.new'
108
109    def get_lock_filename(self, real_filename):
110        return real_filename + '.lock'
111
112    def save_with_locking(self, key, value, blocking, do_save):
113        real_filename = self.get_filename(key)
114        lock_filename = self.get_lock_filename(real_filename)
115        new_filename = self.get_new_filename(real_filename)
116        error = None
117        loop_cnt = 0
118
119        while True:
120            loop_cnt += 1
121            if self.no_excl:
122                # if usage of O_EXCL flag is disabled
123                try:
124                    fd = os.open(lock_filename, os.O_RDONLY)
125                except OSError:
126                    fd = os.open(lock_filename, os.O_CREAT)
127                    break
128            else:
129                # locking using O_EXCL flag
130                try:
131                    fd = os.open(lock_filename, os.O_CREAT | os.O_EXCL)
132                except OSError, e:
133                    error = e
134                else:
135                    break
136                try:
137                    fd = os.open(lock_filename, os.O_RDONLY)
138                except OSError:
139                    if loop_cnt >= 3:
140                        raise error
141                    continue
142            try:
143                # check lock time
144                lock_time = datetime.datetime.fromtimestamp(os.fstat(fd).st_ctime)
145                if lock_time + self.max_lock_time <= datetime.datetime.now():
146                    os.unlink(lock_filename)
147                    continue
148                if not blocking:
149                    return False
150                # wait until lock file was removed
151                select.select((), (), (fd,), 5)
152            finally:
153                os.close(fd)
154        try:
155            # save data to newly created file, then just move it to the real
156            # file, so that other precesses/threads can read old data while new
157            # data is not completely written
158            do_save(new_filename, value)
159            try:
160                shutil.move(new_filename, real_filename)
161            except IOError:
162                # in some cases db module appends '.db' suffix to the file name
163                # when saving...
164                if (not os.path.exists(new_filename) and
165                        os.path.exists(new_filename + '.db')):
166                    shutil.move(new_filename + '.db', real_filename)
167                else:
168                    raise
169        finally:
170            os.close(fd)
171            os.unlink(lock_filename)
172        return True
173
174class MemMultiHashDriver(BaseMultiHashDriver):
175    """
176    Memory multihash driver.
177
178    >>> drv = MemMultiHashDriver()
179    >>> drv.save('foo', dict(bar = 3))
180    True
181    >>> drv.load('foo')['bar']
182    3
183    """
184    def __init__(self):
185        self.db = {}
186        self.db_mtime = {}
187        self.db_lock = threading.Lock()
188
189    def load(self, key):
190        return self.db[key]
191
192    def get_mtime(self, key):
193        return self.db_mtime[key]
194
195    def save(self, key, value, blocking = True):
196        result = self.db_lock.acquire(blocking)
197        if not result:
198            return False
199        try:
200            if isinstance(value, dict):
201                value = value.copy()
202            else:
203                value = dict(value)
204            self.db[key] = value
205            self.db_mtime[key] = datetime.datetime.now()
206        finally:
207            self.db_lock.release()
208        return True
209
210class ShelveMultiHashDriver(BaseMultiHashDriver, MultiHashInFilesMixin):
211    """
212    Multihash driver that use shelve module to store hashes.
213
214    >>> import os
215    >>> import os.path
216    >>> filename = 'shelve_multihash_test_XXX.db'
217    >>> os.path.exists(filename.replace('XXX', 'foo'))
218    False
219    >>> drv = ShelveMultiHashDriver(filename)
220    >>> drv.save('foo', dict(bar = 3))
221    True
222    >>> os.path.exists(filename.replace('XXX', 'foo'))
223    True
224    >>> drv.load('foo')['bar']
225    3
226    >>> def test_generator():
227    ...   for i in range(100):
228    ...     if i == 5:
229    ...       x = drv.save('foo', dict(bar = 3), blocking = False)
230    ...       assert x == False
231    ...     yield ('bar%d' % i, i)
232    >>> drv.save('foo', test_generator())
233    True
234    >>> drv.load('foo')['bar55']
235    55
236    >>> os.unlink(filename.replace('XXX', 'foo'))
237    """
238    def __init__(self, filename, max_lock_time = None, no_excl = False,
239            db_module = None):
240        """
241        @param filename: file name to use for hash files (string or callable)
242        @keyword max_lock_time: maximum lock time in seconds or as
243                                datetime.timedelta object
244        @keyword no_excl: disable usage of O_EXCL flag when locking
245        @keyword db_module: DBM module to use
246        """
247        MultiHashInFilesMixin.__init__(self, filename, max_lock_time, no_excl)
248        if isinstance(db_module, basestring):
249            db_module = __import__(db_module)
250        self.db_module = db_module
251
252    def load(self, key):
253        try:
254            return shelve.open(self.get_filename(key), 'r')
255        except anydbm.error:
256            raise KeyError(key)
257
258    def get_mtime(self, key):
259        try:
260            return datetime.datetime.fromtimestamp(os.stat(
261                self.get_filename(key)).st_mtime)
262        except OSError:
263            raise KeyError(key)
264
265    def save(self, key, value, blocking = True):
266        def do_save(new_filename, value):
267            if isinstance(value, dict):
268                value = value.items()
269            if self.db_module:
270                # if appropriate db module is specified, use it to create empty
271                # database
272                db = self.db_module.open(new_filename, 'n')
273                db.sync()
274                db = shelve.open(new_filename, 'w')
275            else:
276                db = shelve.open(new_filename, 'n')
277            for k, v in value:
278                db[k] = v
279            db.sync()
280        return self.save_with_locking(key, value, blocking, do_save)
281
282if __name__ == "__main__":
283    import doctest
284    doctest.testmod()
Note: See TracBrowser for help on using the repository browser.
[ антивирус 2010 бесплатно антивирус 2010 антивирус ]