whee

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

commit 8c12346c5991f6cfacac33114506916efbf01ab1
parent 03061bbc706d5bc7c94660d444f108dec0931e58
Author: lash <dev@holbrook.no>
Date:   Sun, 24 May 2026 16:31:59 -0600

Add partial match iterator to fs

Diffstat:
Msetup.py | 2+-
Mtests/test_fs.py | 30++++++++++++++++++++++++++++--
Mwhee/base.py | 8++++++++
Mwhee/fs/__init__.py | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
4 files changed, 117 insertions(+), 5 deletions(-)

diff --git a/setup.py b/setup.py @@ -6,7 +6,7 @@ f.close() setup( name='wheepy', - version='0.0.5a2', + version='0.0.6a1', description='Unified interface to key value with locking and transactions.', author='Louis Holbrook', author_email='dev@holbrook.no', diff --git a/tests/test_fs.py b/tests/test_fs.py @@ -22,7 +22,7 @@ class TestFs(unittest.TestCase): shutil.rmtree(self.base) - def test_get_put(self): + def test_fs_get_put(self): r = self.store.have(b'foo') self.assertFalse(r) self.store.put(b'foo', b'bar') @@ -40,11 +40,37 @@ class TestFs(unittest.TestCase): self.store.delete(b'foo') - def test_short(self): + def test_fs_short(self): self.store.put(b'f', b'oo') self.store.put(b'fo', b'o') self.store.put(b'foo', b'') + def test_fs_match(self): + self.store.put(b'afoo', b'inky') + self.store.put(b'abar', b'pinky') + self.store.put(b'abaz', b'blinky') + self.store.put(b'barbarbar', b'blinky') + self.store.match(b'a') + r = list(self.store) + self.assertEqual(len(r), 3) + + self.store.match(b'ab') + r = list(self.store) + self.assertEqual(len(r), 2) + + + def test_fs_all(self): + self.store.put(b'afoo', b'inky') + self.store.put(b'abar', b'pinky') + self.store.put(b'abaz', b'blinky') + self.store.put(b'barbarbar', b'blinky') + r = list(self.store) + self.assertEqual(len(r), 4) + + r = list(self.store.iter()) + self.assertEqual(len(r), 4) + + if __name__ == '__main__': unittest.main() diff --git a/whee/base.py b/whee/base.py @@ -37,6 +37,14 @@ class Interface: raise NotImplementedError() + def match(self, k): + """Returns an iterator matching all stored keys that start with the given key. + + :raises: IOError if query fails for any reason. + """ + raise NotImplementedError() + + def start(self): """Start a store transaction. diff --git a/whee/fs/__init__.py b/whee/fs/__init__.py @@ -17,6 +17,9 @@ class FsStore(Interface): os.makedirs(self.base, exist_ok=True) self.levels = levels logg.info('create fsstore {}'.format(self.base)) + self.k = None + self.i = -1 + self.l = 0 def have(self, k): @@ -46,20 +49,90 @@ class FsStore(Interface): logg.debug('fs put {} under {}'.format(k, fp)) + def match(self, k): + if self.l == 0: + self.k = [] + self.i = 0 + if isinstance(k, bytes): + k = k.hex() + + if self.l == 0: + v = self.to_levelsub(k) + if v[0] == '_': + dp = os.path.join(self.base, '_') + d = [] + try: + d = os.listdir(dp) + except FileNotFoundError: + pass + for v in d: + if v[0] == '.': + continue + if len(v) < len(k): + continue + if v[len(k)] == k: + self.k.append(k) + + self.l += 1 + dp = self.to_levelpath(k, trunc_short=False) + d = os.listdir(dp) + for v in d: + if v[0] == '.': + continue + if v[0] == '_': + continue + fp = os.path.join(dp, v) + part = k + v + if os.path.isdir(fp): + self.match(part) + continue + self.k.append(part) + + + def __iter__(self): + if self.k != None: + return self + self.match(b'') + return self + + + def iter(self): + self.k = None + self.i = -1 + self.l = 0 + return self.__iter__() + + + def __next__(self): + v = None + try: + v = self.k[self.i] + except IndexError: + raise StopIteration() + self.i += 1 + return v + + def delete(self, k): fp = self.to_levelpath(k) os.unlink(fp) - def to_levelpath(self, k): + def to_levelsub(self, k, trunc_short=True): s = '' + if len(k) == 0: + if trunc_short: + return self.base + '_' + else: + return self.base + if isinstance(k, bytes): k = k.hex() else: bytes.fromhex(k) l = len(k) - if l <= self.levels * 2: + if l <= self.levels * 2 and trunc_short: #levels = len(k) - 1 s = '_/' + k else: @@ -71,4 +144,9 @@ class FsStore(Interface): s = s[:-1] else: s += k[c:] + return s + + + def to_levelpath(self, k, trunc_short=True): + s = self.to_levelsub(k, trunc_short=trunc_short) return os.path.join(self.base, s)