%global _empty_manifest_terminate_build 0 Name: python-lsm Version: 0.5.4 Release: 1 Summary: Python bindings for SQLite's LSM key/value engine License: Apache Software License URL: https://github.com/mosquito/python-lsm/ Source0: https://mirrors.nju.edu.cn/pypi/web/packages/06/6b/0ffe3ccab39dc87807aa52ddde66c7947324bbf1827604df571a44c5a64e/lsm-0.5.4.tar.gz %description Fast Python bindings for [SQLite's LSM key/value store](http://www.sqlite.org/src4/doc/trunk/www/lsmusr.wiki>). The LSM storage engine was initially written as part of the experimental SQLite4 rewrite (now abandoned). More recently, the LSM source code was moved into the SQLite3 [source tree](https://www.sqlite.org/cgi/src/dir?ci=e148cdad35520e66&name=ext/lsm1) and has seen some improvements and fixes. This project uses the LSM code from the SQLite3 source tree. Features: * Embedded zero-conf database. * Keys support in-order traversal using cursors. * Transactional (including nested transactions). * Single writer/multiple reader MVCC based transactional concurrency model. * On-disk database stored in a single file. * Data is durable in the face of application or power failure. * Thread-safe. * Releases GIL for read and write operations (each connection has own mutex) * Page compression (lz4 or zstd) * Zero dependency static library * Python 3.x. Limitations: The source for Python lsm is [hosted on GitHub](https://github.com/mosquito/python-lsm). If you encounter any bugs in the library, please [open an issue](https://github.com/mosquito/python-lsm/issues/new), including a description of the bug and any related traceback. ## Quick-start Below is a sample interactive console session designed to show some of the basic features and functionality of the ``lsm`` Python library. To begin, instantiate a `LSM` object, specifying a path to a database file. ```python from lsm import LSM db = LSM('test.ldb') assert db.open() ``` More pythonic variant is using context manager: ```python from lsm import LSM with LSM("test.ldb") as db: assert db.info() ``` Not opened database will raise a RuntimeError: ```python import pytest from lsm import LSM db = LSM('test.ldb') with pytest.raises(RuntimeError): db.info() ``` ### Binary/string mode You should select mode for opening the database with ``binary: bool = True`` argument. For example when you want to store strings just pass ``binary=False``: ```python from lsm import LSM with LSM("test_0.ldb", binary=False) as db: # must be str for keys and values db['foo'] = 'bar' assert db['foo'] == "bar" ``` Otherwise, you must pass keys and values ad ``bytes`` (default behaviour): ```python from lsm import LSM with LSM("test.ldb") as db: db[b'foo'] = b'bar' assert db[b'foo'] == b'bar' ``` ### Key/Value Features ``lsm`` is a key/value store, and has a dictionary-like API: ```python from lsm import LSM with LSM("test.ldb", binary=False) as db: db['foo'] = 'bar' assert db['foo'] == 'bar' ``` Database apply changes as soon as possible: ```python import pytest from lsm import LSM with LSM("test.ldb", binary=False) as db: for i in range(4): db[f'k{i}'] = str(i) assert 'k3' in db assert 'k4' not in db del db['k3'] with pytest.raises(KeyError): print(db['k3']) ``` By default, when you attempt to look up a key, ``lsm`` will search for an exact match. You can also search for the closest key, if the specific key you are searching for does not exist: ```python import pytest from lsm import LSM, SEEK_LE, SEEK_GE, SEEK_LEFAST with LSM("test.ldb", binary=False) as db: for i in range(4): db[f'k{i}'] = str(i) # Here we will match "k1". assert db['k1xx', SEEK_LE] == '1' # Here we will match "k1" but do not fetch a value # In this case the value will always be ``True`` or there will # be an exception if the key is not found assert db['k1xx', SEEK_LEFAST] is True with pytest.raises(KeyError): print(db['000', SEEK_LEFAST]) # Here we will match "k2". assert db['k1xx', SEEK_GE] == "2" ``` `LSM` supports other common dictionary methods such as: * `keys()` * `values()` * `items()` * `update()` ### Slices and Iteration The database can be iterated through directly, or sliced. When you are slicing the database the start and end keys need not exist -- ``lsm`` will find the closest key (details can be found in the [LSM.fetch_range()](https://lsm-db.readthedocs.io/en/latest/api.html#lsm.LSM.fetch_range) documentation). ```python from lsm import LSM with LSM("test_slices.ldb", binary=False) as db: # clean database for key in db.keys(): del db[key] db['foo'] = 'bar' for i in range(3): db[f'k{i}'] = str(i) # Can easily iterate over the database items assert ( sorted(item for item in db.items()) == [ ('foo', 'bar'), ('k0', '0'), ('k1', '1'), ('k2', '2') ] ) # However, you will not read the entire database into memory, as special # iterator objects are used. assert str(db['k0':'k99']).startswith(" ```python with LSM("test_slices.ldb", binary=False, readonly=True) as db: assert list(db['k0':]) == [('k0', '0'), ('k1', '1'), ('k2', '2')] assert list(db[:'k1']) == [('foo', 'bar'), ('k0', '0'), ('k1', '1')] assert list(db[:'aaa']) == [] ``` To retrieve keys in reverse order or stepping over more than one item, simply use a third slice argument as usual. Negative step value means reverse order, but first and second arguments must be ordinarily ordered. ```python with LSM("test_slices.ldb", binary=False, readonly=True) as db: assert list(db['k0':'k99':2]) == [('k0', '0'), ('k2', '2')] assert list(db['k0'::-1]) == [('k2', '2'), ('k1', '1'), ('k0', '0')] assert list(db['k0'::-2]) == [('k2', '2'), ('k0', '0')] assert list(db['k0'::3]) == [('k0', '0')] ``` You can also **delete** slices of keys, but note that delete **will not** include the keys themselves: ```python with LSM("test_slices.ldb", binary=False) as db: del db['k0':'k99'] # Note that 'k0' still exists. assert list(db.items()) == [('foo', 'bar'), ('k0', '0')] ``` ### Cursors While slicing may cover most use-cases, for finer-grained control you can use cursors for traversing records. ```python from lsm import LSM, SEEK_GE, SEEK_LE with LSM("test_cursors.ldb", binary=False) as db: del db["a":"z"] db["spam"] = "spam" with db.cursor() as cursor: cursor.seek('spam') key, value = cursor.retrieve() assert key == 'spam' assert value == 'spam' ``` Seeking over cursors: ```python with LSM("test_cursors.ldb", binary=False) as db: db.update({'k0': '0', 'k1': '1', 'k2': '2', 'k3': '3', 'foo': 'bar'}) with db.cursor() as cursor: cursor.first() key, value = cursor.retrieve() assert key == "foo" assert value == "bar" cursor.last() key, value = cursor.retrieve() assert key == "spam" assert value == "spam" cursor.previous() key, value = cursor.retrieve() assert key == "k3" assert value == "3" ``` Finding the first match that is greater than or equal to `'k0'` and move forward until the key is less than `'k99'` ```python with LSM("test_cursors.ldb", binary=False) as db: with db.cursor() as cursor: cursor.seek("k0", SEEK_GE) results = [] while cursor.compare("k99") > 0: key, value = cursor.retrieve() results.append((key, value)) cursor.next() assert results == [('k0', '0'), ('k1', '1'), ('k2', '2'), ('k3', '3')] ``` Finding the last match that is lower than or equal to `'k99'` and move backward until the key is less than `'k0'` ```python with LSM("test_cursors.ldb", binary=False) as db: with db.cursor() as cursor: cursor.seek("k99", SEEK_LE) results = [] while cursor.compare("k0") >= 0: key, value = cursor.retrieve() results.append((key, value)) cursor.previous() assert results == [('k3', '3'), ('k2', '2'), ('k1', '1'), ('k0', '0')] ``` It is very important to close a cursor when you are through using it. For this reason, it is recommended you use the `LSM.cursor()` context-manager, which ensures the cursor is closed properly. ### Transactions ``lsm`` supports nested transactions. The simplest way to use transactions is with the `LSM.transaction()` method, which returns a context-manager: ```python from lsm import LSM with LSM("test_tx.ldb", binary=False) as db: del db["a":"z"] for i in range(10): db[f"k{i}"] = f"{i}" with LSM("test_tx.ldb", binary=False) as db: with db.transaction() as tx1: db['k1'] = '1-mod' with db.transaction() as tx2: db['k2'] = '2-mod' tx2.rollback() assert db['k1'] == '1-mod' assert db['k2'] == '2' ``` You can commit or roll-back transactions part-way through a wrapped block: ```python from lsm import LSM with LSM("test_tx_2.ldb", binary=False) as db: del db["a":"z"] for i in range(10): db[f"k{i}"] = f"{i}" with LSM("test_tx_2.ldb", binary=False) as db: with db.transaction() as txn: db['k1'] = 'outer txn' # The write operation is preserved. txn.commit() db['k1'] = 'outer txn-2' with db.transaction() as txn2: # This is committed after the block ends. db['k1'] = 'inner-txn' assert db['k1'] == "inner-txn" # Rolls back both the changes from txn2 and the preceding write. txn.rollback() assert db['k1'] == 'outer txn', db['k1'] ``` If you like, you can also explicitly call `LSM.begin()`, `LSM.commit()`, and `LSM.rollback()`. ```python from lsm import LSM # fill db with LSM("test_db_tx.ldb", binary=False) as db: del db["k":"z"] for i in range(10): db[f"k{i}"] = f"{i}" with LSM("test_db_tx.ldb", binary=False) as db: # start transaction db.begin() db['k1'] = '1-mod' # nested transaction db.begin() db['k2'] = '2-mod' # rolling back nested transaction db.rollback() # comitting top-level transaction db.commit() assert db['k1'] == '1-mod' assert db['k2'] == '2' ``` ### Thanks to * [@coleifer](https://github.com/coleifer) - this project was inspired by [coleifer/python-lsm-db](https://github.com/coleifer/python-lsm-db). %package -n python3-lsm Summary: Python bindings for SQLite's LSM key/value engine Provides: python-lsm BuildRequires: python3-devel BuildRequires: python3-setuptools BuildRequires: python3-pip BuildRequires: python3-cffi BuildRequires: gcc BuildRequires: gdb %description -n python3-lsm Fast Python bindings for [SQLite's LSM key/value store](http://www.sqlite.org/src4/doc/trunk/www/lsmusr.wiki>). The LSM storage engine was initially written as part of the experimental SQLite4 rewrite (now abandoned). More recently, the LSM source code was moved into the SQLite3 [source tree](https://www.sqlite.org/cgi/src/dir?ci=e148cdad35520e66&name=ext/lsm1) and has seen some improvements and fixes. This project uses the LSM code from the SQLite3 source tree. Features: * Embedded zero-conf database. * Keys support in-order traversal using cursors. * Transactional (including nested transactions). * Single writer/multiple reader MVCC based transactional concurrency model. * On-disk database stored in a single file. * Data is durable in the face of application or power failure. * Thread-safe. * Releases GIL for read and write operations (each connection has own mutex) * Page compression (lz4 or zstd) * Zero dependency static library * Python 3.x. Limitations: The source for Python lsm is [hosted on GitHub](https://github.com/mosquito/python-lsm). If you encounter any bugs in the library, please [open an issue](https://github.com/mosquito/python-lsm/issues/new), including a description of the bug and any related traceback. ## Quick-start Below is a sample interactive console session designed to show some of the basic features and functionality of the ``lsm`` Python library. To begin, instantiate a `LSM` object, specifying a path to a database file. ```python from lsm import LSM db = LSM('test.ldb') assert db.open() ``` More pythonic variant is using context manager: ```python from lsm import LSM with LSM("test.ldb") as db: assert db.info() ``` Not opened database will raise a RuntimeError: ```python import pytest from lsm import LSM db = LSM('test.ldb') with pytest.raises(RuntimeError): db.info() ``` ### Binary/string mode You should select mode for opening the database with ``binary: bool = True`` argument. For example when you want to store strings just pass ``binary=False``: ```python from lsm import LSM with LSM("test_0.ldb", binary=False) as db: # must be str for keys and values db['foo'] = 'bar' assert db['foo'] == "bar" ``` Otherwise, you must pass keys and values ad ``bytes`` (default behaviour): ```python from lsm import LSM with LSM("test.ldb") as db: db[b'foo'] = b'bar' assert db[b'foo'] == b'bar' ``` ### Key/Value Features ``lsm`` is a key/value store, and has a dictionary-like API: ```python from lsm import LSM with LSM("test.ldb", binary=False) as db: db['foo'] = 'bar' assert db['foo'] == 'bar' ``` Database apply changes as soon as possible: ```python import pytest from lsm import LSM with LSM("test.ldb", binary=False) as db: for i in range(4): db[f'k{i}'] = str(i) assert 'k3' in db assert 'k4' not in db del db['k3'] with pytest.raises(KeyError): print(db['k3']) ``` By default, when you attempt to look up a key, ``lsm`` will search for an exact match. You can also search for the closest key, if the specific key you are searching for does not exist: ```python import pytest from lsm import LSM, SEEK_LE, SEEK_GE, SEEK_LEFAST with LSM("test.ldb", binary=False) as db: for i in range(4): db[f'k{i}'] = str(i) # Here we will match "k1". assert db['k1xx', SEEK_LE] == '1' # Here we will match "k1" but do not fetch a value # In this case the value will always be ``True`` or there will # be an exception if the key is not found assert db['k1xx', SEEK_LEFAST] is True with pytest.raises(KeyError): print(db['000', SEEK_LEFAST]) # Here we will match "k2". assert db['k1xx', SEEK_GE] == "2" ``` `LSM` supports other common dictionary methods such as: * `keys()` * `values()` * `items()` * `update()` ### Slices and Iteration The database can be iterated through directly, or sliced. When you are slicing the database the start and end keys need not exist -- ``lsm`` will find the closest key (details can be found in the [LSM.fetch_range()](https://lsm-db.readthedocs.io/en/latest/api.html#lsm.LSM.fetch_range) documentation). ```python from lsm import LSM with LSM("test_slices.ldb", binary=False) as db: # clean database for key in db.keys(): del db[key] db['foo'] = 'bar' for i in range(3): db[f'k{i}'] = str(i) # Can easily iterate over the database items assert ( sorted(item for item in db.items()) == [ ('foo', 'bar'), ('k0', '0'), ('k1', '1'), ('k2', '2') ] ) # However, you will not read the entire database into memory, as special # iterator objects are used. assert str(db['k0':'k99']).startswith(" ```python with LSM("test_slices.ldb", binary=False, readonly=True) as db: assert list(db['k0':]) == [('k0', '0'), ('k1', '1'), ('k2', '2')] assert list(db[:'k1']) == [('foo', 'bar'), ('k0', '0'), ('k1', '1')] assert list(db[:'aaa']) == [] ``` To retrieve keys in reverse order or stepping over more than one item, simply use a third slice argument as usual. Negative step value means reverse order, but first and second arguments must be ordinarily ordered. ```python with LSM("test_slices.ldb", binary=False, readonly=True) as db: assert list(db['k0':'k99':2]) == [('k0', '0'), ('k2', '2')] assert list(db['k0'::-1]) == [('k2', '2'), ('k1', '1'), ('k0', '0')] assert list(db['k0'::-2]) == [('k2', '2'), ('k0', '0')] assert list(db['k0'::3]) == [('k0', '0')] ``` You can also **delete** slices of keys, but note that delete **will not** include the keys themselves: ```python with LSM("test_slices.ldb", binary=False) as db: del db['k0':'k99'] # Note that 'k0' still exists. assert list(db.items()) == [('foo', 'bar'), ('k0', '0')] ``` ### Cursors While slicing may cover most use-cases, for finer-grained control you can use cursors for traversing records. ```python from lsm import LSM, SEEK_GE, SEEK_LE with LSM("test_cursors.ldb", binary=False) as db: del db["a":"z"] db["spam"] = "spam" with db.cursor() as cursor: cursor.seek('spam') key, value = cursor.retrieve() assert key == 'spam' assert value == 'spam' ``` Seeking over cursors: ```python with LSM("test_cursors.ldb", binary=False) as db: db.update({'k0': '0', 'k1': '1', 'k2': '2', 'k3': '3', 'foo': 'bar'}) with db.cursor() as cursor: cursor.first() key, value = cursor.retrieve() assert key == "foo" assert value == "bar" cursor.last() key, value = cursor.retrieve() assert key == "spam" assert value == "spam" cursor.previous() key, value = cursor.retrieve() assert key == "k3" assert value == "3" ``` Finding the first match that is greater than or equal to `'k0'` and move forward until the key is less than `'k99'` ```python with LSM("test_cursors.ldb", binary=False) as db: with db.cursor() as cursor: cursor.seek("k0", SEEK_GE) results = [] while cursor.compare("k99") > 0: key, value = cursor.retrieve() results.append((key, value)) cursor.next() assert results == [('k0', '0'), ('k1', '1'), ('k2', '2'), ('k3', '3')] ``` Finding the last match that is lower than or equal to `'k99'` and move backward until the key is less than `'k0'` ```python with LSM("test_cursors.ldb", binary=False) as db: with db.cursor() as cursor: cursor.seek("k99", SEEK_LE) results = [] while cursor.compare("k0") >= 0: key, value = cursor.retrieve() results.append((key, value)) cursor.previous() assert results == [('k3', '3'), ('k2', '2'), ('k1', '1'), ('k0', '0')] ``` It is very important to close a cursor when you are through using it. For this reason, it is recommended you use the `LSM.cursor()` context-manager, which ensures the cursor is closed properly. ### Transactions ``lsm`` supports nested transactions. The simplest way to use transactions is with the `LSM.transaction()` method, which returns a context-manager: ```python from lsm import LSM with LSM("test_tx.ldb", binary=False) as db: del db["a":"z"] for i in range(10): db[f"k{i}"] = f"{i}" with LSM("test_tx.ldb", binary=False) as db: with db.transaction() as tx1: db['k1'] = '1-mod' with db.transaction() as tx2: db['k2'] = '2-mod' tx2.rollback() assert db['k1'] == '1-mod' assert db['k2'] == '2' ``` You can commit or roll-back transactions part-way through a wrapped block: ```python from lsm import LSM with LSM("test_tx_2.ldb", binary=False) as db: del db["a":"z"] for i in range(10): db[f"k{i}"] = f"{i}" with LSM("test_tx_2.ldb", binary=False) as db: with db.transaction() as txn: db['k1'] = 'outer txn' # The write operation is preserved. txn.commit() db['k1'] = 'outer txn-2' with db.transaction() as txn2: # This is committed after the block ends. db['k1'] = 'inner-txn' assert db['k1'] == "inner-txn" # Rolls back both the changes from txn2 and the preceding write. txn.rollback() assert db['k1'] == 'outer txn', db['k1'] ``` If you like, you can also explicitly call `LSM.begin()`, `LSM.commit()`, and `LSM.rollback()`. ```python from lsm import LSM # fill db with LSM("test_db_tx.ldb", binary=False) as db: del db["k":"z"] for i in range(10): db[f"k{i}"] = f"{i}" with LSM("test_db_tx.ldb", binary=False) as db: # start transaction db.begin() db['k1'] = '1-mod' # nested transaction db.begin() db['k2'] = '2-mod' # rolling back nested transaction db.rollback() # comitting top-level transaction db.commit() assert db['k1'] == '1-mod' assert db['k2'] == '2' ``` ### Thanks to * [@coleifer](https://github.com/coleifer) - this project was inspired by [coleifer/python-lsm-db](https://github.com/coleifer/python-lsm-db). %package help Summary: Development documents and examples for lsm Provides: python3-lsm-doc %description help Fast Python bindings for [SQLite's LSM key/value store](http://www.sqlite.org/src4/doc/trunk/www/lsmusr.wiki>). The LSM storage engine was initially written as part of the experimental SQLite4 rewrite (now abandoned). More recently, the LSM source code was moved into the SQLite3 [source tree](https://www.sqlite.org/cgi/src/dir?ci=e148cdad35520e66&name=ext/lsm1) and has seen some improvements and fixes. This project uses the LSM code from the SQLite3 source tree. Features: * Embedded zero-conf database. * Keys support in-order traversal using cursors. * Transactional (including nested transactions). * Single writer/multiple reader MVCC based transactional concurrency model. * On-disk database stored in a single file. * Data is durable in the face of application or power failure. * Thread-safe. * Releases GIL for read and write operations (each connection has own mutex) * Page compression (lz4 or zstd) * Zero dependency static library * Python 3.x. Limitations: The source for Python lsm is [hosted on GitHub](https://github.com/mosquito/python-lsm). If you encounter any bugs in the library, please [open an issue](https://github.com/mosquito/python-lsm/issues/new), including a description of the bug and any related traceback. ## Quick-start Below is a sample interactive console session designed to show some of the basic features and functionality of the ``lsm`` Python library. To begin, instantiate a `LSM` object, specifying a path to a database file. ```python from lsm import LSM db = LSM('test.ldb') assert db.open() ``` More pythonic variant is using context manager: ```python from lsm import LSM with LSM("test.ldb") as db: assert db.info() ``` Not opened database will raise a RuntimeError: ```python import pytest from lsm import LSM db = LSM('test.ldb') with pytest.raises(RuntimeError): db.info() ``` ### Binary/string mode You should select mode for opening the database with ``binary: bool = True`` argument. For example when you want to store strings just pass ``binary=False``: ```python from lsm import LSM with LSM("test_0.ldb", binary=False) as db: # must be str for keys and values db['foo'] = 'bar' assert db['foo'] == "bar" ``` Otherwise, you must pass keys and values ad ``bytes`` (default behaviour): ```python from lsm import LSM with LSM("test.ldb") as db: db[b'foo'] = b'bar' assert db[b'foo'] == b'bar' ``` ### Key/Value Features ``lsm`` is a key/value store, and has a dictionary-like API: ```python from lsm import LSM with LSM("test.ldb", binary=False) as db: db['foo'] = 'bar' assert db['foo'] == 'bar' ``` Database apply changes as soon as possible: ```python import pytest from lsm import LSM with LSM("test.ldb", binary=False) as db: for i in range(4): db[f'k{i}'] = str(i) assert 'k3' in db assert 'k4' not in db del db['k3'] with pytest.raises(KeyError): print(db['k3']) ``` By default, when you attempt to look up a key, ``lsm`` will search for an exact match. You can also search for the closest key, if the specific key you are searching for does not exist: ```python import pytest from lsm import LSM, SEEK_LE, SEEK_GE, SEEK_LEFAST with LSM("test.ldb", binary=False) as db: for i in range(4): db[f'k{i}'] = str(i) # Here we will match "k1". assert db['k1xx', SEEK_LE] == '1' # Here we will match "k1" but do not fetch a value # In this case the value will always be ``True`` or there will # be an exception if the key is not found assert db['k1xx', SEEK_LEFAST] is True with pytest.raises(KeyError): print(db['000', SEEK_LEFAST]) # Here we will match "k2". assert db['k1xx', SEEK_GE] == "2" ``` `LSM` supports other common dictionary methods such as: * `keys()` * `values()` * `items()` * `update()` ### Slices and Iteration The database can be iterated through directly, or sliced. When you are slicing the database the start and end keys need not exist -- ``lsm`` will find the closest key (details can be found in the [LSM.fetch_range()](https://lsm-db.readthedocs.io/en/latest/api.html#lsm.LSM.fetch_range) documentation). ```python from lsm import LSM with LSM("test_slices.ldb", binary=False) as db: # clean database for key in db.keys(): del db[key] db['foo'] = 'bar' for i in range(3): db[f'k{i}'] = str(i) # Can easily iterate over the database items assert ( sorted(item for item in db.items()) == [ ('foo', 'bar'), ('k0', '0'), ('k1', '1'), ('k2', '2') ] ) # However, you will not read the entire database into memory, as special # iterator objects are used. assert str(db['k0':'k99']).startswith(" ```python with LSM("test_slices.ldb", binary=False, readonly=True) as db: assert list(db['k0':]) == [('k0', '0'), ('k1', '1'), ('k2', '2')] assert list(db[:'k1']) == [('foo', 'bar'), ('k0', '0'), ('k1', '1')] assert list(db[:'aaa']) == [] ``` To retrieve keys in reverse order or stepping over more than one item, simply use a third slice argument as usual. Negative step value means reverse order, but first and second arguments must be ordinarily ordered. ```python with LSM("test_slices.ldb", binary=False, readonly=True) as db: assert list(db['k0':'k99':2]) == [('k0', '0'), ('k2', '2')] assert list(db['k0'::-1]) == [('k2', '2'), ('k1', '1'), ('k0', '0')] assert list(db['k0'::-2]) == [('k2', '2'), ('k0', '0')] assert list(db['k0'::3]) == [('k0', '0')] ``` You can also **delete** slices of keys, but note that delete **will not** include the keys themselves: ```python with LSM("test_slices.ldb", binary=False) as db: del db['k0':'k99'] # Note that 'k0' still exists. assert list(db.items()) == [('foo', 'bar'), ('k0', '0')] ``` ### Cursors While slicing may cover most use-cases, for finer-grained control you can use cursors for traversing records. ```python from lsm import LSM, SEEK_GE, SEEK_LE with LSM("test_cursors.ldb", binary=False) as db: del db["a":"z"] db["spam"] = "spam" with db.cursor() as cursor: cursor.seek('spam') key, value = cursor.retrieve() assert key == 'spam' assert value == 'spam' ``` Seeking over cursors: ```python with LSM("test_cursors.ldb", binary=False) as db: db.update({'k0': '0', 'k1': '1', 'k2': '2', 'k3': '3', 'foo': 'bar'}) with db.cursor() as cursor: cursor.first() key, value = cursor.retrieve() assert key == "foo" assert value == "bar" cursor.last() key, value = cursor.retrieve() assert key == "spam" assert value == "spam" cursor.previous() key, value = cursor.retrieve() assert key == "k3" assert value == "3" ``` Finding the first match that is greater than or equal to `'k0'` and move forward until the key is less than `'k99'` ```python with LSM("test_cursors.ldb", binary=False) as db: with db.cursor() as cursor: cursor.seek("k0", SEEK_GE) results = [] while cursor.compare("k99") > 0: key, value = cursor.retrieve() results.append((key, value)) cursor.next() assert results == [('k0', '0'), ('k1', '1'), ('k2', '2'), ('k3', '3')] ``` Finding the last match that is lower than or equal to `'k99'` and move backward until the key is less than `'k0'` ```python with LSM("test_cursors.ldb", binary=False) as db: with db.cursor() as cursor: cursor.seek("k99", SEEK_LE) results = [] while cursor.compare("k0") >= 0: key, value = cursor.retrieve() results.append((key, value)) cursor.previous() assert results == [('k3', '3'), ('k2', '2'), ('k1', '1'), ('k0', '0')] ``` It is very important to close a cursor when you are through using it. For this reason, it is recommended you use the `LSM.cursor()` context-manager, which ensures the cursor is closed properly. ### Transactions ``lsm`` supports nested transactions. The simplest way to use transactions is with the `LSM.transaction()` method, which returns a context-manager: ```python from lsm import LSM with LSM("test_tx.ldb", binary=False) as db: del db["a":"z"] for i in range(10): db[f"k{i}"] = f"{i}" with LSM("test_tx.ldb", binary=False) as db: with db.transaction() as tx1: db['k1'] = '1-mod' with db.transaction() as tx2: db['k2'] = '2-mod' tx2.rollback() assert db['k1'] == '1-mod' assert db['k2'] == '2' ``` You can commit or roll-back transactions part-way through a wrapped block: ```python from lsm import LSM with LSM("test_tx_2.ldb", binary=False) as db: del db["a":"z"] for i in range(10): db[f"k{i}"] = f"{i}" with LSM("test_tx_2.ldb", binary=False) as db: with db.transaction() as txn: db['k1'] = 'outer txn' # The write operation is preserved. txn.commit() db['k1'] = 'outer txn-2' with db.transaction() as txn2: # This is committed after the block ends. db['k1'] = 'inner-txn' assert db['k1'] == "inner-txn" # Rolls back both the changes from txn2 and the preceding write. txn.rollback() assert db['k1'] == 'outer txn', db['k1'] ``` If you like, you can also explicitly call `LSM.begin()`, `LSM.commit()`, and `LSM.rollback()`. ```python from lsm import LSM # fill db with LSM("test_db_tx.ldb", binary=False) as db: del db["k":"z"] for i in range(10): db[f"k{i}"] = f"{i}" with LSM("test_db_tx.ldb", binary=False) as db: # start transaction db.begin() db['k1'] = '1-mod' # nested transaction db.begin() db['k2'] = '2-mod' # rolling back nested transaction db.rollback() # comitting top-level transaction db.commit() assert db['k1'] == '1-mod' assert db['k2'] == '2' ``` ### Thanks to * [@coleifer](https://github.com/coleifer) - this project was inspired by [coleifer/python-lsm-db](https://github.com/coleifer/python-lsm-db). %prep %autosetup -n lsm-0.5.4 %build %py3_build %install %py3_install install -d -m755 %{buildroot}/%{_pkgdocdir} if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi pushd %{buildroot} if [ -d usr/lib ]; then find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst fi if [ -d usr/lib64 ]; then find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst fi if [ -d usr/bin ]; then find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst fi if [ -d usr/sbin ]; then find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst fi touch doclist.lst if [ -d usr/share/man ]; then find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst fi popd mv %{buildroot}/filelist.lst . mv %{buildroot}/doclist.lst . %files -n python3-lsm -f filelist.lst %dir %{python3_sitearch}/* %files help -f doclist.lst %{_docdir}/* %changelog * Tue Apr 11 2023 Python_Bot - 0.5.4-1 - Package Spec generated