Skip to main content

Python bindings for SQLite's LSM key/value engine

Project description

lsm

Fast Python bindings for SQLite's LSM key/value store. 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 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.

If you encounter any bugs in the library, please open an issue, 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.

from lsm import LSM
db = LSM('test.ldb')
assert db.open()

More pythonic variant is using context manager:

from lsm import LSM
with LSM("test.ldb") as db:
    assert db.info()

Not opened database will raise a RuntimeError:

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:

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):

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:

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:

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:

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() documentation).

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("<lsm_slice object at")

    # But you can cast it to the list for example
    assert list(db['k0':'k99']) == [('k0', '0'), ('k1', '1'), ('k2', '2')]

You can use open-ended slices. If the lower- or upper-bound is outside the range of keys an empty list is returned.

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.

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:

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.

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:

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'

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'

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:

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:

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().

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lsm-0.5.9.tar.gz (896.1 kB view details)

Uploaded Source

Built Distributions

lsm-0.5.9-cp313-cp313-win_amd64.whl (249.6 kB view details)

Uploaded CPython 3.13 Windows x86-64

lsm-0.5.9-cp313-cp313-macosx_10_13_universal2.whl (1.2 MB view details)

Uploaded CPython 3.13 macOS 10.13+ universal2 (ARM64, x86-64)

lsm-0.5.9-cp312-cp312-win_amd64.whl (249.6 kB view details)

Uploaded CPython 3.12 Windows x86-64

lsm-0.5.9-cp312-cp312-macosx_10_13_universal2.whl (1.2 MB view details)

Uploaded CPython 3.12 macOS 10.13+ universal2 (ARM64, x86-64)

lsm-0.5.9-cp311-cp311-win_amd64.whl (249.3 kB view details)

Uploaded CPython 3.11 Windows x86-64

lsm-0.5.9-cp311-cp311-macosx_10_9_universal2.whl (1.2 MB view details)

Uploaded CPython 3.11 macOS 10.9+ universal2 (ARM64, x86-64)

lsm-0.5.9-cp310-cp310-win_amd64.whl (249.1 kB view details)

Uploaded CPython 3.10 Windows x86-64

lsm-0.5.9-cp310-cp310-macosx_10_9_universal2.whl (1.2 MB view details)

Uploaded CPython 3.10 macOS 10.9+ universal2 (ARM64, x86-64)

lsm-0.5.9-cp39-cp39-win_amd64.whl (339.9 kB view details)

Uploaded CPython 3.9 Windows x86-64

lsm-0.5.9-cp39-cp39-macosx_10_9_universal2.whl (1.2 MB view details)

Uploaded CPython 3.9 macOS 10.9+ universal2 (ARM64, x86-64)

File details

Details for the file lsm-0.5.9.tar.gz.

File metadata

  • Download URL: lsm-0.5.9.tar.gz
  • Upload date:
  • Size: 896.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for lsm-0.5.9.tar.gz
Algorithm Hash digest
SHA256 a74a28cdabc5d5cc4d3e8d1312d8ffd8ae96f33f84e09d2ac923dd084b777a7e
MD5 093d69a9f33fedf0837eeac881c516ed
BLAKE2b-256 c19f5517ae54e98f9538f53b687239c063527495355adde82b8e0f5ed8c7af9e

See more details on using hashes here.

File details

Details for the file lsm-0.5.9-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: lsm-0.5.9-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 249.6 kB
  • Tags: CPython 3.13, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.2

File hashes

Hashes for lsm-0.5.9-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 cf8e2910359acbe1dd0287fca952ecd87fdbb4406925ba156189e8a269c99d35
MD5 51d3d3896eaaa1448b1760f819d35491
BLAKE2b-256 defb566c6874ec2173f290cca277635c32fc860736388b37d9f7616dbccb4bdf

See more details on using hashes here.

File details

Details for the file lsm-0.5.9-cp313-cp313-macosx_10_13_universal2.whl.

File metadata

File hashes

Hashes for lsm-0.5.9-cp313-cp313-macosx_10_13_universal2.whl
Algorithm Hash digest
SHA256 731af7b9932721c9c86698e0a265208ee5e18cc7ab0f4e5c1c612ff8d2115ebc
MD5 b8ddfbad19f19cf010e1768145bae5cc
BLAKE2b-256 b14e1e32b62b9496b8e6d83b6e2364aa403a8fd44a02a87d24dcef6a50a39c2d

See more details on using hashes here.

File details

Details for the file lsm-0.5.9-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: lsm-0.5.9-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 249.6 kB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for lsm-0.5.9-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 7774dc91395678fce0d459e7e47f3ffe6e4f59f02ae874a2b7b0e2e6f044ef33
MD5 aa395407e95c4d53e6e193b67b37141b
BLAKE2b-256 c78b235fc0e15603dff14d5f8d8d10f4c461e082844f68546f6a38ccfad3ecba

See more details on using hashes here.

File details

Details for the file lsm-0.5.9-cp312-cp312-macosx_10_13_universal2.whl.

File metadata

File hashes

Hashes for lsm-0.5.9-cp312-cp312-macosx_10_13_universal2.whl
Algorithm Hash digest
SHA256 9aac1bc2f8392c729351091bb464677be662fccd72f00785db8116e38a9cbb92
MD5 9201ad44d45d6a5064b0af4974e8e8ff
BLAKE2b-256 a138a74bcb0f16aad36a95cdd3c480851056942dbf7b742062513ff507e4783e

See more details on using hashes here.

File details

Details for the file lsm-0.5.9-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: lsm-0.5.9-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 249.3 kB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.9

File hashes

Hashes for lsm-0.5.9-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 e76add30c8c56e92d7134a8f3dc15f1217498d2797ff1d45178efffe26b977aa
MD5 b6ff79113dfbc81f241955060e05c652
BLAKE2b-256 a780136c0b0cfe9d3b191ab36c6eb326436da9c69ee9774c2ebebf87a1966724

See more details on using hashes here.

File details

Details for the file lsm-0.5.9-cp311-cp311-macosx_10_9_universal2.whl.

File metadata

File hashes

Hashes for lsm-0.5.9-cp311-cp311-macosx_10_9_universal2.whl
Algorithm Hash digest
SHA256 f789b82f02e8f611324438275996996a4dbe76517956036594539e7c1863b87f
MD5 75897562f69d2f2a68269206f046b8b2
BLAKE2b-256 e64e2ebc9e738a05da757e4428ab153525d9d670e8e0676847d9239a906278d7

See more details on using hashes here.

File details

Details for the file lsm-0.5.9-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: lsm-0.5.9-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 249.1 kB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.10.11

File hashes

Hashes for lsm-0.5.9-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 b707b5a5e7498c572fe943457f256096d04d4c01eb5e855d65c665ad32c83992
MD5 b354645ebf3ce00e8bbd988c5c145dce
BLAKE2b-256 0e2cf41fac28f6277fad3f7e127d66f917085231cb757c145eddcb50df9c2730

See more details on using hashes here.

File details

Details for the file lsm-0.5.9-cp310-cp310-macosx_10_9_universal2.whl.

File metadata

File hashes

Hashes for lsm-0.5.9-cp310-cp310-macosx_10_9_universal2.whl
Algorithm Hash digest
SHA256 f206540cac3553746d6fb16083ac14037ca20019e623c559f7b0fc476988cc42
MD5 07d819bcf70480b1a7ff065314ae00af
BLAKE2b-256 3ec7249a27a5b669ae18c6c4a0a893e6f0028e59b3148b91319d7f65c6dd9aeb

See more details on using hashes here.

File details

Details for the file lsm-0.5.9-cp39-cp39-win_amd64.whl.

File metadata

  • Download URL: lsm-0.5.9-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 339.9 kB
  • Tags: CPython 3.9, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.9.13

File hashes

Hashes for lsm-0.5.9-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 1ff6bce6393c6e49d7500a7e083ac26f4461b457f5d4243ef394f3292b01eaa2
MD5 d16de85591a8a9e7ed906ade1ab4528c
BLAKE2b-256 66907109bb37e75fdcf7d9bb49d1d06a2b2521e8bd0fb5392a303ed4bfcf3b4f

See more details on using hashes here.

File details

Details for the file lsm-0.5.9-cp39-cp39-macosx_10_9_universal2.whl.

File metadata

File hashes

Hashes for lsm-0.5.9-cp39-cp39-macosx_10_9_universal2.whl
Algorithm Hash digest
SHA256 3071e4e100e0838242411625940c57d00181f0486d1e2ee63c01cf7236d6a124
MD5 e744bacbe8a6f61ea733fbb5a71d0bab
BLAKE2b-256 5ee19e5d0e66c6cac79c9e74cb6d312c56b8a143a0fd6cbe7ba4c7003de4ad4d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page