Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

I think I've seen this ORM somewhere before...

root/branches/crazycache/dejavu/storage/storefs.py

Revision 543 (checked in by fumanchu, 4 years ago)

Changed to unit(cls, **kwargs) sig throughout. Also changed sandbox.recall to include order, limit, and offset args; removed **kwargs from recall, but the 'expr' arg may now be a dict for all x/multi/recall methods throughout. Removed inheritance code. Added a 'sum' method to StorageManager?.

  • Property svn:eol-style set to native
Line 
1 try:
2     import cPickle as pickle
3 except ImportError:
4     import pickle
5
6 import os
7 import shutil
8 import threading
9 import time
10
11 import dejavu
12 from dejavu import errors, logflags, storage
13
14 from geniusql import logic
15
16
17 class StorageManagerFolders(storage.StorageManager):
18     """StoreManager to save and retrieve Units in flat files.
19     
20     This is slightly different from shelve, in that this saves
21     each Unit to its own folder, and each property to its own file.
22     This is useful for storing large items like images and video
23     for which you want both a native, readable file for the data
24     and also the ability to store metadata.
25     
26     Unit properties which you want to use for binary data should
27     be of type 'str'. The filename will be the property name,
28     and the extension should be supplied in config.
29     
30     Be aware that this SM stores each Unit in its own folder,
31     and your operating system may have hard limits or performance
32     issues when the number of Units of a given class (or the number
33     of classes) grows large.
34     """
35    
36     def __init__(self, allOptions={}):
37         storage.StorageManager.__init__(self, allOptions)
38        
39         root = allOptions['root']
40         if not os.path.isabs(root):
41             root = os.path.join(os.getcwd(), root)
42         self.root = root
43        
44         self.mode = int(allOptions.get('mode', '0777'), 8)
45        
46         # Character to use for joining multiple identifiers
47         # into a single folder name.
48         self.idsepchar = allOptions.get('idsepchar', '_')
49        
50         # Map of file extensions. Keys should be "clsname.propname"
51         # and values should contain the dot (if desired).
52         self.extmap = dict([(k, v) for k, v in allOptions.iteritems()
53                             if '.' in k])
54         self.extdefault = allOptions.get('extdefault', '.txt')
55    
56     def shelf(self, cls):
57         """Return path for the given cls."""
58         return os.path.join(self.root, cls.__name__)
59    
60     def get_lock(self, cls):
61         clsname = cls.__name__
62         path = os.path.join(self.root, clsname, "class.lock")
63         while True:
64             try:
65                 lockfd = os.open(path, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
66             except OSError:
67                 time.sleep(0.1)
68             else:
69                 os.close(lockfd)
70                 break
71    
72     def release_lock(self, cls):
73         clsname = cls.__name__
74         path = os.path.join(self.root, clsname, "class.lock")
75         os.unlink(path)
76    
77     def _pull(self, cls, root, idset):
78         """Return data from the given path as a dict."""
79        
80         # Grab identifiers from the folder name.
81         unitdict = dict(self.ids_from_folder(cls, idset))
82        
83         # Grab values from the files in the folder.
84         clsname = cls.__name__
85         for k in cls.properties:
86             extkey = "%s.%s" % (clsname, k)
87             ext = self.extmap.get(extkey, self.extdefault)
88            
89             fname = os.path.join(root, idset, "%s%s" % (k, ext))
90             try:
91                 v = open(fname, 'rb').read()
92                 if getattr(cls, k).type is not str:
93                     v = pickle.loads(v)
94             except (EOFError, IOError), exc:
95                 v = None
96             unitdict[k] = v
97        
98         return unitdict
99    
100     def unit(self, cls, **kwargs):
101         """A single Unit which matches the given kwargs, else None.
102         
103         The first Unit matching the kwargs is returned; if no Units match,
104         None is returned.
105         """
106         if set(kwargs.keys()) == set(cls.identifiers):
107             # Looking up a Unit by its identifiers.
108             # Skip walking the shelf.
109             if self.logflags & logflags.RECALL:
110                 self.log(logflags.RECALL.message(cls, kwargs))
111            
112             classdir = self.shelf(cls)
113             folder = self.idsepchar.join([str(kwargs[k])
114                                           for k in cls.identifiers])
115             if not folder:
116                 folder = "__blank__"
117            
118             if os.path.exists(os.path.join(classdir, folder)):
119                 unit = cls()
120                 unit._properties = self._pull(cls, classdir, folder)
121                 unit.cleanse()
122                 return unit
123             else:
124                 return None
125        
126         return storage.StorageManager.unit(self, cls, **kwargs)
127    
128     def xrecall(self, classes, expr=None, order=None, limit=None, offset=None):
129         if isinstance(classes, dejavu.UnitJoin):
130             return self._xmultirecall(classes, expr, order=order,
131                                       limit=limit, offset=offset)
132        
133         cls = classes
134         if self.logflags & logflags.RECALL:
135             self.log(logflags.RECALL.message(cls, expr))
136        
137         path = self.shelf(cls)
138         self.get_lock(cls)
139         try:
140             root, dirs, _ = os.walk(path).next()
141         finally:
142             self.release_lock(cls)
143        
144         data = self._xrecall_inner(cls, expr, root, dirs)
145         return self._paginate(data, order, limit, offset, single=True)
146    
147     def _xrecall_inner(self, cls, expr, root, dirs):
148         """Private helper for self.xrecall."""
149         for idset in dirs:
150             unit = cls()
151             unit._properties = self._pull(cls, root, idset)
152             if expr is None or expr(unit):
153                 unit.cleanse()
154                 # Must yield a sequence for use in _paginate.
155                 yield (unit,)
156    
157     def ids_from_folder(self, cls, fname):
158         """Return a list of identifier (k, v) pairs for the named folder."""
159         if cls.identifiers:
160             if fname == "__blank__":
161                 return [(k, "") for k in cls.identifiers]
162            
163             # cls.identifiers is ordered, and should match
164             # the order of atoms inside fname.
165             return [(k, getattr(cls, k).coerce(None, v))
166                     for k, v in zip(cls.identifiers,
167                                     fname.split(self.idsepchar))
168                     ]
169         else:
170             return []
171    
172     def folder_from_unit(self, unit):
173         """Return the folder name for the given unit."""
174         if unit.identifiers:
175             folder = self.idsepchar.join([str(getattr(unit, k))
176                                           for k in unit.identifiers])
177         else:
178             folder = str(hash(unit))
179        
180         if not folder:
181             folder = "__blank__"
182         return folder
183    
184     def _push(self, unit, abspath):
185         """Persist unit properties into its folder.
186         
187         This assumes the folder exists and we have a lock on it.
188         """
189         cls = unit.__class__
190         for key, value in unit._properties.iteritems():
191             extkey = "%s.%s" % (cls.__name__, key)
192             ext = self.extmap.get(extkey, self.extdefault)
193             fname = "%s%s" % (key, ext)
194             fname = os.path.join(abspath, fname)
195             try:
196                 f = open(fname, 'wb')
197             except IOError:
198                 raise
199            
200             try:
201                 if getattr(cls, key).type is not str:
202                     value = pickle.dumps(value)
203                 f.write(value)
204             finally:
205                 f.close()
206    
207     def reserve(self, unit):
208         """Reserve a persistent slot for unit."""
209         if self.logflags & logflags.RESERVE:
210             self.log(logflags.RESERVE.message(unit))
211        
212         cls = unit.__class__
213         path = self.shelf(cls)
214         self.get_lock(cls)
215         try:
216             if not unit.sequencer.valid_id(unit.identity()):
217                 root, dirs, _ = os.walk(path).next()
218                 ids = [[v for k, v in self.ids_from_folder(cls, dirname)]
219                        for dirname in dirs]
220                 unit.sequencer.assign(unit, ids)
221            
222             fname = self.folder_from_unit(unit)
223             fname = os.path.join(path, fname)
224             if not os.path.exists(fname):
225                 os.mkdir(fname, self.mode)
226             self._push(unit, fname)
227         finally:
228             self.release_lock(cls)
229             unit.cleanse()
230    
231     def save(self, unit, forceSave=False):
232         """Update storage from unit's data."""
233         if self.logflags & logflags.SAVE:
234             self.log(logflags.SAVE.message(unit, forceSave))
235        
236         if forceSave or unit.dirty():
237             cls = unit.__class__
238             path = self.shelf(cls)
239             self.get_lock(cls)
240             try:
241                 fname = self.folder_from_unit(unit)
242                 self._push(unit, os.path.join(path, fname))
243             finally:
244                 self.release_lock(cls)
245                 unit.cleanse()
246    
247     def destroy(self, unit):
248         """Delete the unit."""
249         if self.logflags & logflags.DESTROY:
250             self.log(logflags.DESTROY.message(unit))
251        
252         cls = unit.__class__
253         path = self.shelf(cls)
254         self.get_lock(cls)
255         try:
256             fname = self.folder_from_unit(unit)
257             shutil.rmtree(os.path.join(path, fname))
258         finally:
259             self.release_lock(cls)
260    
261     __version__ = "0.2"
262    
263     def version(self):
264         return "%s %s" % (self.__class__.__name__, self.__version__)
265    
266    
267     #                               Schemas                               #
268    
269     def create_database(self, conflicts='error'):
270         """Create internal structures for the entire database.
271         
272         conflicts: see errors.conflict.
273         """
274         if self.logflags & logflags.DDL:
275             self.log(logflags.DDL.message("create database"))
276        
277         try:
278             os.makedirs(self.root)
279         except Exception, x:
280             errors.conflict(conflicts, str(x))
281    
282     def drop_database(self, conflicts='error'):
283         """Destroy internal structures for the entire database.
284         
285         conflicts: see errors.conflict.
286         """
287         if self.logflags & logflags.DDL:
288             self.log(logflags.DDL.message("drop database"))
289        
290         try:
291             shutil.rmtree(self.root)
292         except Exception, x:
293             errors.conflict(conflicts, str(x))
294    
295     def create_storage(self, cls, conflicts='error'):
296         """Destroy internal structures for the given class.
297         
298         conflicts: see errors.conflict.
299         """
300         if self.logflags & logflags.DDL:
301             self.log(logflags.DDL.message("create storage %s" % cls))
302        
303         try:
304             path = self.shelf(cls)
305             os.mkdir(path, self.mode)
306         except Exception, x:
307             errors.conflict(conflicts, str(x))
308    
309     def has_storage(self, cls):
310         """If storage structures exist for the given class, return True."""
311         return os.path.exists(self.shelf(cls))
312    
313     def drop_storage(self, cls, conflicts='error'):
314         """Destroy internal structures for the given class.
315         
316         conflicts: see errors.conflict.
317         """
318         if self.logflags & logflags.DDL:
319             self.log(logflags.DDL.message("drop storage %s" % cls))
320        
321         try:
322             shutil.rmtree(self.shelf(cls))
323         except Exception, x:
324             errors.conflict(conflicts, str(x))
325    
326     def add_property(self, cls, name, conflicts='error'):
327         """Add internal structures for the given property.
328         
329         conflicts: see errors.conflict.
330         """
331         if self.logflags & logflags.DDL:
332             self.log(logflags.DDL.message("add property %s %s"
333                                           % (cls, name)))
334         pass
335    
336     def has_property(self, cls, name):
337         """If storage structures exist for the given property, return True."""
338         return True
339    
340     def drop_property(self, cls, name, conflicts='error'):
341         """Destroy internal structures for the given property.
342         
343         conflicts: see errors.conflict.
344         """
345         if self.logflags & logflags.DDL:
346             self.log(logflags.DDL.message("drop property %s %s"
347                                           % (cls, name)))
348        
349         extkey = "%s.%s" % (cls.__name__, name)
350         ext = self.extmap.get(extkey, self.extdefault)
351         fname = "%s%s" % (name, ext)
352        
353         path = self.shelf(cls)
354         self.get_lock(cls)
355         try:
356             root, dirs, _ = os.walk(path).next()
357             for idset in dirs:
358                 try:
359                     os.remove(os.path.join(root, idset, fname))
360                 except Exception, x:
361                     errors.conflict(conflicts, str(x))
362         finally:
363             self.release_lock(cls)
364    
365     def rename_property(self, cls, oldname, newname, conflicts='error'):
366         """Rename internal structures for the given property.
367         
368         conflicts: see errors.conflict.
369         """
370         if self.logflags & logflags.DDL:
371             self.log(logflags.DDL.message(
372                 "rename property %s from %s to %s"
373                 % (cls, oldname, newname)))
374        
375         extkey = "%s.%s" % (cls.__name__, oldname)
376         ext = self.extmap.get(extkey, self.extdefault)
377         oname = "%s%s" % (oldname, ext)
378        
379         extkey = "%s.%s" % (cls.__name__, newname)
380         ext = self.extmap.get(extkey, self.extdefault)
381         nname = "%s%s" % (newname, ext)
382        
383         path = self.shelf(cls)
384         self.get_lock(cls)
385         try:
386             root, dirs, _ = os.walk(path).next()
387             for idset in dirs:
388                 try:
389                     os.rename(os.path.join(root, idset, oname),
390                               os.path.join(root, idset, nname))
391                 except Exception, x:
392                     errors.conflict(conflicts, str(x))
393         finally:
394             self.release_lock(cls)
395
Note: See TracBrowser for help on using the browser.