Cleaned-up and re-designed preferences dialog
[gpodder.git] / src / gpodder / minidb.py
blob1aeed0e27ade42b800b3db7f580eb6c35578babe
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
4 # gPodder - A media aggregator and podcast client
5 # Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
7 # gPodder is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # gPodder is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 # gpodder.minidb - A simple SQLite store for Python objects
22 # Thomas Perl, 2010-01-28
24 # based on: "ORM wie eine Kirchenmaus - a very poor ORM implementation
25 # by thp, 2009-11-29 (thpinfo.com/about)"
28 # For Python 2.5, we need to request the "with" statement
29 from __future__ import with_statement
31 import sqlite3.dbapi2 as sqlite
33 import threading
35 class Store(object):
36 def __init__(self, filename=':memory:'):
37 self.db = sqlite.connect(filename, check_same_thread=False)
38 self.lock = threading.RLock()
40 def _schema(self, class_):
41 return class_.__name__, list(sorted(class_.__slots__))
43 def _set(self, o, slot, value):
44 # Set a slot on the given object to value, doing a cast if
45 # necessary. The value None is special-cased and never cast.
46 if value is not None:
47 value = o.__class__.__slots__[slot](value)
48 setattr(o, slot, value)
50 def commit(self):
51 with self.lock:
52 self.db.commit()
54 def close(self):
55 with self.lock:
56 self.db.close()
58 def _register(self, class_):
59 with self.lock:
60 table, slots = self._schema(class_)
61 cur = self.db.execute('PRAGMA table_info(%s)' % table)
62 available = cur.fetchall()
64 if available:
65 available = [row[1] for row in available]
66 missing_slots = (s for s in slots if s not in available)
67 for slot in missing_slots:
68 self.db.execute('ALTER TABLE %s ADD COLUMN %s TEXT' % (table,
69 slot))
70 else:
71 self.db.execute('CREATE TABLE %s (%s)' % (table,
72 ', '.join('%s TEXT'%s for s in slots)))
74 def update(self, o, **kwargs):
75 self.remove(o)
76 for k, v in kwargs.items():
77 setattr(o, k, v)
78 self.save(o)
80 def save(self, o):
81 if hasattr(o, '__iter__'):
82 for child in o:
83 self.save(child)
84 return
86 with self.lock:
87 self._register(o.__class__)
88 table, slots = self._schema(o.__class__)
90 # Only save values that have values set (non-None values)
91 slots = [s for s in slots if getattr(o, s) is not None]
93 values = [str(getattr(o, slot)) for slot in slots]
94 self.db.execute('INSERT INTO %s (%s) VALUES (%s)' % (table,
95 ', '.join(slots), ', '.join('?'*len(slots))), values)
97 def remove(self, o):
98 if hasattr(o, '__iter__'):
99 for child in o:
100 self.remove(child)
101 return
103 with self.lock:
104 self._register(o.__class__)
105 table, slots = self._schema(o.__class__)
107 # Use "None" as wildcard selector in remove actions
108 slots = [s for s in slots if getattr(o, s, None) is not None]
110 values = [getattr(o, slot) for slot in slots]
111 self.db.execute('DELETE FROM %s WHERE %s' % (table,
112 ' AND '.join('%s=?'%s for s in slots)), values)
114 def load(self, class_, **kwargs):
115 with self.lock:
116 self._register(class_)
117 table, slots = self._schema(class_)
118 sql = 'SELECT %s FROM %s' % (', '.join(slots), table)
119 if kwargs:
120 sql += ' WHERE %s' % (' AND '.join('%s=?' % k for k in kwargs))
121 try:
122 cur = self.db.execute(sql, kwargs.values())
123 except Exception, e:
124 print sql
125 raise
126 def apply(row):
127 o = class_.__new__(class_)
128 for attr, value in zip(slots, row):
129 self._set(o, attr, value)
130 return o
131 return [apply(row) for row in cur.fetchall()]
133 def get(self, class_, **kwargs):
134 result = self.load(class_, **kwargs)
135 if result:
136 return result[0]
137 else:
138 return None
140 if __name__ == '__main__':
141 class Person(object):
142 __slots__ = {'username': str, 'id': int}
144 def __init__(self, username, id):
145 self.username = username
146 self.id = id
148 def __repr__(self):
149 return '<Person "%s" (%d)>' % (self.username, self.id)
151 m = Store()
152 m.save(Person('User %d' % x, x*20) for x in range(50))
154 p = m.get(Person, id=200)
155 print p
156 m.remove(p)
157 p = m.get(Person, id=200)
159 # Remove some persons again (deletion by value!)
160 m.remove(Person('User %d' % x, x*20) for x in range(40))
162 class Person(object):
163 __slots__ = {'username': str, 'id': int, 'mail': str}
165 def __init__(self, username, id, mail):
166 self.username = username
167 self.id = id
168 self.mail = mail
170 def __repr__(self):
171 return '<Person "%s" (%s)>' % (self.username, self.mail)
173 # A schema update takes place here
174 m.save(Person('User %d' % x, x*20, 'user@home.com') for x in range(50))
175 print m.load(Person)