Skip to content

Instantly share code, notes, and snippets.

@romanvm
Last active September 22, 2022 18:33
Show Gist options
  • Save romanvm/ab95d6ea7bb08a8763daba097d8afe6a to your computer and use it in GitHub Desktop.
Save romanvm/ab95d6ea7bb08a8763daba097d8afe6a to your computer and use it in GitHub Desktop.
Simple key-value storage based on SQLite
# coding: utf-8
#
# Copyright (c) 2017 Roman Miroshnychenko <[email protected]>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""
This module provides SQLite-based key-value storage
It is compatible with both Python 2 and 3.
"""
from __future__ import print_function
try:
import cPickle as pickle
except ImportError:
import pickle
import sqlite3
try:
basestring
except NameError:
basestring = str
class SqliteStorage(object):
"""
SQLite-based key-value storage
It can work as a context manager.
Example::
with SqliteStorage('storage.db') as stor:
stor.set_value('foo', 'bar')
"""
def __init__(self, db_path, table='storage'):
"""
:param db_path: full path to the datebase including filename and extention
:type db_path: str
:param table: table name (optional)
:type table: str
"""
self._table = table
self._dirty = False
self._connection = sqlite3.connect(db_path)
self._cursor = self._connection.cursor()
self._cursor.execute('SELECT name FROM sqlite_master WHERE type="table" AND name=?', (self._table,))
if self._cursor.fetchone() is None:
self._cursor.execute('CREATE TABLE {0} (skey TEXT PRIMARY KEY, value BLOB)'.format(self._table))
self._dirty = True
def set_value(self, key, value):
"""
Store a value with the given key
.. note:: ``value`` must be picklable
:param key: storage key
:type key: str
:param value: a value to store
:raises TypeError: if the key is not of class:`str` type
"""
if not isinstance(key, basestring):
raise TypeError('key must be of str type!')
self._cursor.execute('INSERT OR REPLACE INTO {0} VALUES (?,?)'.format(self._table), (key, bytes(pickle.dumps(value))))
self._dirty = True
def get_value(self, key):
"""
Retrieve a value from the storage by the given key
:param key: storage key
:type key: str
:return: a stored value
:raises TypeError: if the key is not of :class:`str` type
:raises KeyError: if the key is not found in the storage
"""
if not isinstance(key, basestring):
raise TypeError('key must be of str type!')
self._cursor.execute('SELECT value FROM {0} WHERE skey=?'.format(self._table), (key,))
row = self._cursor.fetchone()
if row is None:
raise KeyError(key)
return pickle.loads(bytes(row[0]))
def close(self):
"""Close storage and commit changes if any"""
if self._dirty:
self._connection.commit()
self._connection.close()
def keys(self):
"""
:return: iterator for storage keys
:rtype: types.GeneratorType
"""
self._cursor.execute('SELECT skey FROM {0}'.format(self._table))
for row in self._cursor.fetchall():
yield row[0]
def values(self):
"""
:return: iterator stored values
:rtype: types.GeneratorType
"""
self._cursor.execute('SELECT value FROM {0}'.format(self._table))
for row in self._cursor.fetchall():
yield pickle.loads(bytes(row[0]))
def items(self):
"""
:return: iterator for (key, value) tuples
:rtype: types.GeneratorType
"""
self._cursor.execute('SELECT * FROM {0}'.format(self._table))
for row in self._cursor.fetchall():
yield row[0], pickle.loads(bytes(row[1]))
def __iter__(self):
return self.keys()
def __enter__(self):
return self
def __exit__(self, t, v, tb):
self.close()
def __contains__(self, key):
try:
self.get_value(key)
except KeyError:
return False
return True
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment