bwDB.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. #!/usr/bin/env python3
  2. # bwDB - CRUD library for sqlite 3
  3. # by Bill Weinman [http://bw.org/]
  4. # Copyright 2010-2017 The BearHeart Group LLC
  5. # 1.2.0 - 2017-09-27 -
  6. # lots of cleanup. uses f-strings so requires Python 3.6
  7. import sqlite3
  8. __version__ = '1.2.0'
  9. class bwDB:
  10. def __init__(self, **kwargs):
  11. """
  12. db = bwDB( [ table = ''] [, filename = ''] )
  13. constructor method
  14. table is for CRUD methods
  15. filename is for connecting to the database file
  16. """
  17. # see filename @property decorators below
  18. self._filename = kwargs.get('filename')
  19. self._table = kwargs.get('table', '')
  20. def set_table(self, tablename):
  21. self._table = tablename
  22. def sql_do(self, sql, params=()):
  23. """
  24. db.sql_do( sql[, params] )
  25. method for non-select queries
  26. sql is string containing SQL
  27. params is list containing parameters
  28. returns nothing
  29. """
  30. self._db.execute(sql, params)
  31. self._db.commit()
  32. def sql_do_nocommit(self, sql, params=()):
  33. """
  34. sql_do_nocommit( sql[, params] )
  35. method for non-select queries *without commit*
  36. sql is string containing SQL
  37. params is list containing parameters
  38. returns nothing
  39. """
  40. self._db.execute(sql, params)
  41. def sql_query(self, sql, params=()):
  42. """
  43. db.sql_query( sql[, params] )
  44. generator method for queries
  45. sql is string containing SQL
  46. params is list containing parameters
  47. returns a generator with one row per iteration
  48. each row is a Row factory
  49. """
  50. c = self._db.execute(sql, params)
  51. for r in c:
  52. yield r
  53. def sql_query_row(self, sql, params=()):
  54. """
  55. db.sql_query_row( sql[, params] )
  56. query for a single row
  57. sql is string containing SQL
  58. params is list containing parameters
  59. returns a single row as a Row factory
  60. """
  61. c = self._db.execute(sql, params)
  62. return c.fetchone()
  63. def sql_query_value(self, sql, params=()):
  64. """
  65. db.sql_query_row( sql[, params] )
  66. query for a single value
  67. sql is string containing SQL
  68. params is list containing parameters
  69. returns a single value
  70. """
  71. c = self._db.execute(sql, params)
  72. return c.fetchone()[0]
  73. def commit(self):
  74. self._db.commit()
  75. def getrec(self, recid):
  76. """
  77. db.getrec(recid)
  78. get a single row, by id
  79. """
  80. query = f'SELECT * FROM {self._table} WHERE id = ?'
  81. c = self._db.execute(query, (recid,))
  82. return c.fetchone()
  83. def getrecs(self):
  84. """
  85. db.getrecs()
  86. get all rows, returns a generator of Row factories
  87. """
  88. query = f'SELECT * FROM {self._table}'
  89. c = self._db.execute(query)
  90. for r in c:
  91. yield r
  92. def insert_nocommit(self, rec):
  93. """
  94. db.insert(rec)
  95. insert a single record into the table
  96. rec is a dict with key/value pairs corresponding to table schema
  97. omit id column to let SQLite generate it
  98. """
  99. klist = sorted(rec.keys())
  100. values = [rec[v] for v in klist] # a list of values ordered by key
  101. q = 'INSERT INTO {} ({}) VALUES ({})'.format(
  102. self._table,
  103. ', '.join(klist),
  104. ', '.join('?' * len(values))
  105. )
  106. c = self._db.execute(q, values)
  107. return c.lastrowid
  108. def insert(self, rec):
  109. lastrowid = self.insert_nocommit(rec)
  110. self._db.commit()
  111. return lastrowid
  112. def update_nocommit(self, recid, rec):
  113. """
  114. db.update(id, rec)
  115. update a row in the table
  116. id is the value of the id column for the row to be updated
  117. rec is a dict with key/value pairs corresponding to table schema
  118. """
  119. klist = sorted(rec.keys())
  120. values = [rec[v] for v in klist] # a list of values ordered by key
  121. for i, k in enumerate(klist): # don't udpate id
  122. if k == 'id':
  123. del klist[i]
  124. del values[i]
  125. q = 'UPDATE {} SET {} WHERE id = ?'.format(
  126. self._table,
  127. ', '.join(map(lambda s: '{} = ?'.format(s), klist))
  128. )
  129. self._db.execute(q, values + [recid])
  130. def update(self, recid, rec):
  131. self.update_nocommit(recid, rec)
  132. self._db.commit()
  133. def delete_nocommit(self, recid):
  134. """
  135. db.delete(recid)
  136. delete a row from the table, by recid
  137. """
  138. query = f'DELETE FROM {self._table} WHERE id = ?'
  139. self._db.execute(query, [recid])
  140. def delete(self, recid):
  141. self.delete_nocommit(recid)
  142. self._db.commit()
  143. def countrecs(self):
  144. """
  145. db.countrecs()
  146. count the records in the table
  147. returns a single integer value
  148. """
  149. query = f'SELECT COUNT(*) FROM {self._table}'
  150. c = self._db.execute(query)
  151. return c.fetchone()[0]
  152. # filename property
  153. @property
  154. def _filename(self):
  155. return self._dbfilename
  156. @_filename.setter
  157. def _filename(self, fn):
  158. self._dbfilename = fn
  159. self._db = sqlite3.connect(fn)
  160. self._db.row_factory = sqlite3.Row
  161. @_filename.deleter
  162. def _filename(self):
  163. self.close()
  164. def close(self):
  165. self._db.close()
  166. del self._dbfilename
  167. def test():
  168. fn = ':memory:' # in-memory database
  169. t = 'foo'
  170. recs = [
  171. dict(string='one', number=42),
  172. dict(string='two', number=73),
  173. dict(string='three', number=123)
  174. ]
  175. # -- for file-based database
  176. # try: os.stat(fn)
  177. # except: pass
  178. # else:
  179. # print('Delete', fn)
  180. # os.unlink(fn)
  181. print('bwDB version', __version__)
  182. print(f'Create database file {fn} ...', end='')
  183. db = bwDB(filename=fn, table=t)
  184. print('Done.')
  185. print('Create table ... ', end='')
  186. db.sql_do(f' DROP TABLE IF EXISTS {t} ')
  187. db.sql_do(f' CREATE TABLE {t} ( id INTEGER PRIMARY KEY, string TEXT, number INTEGER ) ')
  188. print('Done.')
  189. print('Insert into table ... ', end='')
  190. for r in recs:
  191. db.insert(r)
  192. print('Done.')
  193. print(f'There are {db.countrecs()} rows')
  194. print('Read from table')
  195. for r in db.getrecs():
  196. print(dict(r))
  197. print('Update table')
  198. db.update(2, dict(string='TWO'))
  199. print(dict(db.getrec(2)))
  200. print('Insert an extra row ... ', end='')
  201. newid = db.insert({'string': 'extra', 'number': 512})
  202. print(f'(id is {newid})')
  203. print(dict(db.getrec(newid)))
  204. print(f'There are {db.countrecs()} rows')
  205. print('Now delete it')
  206. db.delete(newid)
  207. print(f'There are {db.countrecs()} rows')
  208. for r in db.getrecs():
  209. print(dict(r))
  210. for r in db.sql_query(f"select * from {t}"):
  211. print(r)
  212. db.close()
  213. if __name__ == "__main__": test()