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
32 import sqlite3
.dbapi2
as sqlite
35 from pysqlite2
import dbapi2
as sqlite
37 raise Exception('Please install SQLite3 support.')
43 def __init__(self
, filename
=':memory:'):
44 self
.db
= sqlite
.connect(filename
, check_same_thread
=False)
45 self
.lock
= threading
.RLock()
47 def _schema(self
, class_
):
48 return class_
.__name
__, list(sorted(class_
.__slots
__))
50 def _set(self
, o
, slot
, value
):
51 # Set a slot on the given object to value, doing a cast if
52 # necessary. The value None is special-cased and never cast.
53 cls
= o
.__class
__.__slots
__[slot
]
55 if isinstance(value
, unicode):
56 value
= value
.decode('utf-8')
58 setattr(o
, slot
, value
)
68 def _register(self
, class_
):
70 table
, slots
= self
._schema
(class_
)
71 cur
= self
.db
.execute('PRAGMA table_info(%s)' % table
)
72 available
= cur
.fetchall()
75 available
= [row
[1] for row
in available
]
76 missing_slots
= (s
for s
in slots
if s
not in available
)
77 for slot
in missing_slots
:
78 self
.db
.execute('ALTER TABLE %s ADD COLUMN %s TEXT' % (table
,
81 self
.db
.execute('CREATE TABLE %s (%s)' % (table
,
82 ', '.join('%s TEXT'%s for s
in slots
)))
85 if isinstance(v
, str) or isinstance(v
, unicode):
90 def update(self
, o
, **kwargs
):
92 for k
, v
in kwargs
.items():
97 if hasattr(o
, '__iter__'):
103 self
._register
(o
.__class
__)
104 table
, slots
= self
._schema
(o
.__class
__)
106 # Only save values that have values set (non-None values)
107 slots
= [s
for s
in slots
if getattr(o
, s
, None) is not None]
109 values
= [self
.convert(getattr(o
, slot
)) for slot
in slots
]
110 self
.db
.execute('INSERT INTO %s (%s) VALUES (%s)' % (table
,
111 ', '.join(slots
), ', '.join('?'*len(slots
))), values
)
113 def delete(self
, class_
, **kwargs
):
115 self
._register
(class_
)
116 table
, slots
= self
._schema
(class_
)
117 sql
= 'DELETE FROM %s' % (table
,)
119 sql
+= ' WHERE %s' % (' AND '.join('%s=?' % k
for k
in kwargs
))
121 self
.db
.execute(sql
, kwargs
.values())
127 if hasattr(o
, '__iter__'):
133 self
._register
(o
.__class
__)
134 table
, slots
= self
._schema
(o
.__class
__)
136 # Use "None" as wildcard selector in remove actions
137 slots
= [s
for s
in slots
if getattr(o
, s
, None) is not None]
139 values
= [self
.convert(getattr(o
, slot
)) for slot
in slots
]
140 self
.db
.execute('DELETE FROM %s WHERE %s' % (table
,
141 ' AND '.join('%s=?'%s for s
in slots
)), values
)
143 def load(self
, class_
, **kwargs
):
145 self
._register
(class_
)
146 table
, slots
= self
._schema
(class_
)
147 sql
= 'SELECT %s FROM %s' % (', '.join(slots
), table
)
149 sql
+= ' WHERE %s' % (' AND '.join('%s=?' % k
for k
in kwargs
))
151 cur
= self
.db
.execute(sql
, kwargs
.values())
155 o
= class_
.__new
__(class_
)
156 for attr
, value
in zip(slots
, row
):
158 self
._set
(o
, attr
, value
)
159 except ValueError, ve
:
162 return filter(lambda x
: x
is not None, [apply(row
) for row
in cur
])
164 def get(self
, class_
, **kwargs
):
165 result
= self
.load(class_
, **kwargs
)
171 if __name__
== '__main__':
172 class Person(object):
173 __slots__
= {'username': str, 'id': int}
175 def __init__(self
, username
, id):
176 self
.username
= username
180 return '<Person "%s" (%d)>' % (self
.username
, self
.id)
183 m
.save(Person('User %d' % x
, x
*20) for x
in range(50))
185 p
= m
.get(Person
, id=200)
188 p
= m
.get(Person
, id=200)
190 # Remove some persons again (deletion by value!)
191 m
.remove(Person('User %d' % x
, x
*20) for x
in range(40))
193 class Person(object):
194 __slots__
= {'username': str, 'id': int, 'mail': str}
196 def __init__(self
, username
, id, mail
):
197 self
.username
= username
202 return '<Person "%s" (%s)>' % (self
.username
, self
.mail
)
204 # A schema update takes place here
205 m
.save(Person('User %d' % x
, x
*20, 'user@home.com') for x
in range(50))