Skip to main content

fast bindings for the sophia database

Project description

sophy, fast Python bindings for Sophia embedded database, v2.2.

About sophy

  • Written in Cython for speed and low-overhead
  • Clean, memorable APIs
  • Extensive support for Sophia's features
  • No 3rd-party dependencies besides Cython
  • Documentation on readthedocs

About Sophia

  • Ordered key/value store
  • Keys and values can be composed of multiple fields of differing data-types
  • ACID transactions
  • MVCC, optimistic, non-blocking concurrency with multiple readers and writers.
  • Multiple databases per environment
  • Multiple- and single-statement transactions across databases
  • Prefix searches
  • Automatic garbage collection and key expiration
  • Hot backup
  • Compression
  • Multi-threaded compaction
  • mmap support, direct I/O support
  • APIs for variety of statistics on storage engine internals
  • BSD licensed

Some ideas of where Sophia might be a good fit

  • Running on application servers, low-latency / high-throughput
  • Time-series
  • Analytics / Events / Logging
  • Full-text search
  • Secondary-index for external data-store

Limitations

  • Not tested on Windoze.

If you encounter any bugs in the library, please open an issue, including a description of the bug and any related traceback.

Installation

The sophia sources are bundled with the sophy source code, so the only thing you need to install is Cython. You can install from GitHub or from PyPI.

Pip instructions:

$ pip install sophy

Or to install the latest code from master:

$ pip install -e git+https://github.com/coleifer/sophy#egg=sophy

Git instructions:

$ pip install Cython
$ git clone https://github.com/coleifer/sophy
$ cd sophy
$ python setup.py build
$ python setup.py install

To run the tests:

$ python tests.py


Overview

Sophy is very simple to use. It acts like a Python dict object, but in addition to normal dictionary operations, you can read slices of data that are returned efficiently using cursors. Similarly, bulk writes using update() use an efficient, atomic batch operation.

Despite the simple APIs, Sophia has quite a few advanced features. There is too much to cover everything in this document, so be sure to check out the official Sophia storage engine documentation.

The next section will show how to perform common actions with sophy.

Using Sophy

Let's begin by import sophy and creating an environment. The environment can host multiple databases, each of which may have a different schema. In this example our database will store arbitrary binary data as the key and value. Finally we'll open the environment so we can start storing and retrieving data.

from sophy import Sophia, Schema, StringIndex

# Instantiate our environment by passing a directory path which will store the
# various data and metadata for our databases.
env = Sophia('/path/to/store/data')

# We'll define a very simple schema consisting of a single utf-8 string for the
# key, and a single utf-8 string for the associated value.
schema = Schema(key_parts=[StringIndex('key')],
                value_parts=[StringIndex('value')])

# Create a key/value database using the schema above.
db = env.add_database('example_db', schema)

if not env.open():
    raise Exception('Unable to open Sophia environment.')

CRUD operations

Sophy databases use the familiar dict APIs for CRUD operations:

db['name'] = 'Huey'
db['animal_type'] = 'cat'
print(db['name'], 'is a', db['animal_type'])  # Huey is a cat

'name' in db  # True
'color' in db  # False

db['temp_val'] = 'foo'
del db['temp_val']
print(db['temp_val'])  # raises a KeyError.

Use update() for bulk-insert, and multi_get() for bulk-fetch. Unlike __getitem__(), calling multi_get() with a non-existant key will not raise an exception and return None instead.

db.update(k1='v1', k2='v2', k3='v3')

for value in db.multi_get(['k1', 'k3', 'kx']):
    print(value)
# v1
# v3
# None

result_dict = db.multi_get_dict(['k1', 'k3', 'kx'])
# {'k1': 'v1', 'k3': 'v3'}

Other dictionary methods

Sophy databases also provides efficient implementations for keys(), values() and items(). Unlike dictionaries, however, iterating directly over a Sophy database will return the equivalent of the items() (as opposed to the just the keys):

db.update(k1='v1', k2='v2', k3='v3')

list(db)
# [('k1', 'v1'), ('k2', 'v2'), ('k3', 'v3')]


db.items()
# same as above.


db.keys()
# ['k1', 'k2', 'k3']


db.values()
# ['v1', 'v2', 'v3']

There are two ways to get the count of items in a database. You can use the len() function, which is not very efficient since it must allocate a cursor and iterate through the full database. An alternative is the index_count property, which may not be exact as it includes transactional duplicates and not-yet-merged duplicates.

print(len(db))
# 3

print(db.index_count)
# 3

Fetching ranges

Because Sophia is an ordered data-store, performing ordered range scans is efficient. To retrieve a range of key-value pairs with Sophy, use the ordinary dictionary lookup with a slice instead.

db.update(k1='v1', k2='v2', k3='v3', k4='v4')


# Slice key-ranges are inclusive:
db['k1':'k3']
# [('k1', 'v1'), ('k2', 'v2'), ('k3', 'v3')]


# Inexact matches are fine, too:
db['k1.1':'k3.1']
# [('k2', 'v2'), ('k3', 'v3')]


# Leave the start or end empty to retrieve from the first/to the last key:
db[:'k2']
# [('k1', 'v1'), ('k2', 'v2')]

db['k3':]
# [('k3', 'v3'), ('k4', 'v4')]


# To retrieve a range in reverse order, use the higher key first:
db['k3':'k1']
# [('k3', 'v3'), ('k2', 'v2'), ('k1', 'v1')]

To retrieve a range in reverse order where the start or end is unspecified, you can pass in True as the step value of the slice to also indicate reverse:

db[:'k2':True]
# [('k2', 'v2'), ('k1', 'v1')]

db['k3'::True]
# [('k4', 'v4'), ('k3', 'v3')]

db[::True]
# [('k4', 'v4'), ('k3', 'v3'), ('k2', 'v2'), ('k1', 'v1')]

Cursors

For finer-grained control over iteration, or to do prefix-matching, Sophy provides a cursor interface.

The cursor() method accepts 5 parameters:

  • order (default=>=) -- semantics for matching the start key and ordering results.
  • key -- the start key
  • prefix -- search for prefix matches
  • keys -- (default=True) -- return keys while iterating
  • values -- (default=True) -- return values while iterating

Suppose we were storing events in a database and were using an ISO-8601-formatted date-time as the key. Since ISO-8601 sorts lexicographically, we could retrieve events in correct order simply by iterating. To retrieve a particular slice of time, a prefix could be specified:

# Iterate over events for July, 2017:
for timestamp, event_data in db.cursor(key='2017-07-01T00:00:00',
                                       prefix='2017-07-'):
    process_event(timestamp, event_data)

Transactions

Sophia supports ACID transactions. Even better, a single transaction can cover operations to multiple databases in a given environment.

Example usage:

account_balance = env.add_database('balance', ...)
transaction_log = env.add_database('transaction_log', ...)

# ...

def transfer_funds(from_acct, to_acct, amount):
    with env.transaction() as txn:
        # To write to a database within a transaction, obtain a reference to
        # a wrapper object for the db:
        txn_acct_bal = txn[account_balance]
        txn_log = txn[transaction_log]

        # Transfer the asset by updating the respective balances. Note that we
        # are operating on the wrapper database, not the db instance.
        from_bal = txn_acct_bal[from_acct]
        to_bal = txn_acct_bal[to_acct]
        txn_acct_bal[to_acct] = to_bal + amount
        txn_acct_bal[from_acct] = from_bal - amount

        # Log the transaction in the transaction_log database. Again, we use
        # the wrapper for the database:
        txn_log[from_acct, to_acct, get_timestamp()] = amount

Multiple transactions are allowed to be open at the same time, but if there are conflicting changes, an exception will be thrown when attempting to commit the offending transaction:

# Create a basic k/v store. Schema.key_value() is a convenience/factory-method.
kv = env.add_database('main', Schema.key_value())

# ...

# Instead of using the context manager, we'll call begin() explicitly so we
# can show the interaction of 2 open transactions.
txn = env.transaction().begin()

t_kv = txn[kv]
t_kv['k1'] = 'v1'

txn2 = env.transaction().begin()
t2_kv = txn2[kv]

t2_kv['k1'] = 'v1-x'

txn2.commit()  # ERROR !!
# SophiaError('transaction is not finished, waiting for a concurrent
#              transaction to finish.')

txn.commit()  # OK

# Try again?
txn2.commit()  # ERROR !!
# SophiaError('transaction was rolled back by another concurrent transaction.')

Index types, multi-field keys and values

Sophia supports multi-field keys and values. Additionally, the individual fields can have different data-types. Sophy provides the following field types:

  • StringIndex - stores UTF8-encoded strings, e.g. text.
  • BytesIndex - stores bytestrings, e.g. binary data.
  • JsonIndex - stores arbitrary objects as UTF8-encoded JSON data.
  • MsgPackIndex - stores arbitrary objects using msgpack serialization.
  • PickleIndex - stores arbitrary objects using Python pickle library.
  • UUIDIndex - stores UUIDs.
  • U64Index and reversed, U64RevIndex
  • U32Index and reversed, U32RevIndex
  • U16Index and reversed, U16RevIndex
  • U8Index and reversed, U8RevIndex
  • SerializedIndex - which is basically a BytesIndex that accepts two functions: one for serializing the value to the db, and another for deserializing.

To store arbitrary data encoded using msgpack, you could use MsgPackIndex:

schema = Schema(StringIndex('key'), MsgPackIndex('value'))
db = sophia_env.add_database('main', schema)

To declare a database with a multi-field key or value, you will pass the individual fields as arguments when constructing the Schema object. To initialize a schema where the key is composed of two strings and a 64-bit unsigned integer, and the value is composed of a string, you would write:

key = [StringIndex('last_name'), StringIndex('first_name'), U64Index('area_code')]
value = [StringIndex('address_data')]
schema = Schema(key_parts=key, value_parts=value)

address_book = sophia_env.add_database('address_book', schema)

To store data, we use the same dictionary methods as usual, just passing tuples instead of individual values:

sophia_env.open()

address_book['kitty', 'huey', 66604] = '123 Meow St'
address_book['puppy', 'mickey', 66604] = '1337 Woof-woof Court'

To retrieve our data:

huey_address = address_book['kitty', 'huey', 66604]

To delete a row:

del address_book['puppy', 'mickey', 66604]

Indexing and slicing works as you would expect.

Note: when working with a multi-part value, a tuple containing the value components will be returned. When working with a scalar value, instead of returning a 1-item tuple, the value itself is returned.

Configuring and Administering Sophia

Sophia can be configured using special properties on the Sophia and Database objects. Refer to the configuration document for the details on the available options, including whether they are read-only, and the expected data-type.

For example, to query Sophia's status, you can use the status property, which is a readonly setting returning a string:

print(env.status)
# online

Other properties can be changed by assigning a new value to the property. For example, to read and then increase the number of threads used by the scheduler:

# Read the current number of scheduler threads.
nthreads = env.scheduler_threads

# Some settings, like scheduler_threads, can only be changed while the
# environment is closed.
env.close()
env.scheduler_threads = nthreads + 2
env.open()

Database-specific properties are available as well. For example to get the number of GET and SET operations performed on a database, you would write:

print(db.stat_get, 'get operations')
print(db.stat_set, 'set operations')

Refer to the documentation for complete lists of settings. Dotted-paths are translated into underscore-separated attributes.

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

sophy-0.7.1.tar.gz (207.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

sophy-0.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl (2.0 MB view details)

Uploaded CPython 3.14tmusllinux: musl 1.2+ x86-64

sophy-0.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

sophy-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl (1.9 MB view details)

Uploaded CPython 3.14musllinux: musl 1.2+ x86-64

sophy-0.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (2.0 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

sophy-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl (1.9 MB view details)

Uploaded CPython 3.13musllinux: musl 1.2+ x86-64

sophy-0.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

sophy-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl (2.0 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ x86-64

sophy-0.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

sophy-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl (2.0 MB view details)

Uploaded CPython 3.11musllinux: musl 1.2+ x86-64

sophy-0.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

sophy-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl (1.9 MB view details)

Uploaded CPython 3.10musllinux: musl 1.2+ x86-64

sophy-0.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (2.0 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

sophy-0.7.1-cp39-cp39-musllinux_1_2_x86_64.whl (1.9 MB view details)

Uploaded CPython 3.9musllinux: musl 1.2+ x86-64

sophy-0.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (2.0 MB view details)

Uploaded CPython 3.9manylinux: glibc 2.17+ x86-64manylinux: glibc 2.28+ x86-64

File details

Details for the file sophy-0.7.1.tar.gz.

File metadata

  • Download URL: sophy-0.7.1.tar.gz
  • Upload date:
  • Size: 207.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for sophy-0.7.1.tar.gz
Algorithm Hash digest
SHA256 ee20b5deeb6fb22abe17e0bab2c6779c37ee3ad8f67df820154a4372cde13f41
MD5 f886d739d0cc7d300f88b16c477b1bdf
BLAKE2b-256 9e4a4e1dc8cb2d349508fe206525253fc7537145e0c0c56fc391f2059e50777f

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 9b7e7fd298293ac6530efcac470305cc63aef464b6f2cad2a382ff0f1dac2ebc
MD5 e60d5d9e19e57fbe62e3a08658219a4c
BLAKE2b-256 e2b51bfcefe691de40fd44e5ac4b6ed8937e6653b76cb941835fbdbd3ce93544

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e8fbf7fdf67096725e6781f169de972fac6ba69bd67367f338def2e8c1dd8729
MD5 b3055dbe0d1a95b0f4a67322545d90ad
BLAKE2b-256 44b99c4e71fc29ab29006870c49abb9f1e2e8b28904faa9d5cb08ec5486c2c7c

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 85b087b595850b39b62ca60b76994d34b4f375145397a24708f380b3b46c4775
MD5 991d6ee1e385f023bf3edb619880b4d8
BLAKE2b-256 90ae1dbb44d709843661986117075451b252d86261a9b21674b5b7885be30782

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 83af43af8109aada3546f20f10c372c2a7b07c7fce5b27be6dcc59209afce00e
MD5 871e491331fb598733a4a652c9768dc8
BLAKE2b-256 f8e9a498b491ee533dfba0723053beb73acd227a41ecd2ea501bfbabdd47b0b7

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 e75268681383329978d852a26cf72d223bcd03321c9e726d46728f17c64c90b4
MD5 638a25065e071a98de4b03f929873265
BLAKE2b-256 907b533b4fc2008ea08ddd7ba15a4702dadbd6105427016bc2e6f99c095c46f3

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 2f08cf41b1eb5570929f80b49d823ea41e54d606ab296543f27c1ab8fb20dd05
MD5 86a273361f82be3beb2796af56ac6073
BLAKE2b-256 37c3c02946bd4a3d29bccd944e4fc11ea1c419b7bb28d31ec82910116afaacb4

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 ec149230fa68a3cfeacb0563dbc7c28a1077e03077856d6c28e203824f4de0ac
MD5 145a3724f21a47611595896542d25a80
BLAKE2b-256 38beba707b5b6baf0e17a29660bb11c368838e51ad178cb403d864e43906f2fa

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 47bfe3ac013f03b37d95b29cd29994aa8dbf9a6e2631c824814c14347b613b82
MD5 70fe4cf45f12de1b8484a96cae73557b
BLAKE2b-256 1f9b65f8e3b95e2ab0df6151209e4ce4475928494f18099db56c9fa910593ac0

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 4cf9ccd9656cf2b7b7818de717b6087aecdc376a078a8568c763624d5ac92e7a
MD5 7711eba058d85ddf625b81f5568b790b
BLAKE2b-256 04c2019d01d5edf38bee45ad66550cddb2631a8cb3791a58d3ec18a28d3d0ef7

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 90ea671ecc0527eb886be2f64f0c8adaf43c4b2ee7e8b980abb6e54a7efcab1d
MD5 a095d103d8ce9d289e62783f7d021a09
BLAKE2b-256 370f876ed6f137bf959935ce96558b2e069a1ecdbe45f1e1a6bf2ae55f731ff0

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 e7042ae11c2c27b3d3a4182e315bb9353bdcd555e19c923e9fb24ac36d805241
MD5 61788caeb1f7144dc96774004ee1d520
BLAKE2b-256 9ad4f086aef69caca0a92770a074543e1cd7eb38b1d159d5457b454005f4bfec

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 05889d35c12cdcb80a9e7382dddb113daabf585811178aebd88c20b914dab7a3
MD5 a95690f0f01047d9873eba1501edb743
BLAKE2b-256 3dff82bbd57ec2945e366129718386ecadd6fc2a7ac8978f018b10862a64a4b5

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp39-cp39-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp39-cp39-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 55cac0bb648d00ed806191ab80b4d943eae3a85b39c987c54c4e8930d1606870
MD5 9b429f10849782aa199aa604aa4e8ed1
BLAKE2b-256 b425505878f675b90c16f356aef9fe57e34727e41c9a26dc8bde2effcbec9555

See more details on using hashes here.

File details

Details for the file sophy-0.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for sophy-0.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 5d2b97c2aef5096b56b4ff27beea46476324c03aa243e227093048d5b12dc23f
MD5 5a642342db24938814251a969ee6398e
BLAKE2b-256 ceaa9ac70abd59f5da85153c19ecad1d874e75a2fd47476bfa9a7a3c68eb57eb

See more details on using hashes here.

Supported by

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