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.10.tar.gz (896.2 kB view details)

Uploaded Source

Built Distributions

lsm-0.5.10-cp313-cp313-win_amd64.whl (249.7 kB view details)

Uploaded CPython 3.13 Windows x86-64

lsm-0.5.10-cp313-cp313-manylinux_2_34_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.13 manylinux: glibc 2.34+ x86-64

lsm-0.5.10-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.10-cp312-cp312-win_amd64.whl (249.6 kB view details)

Uploaded CPython 3.12 Windows x86-64

lsm-0.5.10-cp312-cp312-manylinux_2_34_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.34+ x86-64

lsm-0.5.10-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.10-cp311-cp311-win_amd64.whl (249.3 kB view details)

Uploaded CPython 3.11 Windows x86-64

lsm-0.5.10-cp311-cp311-manylinux_2_34_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.34+ x86-64

lsm-0.5.10-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.10-cp310-cp310-win_amd64.whl (249.1 kB view details)

Uploaded CPython 3.10 Windows x86-64

lsm-0.5.10-cp310-cp310-manylinux_2_34_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.34+ x86-64

lsm-0.5.10-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.10-cp39-cp39-win_amd64.whl (339.9 kB view details)

Uploaded CPython 3.9 Windows x86-64

lsm-0.5.10-cp39-cp39-manylinux_2_34_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.9 manylinux: glibc 2.34+ x86-64

lsm-0.5.10-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.10.tar.gz.

File metadata

  • Download URL: lsm-0.5.10.tar.gz
  • Upload date:
  • Size: 896.2 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.10.tar.gz
Algorithm Hash digest
SHA256 809f75d1a1e96c6e980ad51836e61f72c5221a53ff58e915a266c6ffe7ce20d9
MD5 b2cbe77072443e29cd31dc7fdfe50359
BLAKE2b-256 af05029d5a563e734878fed1d081801155ba4ec458c538eec1dfe1acaf894c2f

See more details on using hashes here.

File details

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

File metadata

  • Download URL: lsm-0.5.10-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 249.7 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.10-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 116b66034ac0650d0e965356bc2e88ac69a753187b0c2593f33665ccd3b99bf2
MD5 9ca8f0b9f9c0457c2cdee1560d4fcdfb
BLAKE2b-256 acbff78d11e05ea8855d95fa200de6f378050f204058533b56480ad43c89e332

See more details on using hashes here.

File details

Details for the file lsm-0.5.10-cp313-cp313-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for lsm-0.5.10-cp313-cp313-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 32033522de2b72f33d7badefa369e430f2987c9c6f7aee5a8430f44c61e7e43e
MD5 c12775d3c8cbe2231fc6237d420114a3
BLAKE2b-256 8049158cef2c5c10d13a3cd20862554e11f0e430b278a980ee231fe1e828e4da

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for lsm-0.5.10-cp313-cp313-macosx_10_13_universal2.whl
Algorithm Hash digest
SHA256 194dba0ccfe02e6dcb7b8abc50a2f559ac2ad469af8f38ae81a56f3abba72364
MD5 8cab9496a937ce19d13c2cb838d2f9af
BLAKE2b-256 d33a774c2bd145ff10bd0a1bde4879fbfd7346260c5e8deb0615d1b28535d0dd

See more details on using hashes here.

File details

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

File metadata

  • Download URL: lsm-0.5.10-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.10-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 f5476e680357683393d56bb128ed46cf0c7ca1e6435cc1771435eda8010f65fb
MD5 1079938fb6067ce772b00b9b3b8ce0ac
BLAKE2b-256 55958c1e7f2143ea419c9729bba30041214a14e954bec678a6237a59006ece00

See more details on using hashes here.

File details

Details for the file lsm-0.5.10-cp312-cp312-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for lsm-0.5.10-cp312-cp312-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 1f7c8db72ffd2bdc930785a0c4897be5766b36d8d9914a5ad88a8919c4abba5c
MD5 5730632a085be536b85af0752f734c2a
BLAKE2b-256 1d52df9925a88b52b541313699c58d5c953e48fec3b50c5e41bf5cbfcbcb353f

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for lsm-0.5.10-cp312-cp312-macosx_10_13_universal2.whl
Algorithm Hash digest
SHA256 6ac95c245b11a1bdcc5f047f9cacdbf3fc4a0141d9592be51eae76b5a4b4aee6
MD5 bdf996811144aeceb6cec5ae080ec7ef
BLAKE2b-256 7cb6f2c2d4fff96d26a1df5ca1c677ccbf515fe20cdb4b23f403329981508304

See more details on using hashes here.

File details

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

File metadata

  • Download URL: lsm-0.5.10-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.10-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 6e88f8d784a442c64f1fbf78b96eadc6e0327ad229c386a95a9454a633699411
MD5 bee5952ea5a35f6ab256ded1123e4707
BLAKE2b-256 147c5d53cb5742220181f030d75521067c0520a88af21b5cc645c0dcf06bdaef

See more details on using hashes here.

File details

Details for the file lsm-0.5.10-cp311-cp311-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for lsm-0.5.10-cp311-cp311-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 3d152942c47a911d18cb151b21e84802f92d162881438f8c833903f66b85d610
MD5 e3e7069ce1b750096d78ff31f9b517a5
BLAKE2b-256 d2acc97dd47eaa94c823b2a0b768ee72e70a52d3639502a6ec5e5d26977fbc09

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for lsm-0.5.10-cp311-cp311-macosx_10_9_universal2.whl
Algorithm Hash digest
SHA256 137e1001a16e26f5cd2c87060c342a30c789ba0e2d173448222b847f1acb8d87
MD5 59e076094143b640195ab7973cda72d5
BLAKE2b-256 69d1201978c03999cde64e2a203cd097831c10b85b61576825f7e1844e4e4aae

See more details on using hashes here.

File details

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

File metadata

  • Download URL: lsm-0.5.10-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.10-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 93eaf0b9c0fa7621aff8025985768fe5b0b03dd7fd03905ee916c03ea7e6cad3
MD5 860bd5f08c9636593a5dc89b9321821f
BLAKE2b-256 6ebb97075074ee34f8e9b96a57a5de8309e332aa909899e3d925be404a9eaece

See more details on using hashes here.

File details

Details for the file lsm-0.5.10-cp310-cp310-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for lsm-0.5.10-cp310-cp310-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 63d549b3397f4d8ad1d95c59bda3345be11571af88c8d05b8d860e369c2db165
MD5 5d22b54bf4d9e7edc2fe06aae9cf46ae
BLAKE2b-256 772d6e4feb6707e1ae2e0f2c27ba4ef761479c634370f9b22f13403c4c4600c8

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for lsm-0.5.10-cp310-cp310-macosx_10_9_universal2.whl
Algorithm Hash digest
SHA256 2735e8def90e2c3ddf71552e0a21dfbd90a3bd2d55c462ca03f8130b0010b0d4
MD5 b5d06609d04e07defae71e3f9e484a86
BLAKE2b-256 520f8fa0ff7a67fd959b1d1c493cd7a888698cbb2378f601d4f23b5068256d28

See more details on using hashes here.

File details

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

File metadata

  • Download URL: lsm-0.5.10-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.10-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 85208bff639387d112dcfd4d278a4adc1dcff5c860110ecd14047feeec71900e
MD5 8e27824d17ec21accbbe180e32ede3ac
BLAKE2b-256 0da4cd5f47079c1b66357dad3e522923e32fa08724fc9f2b844309948bc90172

See more details on using hashes here.

File details

Details for the file lsm-0.5.10-cp39-cp39-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for lsm-0.5.10-cp39-cp39-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 8f0792bebe41bf2f24d460c0179d3b75b6de831eb10800be0fc36851c5af4ec8
MD5 1460b3bb6b0629956b75bb790b5d772a
BLAKE2b-256 d55c62e8195417803797fecdfee6538c0b4632249efa4ced7fec56c8a689c5c8

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for lsm-0.5.10-cp39-cp39-macosx_10_9_universal2.whl
Algorithm Hash digest
SHA256 3bc1eb331877563b7061e81ae765adcf775e44f7e86ebba9d23aedb5d4db26e1
MD5 5abb0d3c9f3fb6d8d90f9acb43e1bcb4
BLAKE2b-256 839699145e7a94d8b2763d5c33dec6e8e7e06f69695e9ced00a97fb23df5d230

See more details on using hashes here.

Supported by

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