whee

Unified interface to key value with locking and transactions.
Log | Files | Refs | README | LICENSE

commit 83dc671e5fecd68498414d6bd90fc01c82eb2726
parent 1504820466cd4b1a9d7c065b950d1ddd132451e6
Author: lash <dev@holbrook.no>
Date:   Thu, 26 Mar 2026 12:55:13 -0600

Add fs store

Diffstat:
Msetup.py | 3++-
Atests/test_fs.py | 38++++++++++++++++++++++++++++++++++++++
Awhee/fs/__init__.py | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 108 insertions(+), 1 deletion(-)

diff --git a/setup.py b/setup.py @@ -6,7 +6,7 @@ f.close() setup( name='wheepy', - version='0.0.3', + version='0.0.5', description='Unified interface to key value with locking and transactions.', author='Louis Holbrook', author_email='dev@holbrook.no', @@ -16,6 +16,7 @@ setup( extras_require={ 'couchdb': ['couchdb~=1.2'], 'valkey': ['valkey~=6.1.1'], + 'fs': ['xdg-base-dirs~=6.0.2'], }, packages=[ 'whee', diff --git a/tests/test_fs.py b/tests/test_fs.py @@ -0,0 +1,38 @@ +import logging +import unittest +import tempfile +import uuid + +from whee.fs import FsStore + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + + +class TestFs(unittest.TestCase): + + def setUp(self): + dp = tempfile.mkdtemp() + uu = uuid.uuid4() + self.store = FsStore(base=dp, dbname=str(uu), levels=2) + + + def test_get_put(self): + r = self.store.have(b'foo') + self.assertFalse(r) + self.store.put(b'foo', b'bar') + r = self.store.have(b'foo') + self.assertTrue(r) + r = self.store.get(b'foo') + self.assertEqual(r, b'bar') + r = self.store.get(b'foo'.hex()) + self.assertEqual(r, b'bar') + with self.assertRaises(FileExistsError): + self.store.put(b'foo', b'baz') + self.store.put(b'foo', b'baz', exist_ok=True) + self.store.delete(b'foo') + with self.assertRaises(FileNotFoundError): + self.store.delete(b'foo') + +if __name__ == '__main__': + unittest.main() diff --git a/whee/fs/__init__.py b/whee/fs/__init__.py @@ -0,0 +1,68 @@ +import os +import logging + +from xdg_base_dirs import xdg_data_home + +from whee import Interface + +logg = logging.getLogger('whee.fs') + + +class FsStore(Interface): + """Implements whee.Interface for Fs backed store + """ + + def __init__(self, passphrase=None, base=xdg_data_home, dbname=None, levels=2): + self.base = os.path.join(base, dbname) + os.makedirs(self.base, exist_ok=True) + self.levels = levels + logg.info('create fsstore {}'.format(self.base)) + + + def have(self, k): + fp = self.to_levelpath(k) + return os.path.isfile(fp) + + + def get(self, k): + fp = self.to_levelpath(k) + f = open(fp, 'rb') + v = f.read() + f.close() + logg.debug('fsstore get {} -> {}'.format(k, v)) + return v + + + def put(self, k, v, exist_ok=False): + fp = self.to_levelpath(k) + if not exist_ok: + if os.path.exists(fp): + raise FileExistsError(k.hex()) + dp = os.path.dirname(fp) + os.makedirs(dp, exist_ok=True) + f = open(fp, 'wb') + f.write(v) + f.close() + + + def delete(self, k): + fp = self.to_levelpath(k) + os.unlink(fp) + + + def to_levelpath(self, k): + if isinstance(k, bytes): + k = k.hex() + else: + bytes.fromhex(k) + s = '' + l = len(k) + c = 0 + for i in range(self.levels): + s += k[c:c+2] + '/' + c += 2 + if c == l: + s = s[:-1] + else: + s += k[c:] + return os.path.join(self.base, s)