Skip to main content

Async scylla driver for python - FentechFork

Project description

PyPI PyPI - Downloads

Async Scylla driver for python

Python driver for ScyllaDB written in Rust. Though description says it's for scylla, however it can be used with Cassandra and AWS keyspaces as well.

This driver uses official ScyllaDB driver for Rust and exposes python API to interact with it.

This driver is a fork of Intreecom/scyllapy with additional features for pulling data.

Installation

To install it, use your favorite package manager for python packages:

pip install scyllaft

Also, you can build from sources. To do it, install stable rust, maturin and openssl libs.

maturin build --release --out dist
# Then install whl file from dist folder.
pip install dist/*

Usage

The usage is pretty straitforward. Create a Scylla instance, run startup and start executing queries.

import asyncio

from scyllaft import Scylla


async def main():
    scylla = Scylla(["localhost:9042"], keyspace="keyspace")
    await scylla.startup()
    await scylla.execute("SELECT * FROM table")
    await scylla.shutdown()

if __name__ == "__main__":
    asyncio.run(main())

Mapping of Scylla/Python Types

Scylla type Python type
int int
tinyint extra_types.TinyInt
bigint extra_types.BigInt
varint any int type
float float
double extra_types.Double
decimal decimal.Decimal
ascii str
text str
varchar str
blob bytes
boolean bool
counter extra_types.Counter
date datetime.date
uuid uuid.UUID
inet ipaddress
time datetime.time
timestamp datetime.datetime
duration dateutil.relativedelta

All types from extra_types module are used to eliminate any possible ambiguity while passing parameters to queries. You can find more information about them in Extra types section.

We use relative delta from dateutil for duration, because it's the only way to represent it in python. Since scylla operates with months, days and nanosecond, there's no way we can represent it in python, because months are variable length.

Parametrizing queries

While executing queries sometimes you may want to fine-tune some parameters, or dynamically pass values to the query.

Passing parameters is simple. You need to add a paramters list to the query.

    await scylla.execute(
        "INSERT INTO otps(id, otp) VALUES (?, ?)",
        [uuid.uuid4(), uuid.uuid4().hex],
    )

Queries can be modified further by using Query class. It allows you to define consistency for query or enable tracing.

from scyllaft import Scylla, Query, Consistency, SerialConsistency

async def make_query(scylla: Scylla) -> None:
    query = Query(
        "SELECT * FROM table",
        consistency=Consistency.ALL,
        serial_consistency=SerialConsistency.LOCAL_SERIAL,
        request_timeout=1,
        timestamp=int(time.time()),
        is_idempotent=False,
        tracing=True,
    )
    result = await scylla.execute(query)
    print(result.all())

Also, with queries you can tweak random parameters for a specific execution.

query = Query("SELECT * FROM table")

new_query = query.with_consistency(Consistency.ALL)

All with_ methods create new query, copying all other parameters.

Named parameters

Also, you can provide named parameters to querties, by using name placeholders instead of ?.

For example:

async def insert(scylla: Scylla):
    await scylla.execute(
        "INSERT INTO table(id, name) VALUES (:id, :name)",
        params={"id": uuid.uuid4(), "name": uuid.uuid4().hex}
    )

Important note: All variables should be in snake_case. Otherwise the error may be raised or parameter may not be placed in query correctly. This happens, because scylla makes all parameters in query lowercase.

The scyllaft makes all parameters lowercase, but you may run into problems, if you use multiple parameters that differ only in cases of some letters.

Preparing queries

Also, queries can be prepared. You can either prepare raw strings, or Query objects.

from scyllaft import Scylla, Query, PreparedQuery


async def prepare(scylla: Scylla, query: str | Query) -> PreparedQuery:
    return await scylla.prepare(query)

You can execute prepared queries by passing them to execute method.

async def run_prepared(scylla: Scylla) -> None:
    prepared = await scylla.prepare("INSERT INTO memse(title) VALUES (?)")
    await scylla.execute(prepared, ("American joke",))

Batching

We support batches. Batching can help a lot when you have lots of queries that you want to execute at the same time.

from scyllaft import Scylla, Batch


async def run_batch(scylla: Scylla, num_queries: int) -> None:
    batch = Batch()
    for _ in range(num_queries):
        batch.add_query("SELECT * FROM table WHERE id = ?")
    await scylla.batch(batch, [(i,) for i in range(num_queries)])

Here we pass query as strings. But you can also add Prepared statements or Query objects.

Also, note that we pass list of lists as parametes for execute. Each element of the list is going to be used in the query with the same index. But named parameters are not supported for batches.

async def run_batch(scylla: Scylla, num_queries: int) -> None:
    batch = Batch()
    batch.add_query("SELECT * FROM table WHERE id = :id")
    await scylla.batch(batch, [{"id": 1}])  # Will rase an error!

Pagination

Sometimes you want to query lots of data. For such cases it's better not to fetch all results at once, but fetch them using pagination. It reduces load not only on your application, but also on a cluster.

To execute query with pagination, simply add paged=True in execute method. After doing so, execute method will return IterableQueryResult, instead of QueryResult. Instances of IterableQueryResult can be iterated with async for statements. You, as a client, won't see any information about pages, it's all handeled internally within a driver.

Please note, that paginated queries are slower to fetch all rows, but much more memory efficent for large datasets.

    result = await scylla.execute("SELECT * FROM table", paged=True)
    async for row in result:
        print(row)

Of course, you can change how results returned to you, by either using scalars or as_cls. For example:

async def func(scylla: Scylla) -> None:
    rows = await scylla.execute("SELECT id FROM table", paged=True)
    # Will print ids of each returned row.
    async for test_id in rows.scalars():
        print(test_id)
from dataclasses import dataclass

@dataclass
class MyDTO:
    id: int
    val: int

async def func(scylla: Scylla) -> None:
    rows = await scylla.execute("SELECT * FROM table", paged=True)
    # Will print ids of each returned row.
    async for my_dto in rows.as_cls(MyDTO):
        print(my_dto.id, my_dto.val)

Execution profiles

You can define profiles using ExecutionProfile class. After that the profile can be used while creating a cluster or when defining queries.

from scyllaft import Consistency, ExecutionProfile, Query, Scylla, SerialConsistency
from scyllaft.load_balancing import LoadBalancingPolicy, LatencyAwareness

default_profile = ExecutionProfile(
    consistency=Consistency.LOCAL_QUORUM,
    serial_consistency=SerialConsistency.LOCAL_SERIAL,
    request_timeout=2,
)

async def main():
    query_profile = ExecutionProfile(
        consistency=Consistency.ALL,
        serial_consistency=SerialConsistency.SERIAL,
        # Load balancing cannot be constructed without running event loop.
        # If you won't do it inside async funcion, it will result in error.
        load_balancing_policy=await LoadBalancingPolicy.build(
            token_aware=True,
            prefer_rack="rack1",
            prefer_datacenter="dc1",
            permit_dc_failover=True,
            shuffling_replicas=True,
            latency_awareness=LatencyAwareness(
                minimum_measurements=10,
                retry_period=1000,
                exclusion_threshold=1.4,
                update_rate=1000,
                scale=2,
            ),
        ),
    )

    scylla = Scylla(
        ["192.168.32.4"],
        default_execution_profile=default_profile,
    )
    await scylla.startup()
    await scylla.execute(
        Query(
            "SELECT * FROM system_schema.keyspaces;",
            profile=query_profile,
        )
    )

Results

Every query returns a class that represents returned rows. It allows you to not fetch and parse actual data if you don't need it. Please be aware that if your query was not expecting any rows in return. Like for Update or Insert queries. The RuntimeError is raised when you call all or first.

result = await scylla.execute("SELECT * FROM table")
print(result.all())

If you were executing query with tracing, you can get tracing id from results.

result = await scylla.execute(Query("SELECT * FROM table", tracing=True))
print(result.trace_id)

Also it's possible to parse your data using custom classes. You can use dataclasses or Pydantic.

from dataclasses import dataclass

@dataclass
class MyDTO:
    id: uuid.UUID
    name: str

result = await scylla.execute("SELECT * FROM inbox")
print(result.all(as_class=MyDTO))

Or with pydantic.

from pydantic import BaseModel

class MyDTO(BaseModel):
    user_id: uuid.UUID
    chat_id: uuid.UUID

result = await scylla.execute("SELECT * FROM inbox")
print(result.all(as_class=MyDTO))

Extra types

Since Rust enforces typing, it's hard to identify which value user tries to pass as a parameter. For example, 1 that comes from python can be either tinyint, smallint or even bigint. But we cannot say for sure how many bytes should we send to server. That's why we created some extra_types to eliminate any possible ambigousnity.

You can find these types in extra_types module from scyllaft.

from scyllaft import Scylla, extra_types

async def execute(scylla: Scylla) -> None:
    await scylla.execute(
        "INSERT INTO table(id, name) VALUES (?, ?)",
        [extra_types.BigInt(1), "memelord"],
    )

User defined types

We also support user defined types. You can pass them as a parameter to query. Or parse it as a model in response.

Here's binding example. Imagine we have defined a type in scylla like this:

CREATE TYPE IF NOT EXISTS test (
    id int,
    name text
);

Now we need to define a model for it in python.

from dataclasses import dataclass
from scyllaft.extra_types import ScyllaPyUDT

@dataclass
class TestUDT(ScyllaPyUDT):
    # Always define fields in the same order as in scylla.
    # Otherwise you will get an error, or wrong data.
    id: int
    name: str

async def execute(scylla: Scylla) -> None:
    await scylla.execute(
        "INSERT INTO table(id, udt_col) VALUES (?, ?)",
        [1, TestUDT(id=1, name="test")],
    )

We also support pydantic based models. Decalre them like this:

from pydantic import BaseModel
from scyllaft.extra_types import ScyllaPyUDT


class TestUDT(BaseModel, ScyllaPyUDT):
    # Always define fields in the same order as in scylla.
    # Otherwise you will get an error, or wrong data.
    id: int
    name: str

Query building

Scyllaft gives you ability to build queries, instead of working with raw cql. The main advantage that it's harder to make syntax error, while creating queries.

Base classes for Query building can be found in scyllaft.query_builder.

Usage example:

from scyllaft import Scylla
from scyllaft.query_builder import Insert, Select, Update, Delete


async def main(scylla: Scylla):
    await scylla.execute("CREATE TABLE users(id INT PRIMARY KEY, name TEXT)")

    user_id = 1

    # We create a user with id and name.
    await Insert("users").set("id", user_id).set(
        "name", "user"
    ).if_not_exists().execute(scylla)

    # We update it's name to be user2
    await Update("users").set("name", "user2").where("id = ?", [user_id]).execute(
        scylla
    )

    # We select all users with id = user_id;
    res = await Select("users").where("id = ?", [user_id]).execute(scylla)
    # Verify that it's correct.
    assert res.first() == {"id": 1, "name": "user2"}

    # We delete our user.
    await Delete("users").where("id = ?", [user_id]).if_exists().execute(scylla)

    res = await Select("users").where("id = ?", [user_id]).execute(scylla)

    # Verify that user is deleted.
    assert not res.all()

    await scylla.execute("DROP TABLE users")

Also, you can pass built queries into InlineBatches. You cannot use queries built with query_builder module with default batches. This constraint is exists, because we need to use values from within your queries and should ignore all parameters passed in batch method of scylla.

Here's batch usage example.

from scyllaft import Scylla, InlineBatch
from scyllaft.query_builder import Insert


async def execute_batch(scylla: Scylla) -> None:
    batch = InlineBatch()
    for i in range(10):
        Insert("users").set("id", i).set("name", "test").add_to_batch(batch)
    await scylla.batch(batch)

Paging

Queries that were built with QueryBuilder also support paged returns. But it supported only for select, because update, delete and insert should not return anything and it makes no sense implementing it. To make built Select query return paginated iterator, add paged parameter in execute method.

The paged argument represents the page_size of the query.

    rows = await Select("test").execute(scylla, paged=5000)
    async for row in rows:
        print(row['id'])

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

scyllaft-1.6.3.tar.gz (59.1 kB view details)

Uploaded Source

Built Distributions

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

scyllaft-1.6.3-cp38-abi3-win_amd64.whl (2.5 MB view details)

Uploaded CPython 3.8+Windows x86-64

scyllaft-1.6.3-cp38-abi3-win32.whl (2.3 MB view details)

Uploaded CPython 3.8+Windows x86

scyllaft-1.6.3-cp38-abi3-musllinux_1_2_x86_64.whl (3.5 MB view details)

Uploaded CPython 3.8+musllinux: musl 1.2+ x86-64

scyllaft-1.6.3-cp38-abi3-musllinux_1_2_i686.whl (3.4 MB view details)

Uploaded CPython 3.8+musllinux: musl 1.2+ i686

scyllaft-1.6.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.3 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ x86-64

scyllaft-1.6.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (3.2 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ppc64le

scyllaft-1.6.3-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl (3.3 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ i686

scyllaft-1.6.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (3.0 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ARMv7l

scyllaft-1.6.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (3.5 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ARM64

scyllaft-1.6.3-cp38-abi3-macosx_11_0_arm64.whl (3.2 MB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

scyllaft-1.6.3-cp38-abi3-macosx_10_12_x86_64.whl (3.0 MB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

File details

Details for the file scyllaft-1.6.3.tar.gz.

File metadata

  • Download URL: scyllaft-1.6.3.tar.gz
  • Upload date:
  • Size: 59.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.7.0

File hashes

Hashes for scyllaft-1.6.3.tar.gz
Algorithm Hash digest
SHA256 2a84bc4170540e6c2a2148be7993a38cac831baeef992d9a827e8e2c4669c234
MD5 52a2e7ba6af4a5a0c030dc2b8dadbd75
BLAKE2b-256 5243b5ff17925d6f547c2e6db5749cee09d41c4b217d4c668ae03d85e09703d7

See more details on using hashes here.

File details

Details for the file scyllaft-1.6.3-cp38-abi3-win_amd64.whl.

File metadata

  • Download URL: scyllaft-1.6.3-cp38-abi3-win_amd64.whl
  • Upload date:
  • Size: 2.5 MB
  • Tags: CPython 3.8+, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.7.0

File hashes

Hashes for scyllaft-1.6.3-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 c8738885f90770a162e3bd3a53c4e74283becd5e96f01761c18dfe1e1d5962ba
MD5 9fbfd497892144df97c00e341e964e1a
BLAKE2b-256 6dbf8efc3ef541b84ca89df7e4aa101c65221e5793afe7ca9e751cd2f77e172d

See more details on using hashes here.

File details

Details for the file scyllaft-1.6.3-cp38-abi3-win32.whl.

File metadata

  • Download URL: scyllaft-1.6.3-cp38-abi3-win32.whl
  • Upload date:
  • Size: 2.3 MB
  • Tags: CPython 3.8+, Windows x86
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.7.0

File hashes

Hashes for scyllaft-1.6.3-cp38-abi3-win32.whl
Algorithm Hash digest
SHA256 2b9638c04aba3598aeb8a7560d87237dc19326b5cc0946f8cbfc669a17c12bb3
MD5 2174418c3a5a53c3cea4479d8c4d198f
BLAKE2b-256 eb5c6faa0428ff3fad4c23d503d8aea2ca937384ef8713912a3bc1affe482a4d

See more details on using hashes here.

File details

Details for the file scyllaft-1.6.3-cp38-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for scyllaft-1.6.3-cp38-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 e0334b74a372d30abdfc8897aeef9a43d01177af880c6bf61115f6cc6ea79fc0
MD5 783e7b3823a8ac4569e457720fe08982
BLAKE2b-256 2eb264f329f4790614e789b2ed41b52b158dd30df5a6512980d95eea4130426d

See more details on using hashes here.

File details

Details for the file scyllaft-1.6.3-cp38-abi3-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for scyllaft-1.6.3-cp38-abi3-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 001b3eb9db60a40305d27f47ebd47f39eadd396fd18c270521eba4e701051de6
MD5 9f8c27f786127062e36a1432e1b06e0b
BLAKE2b-256 025eb33105d5f80ae3d04a074c8fef588ebcdd9f435380ee29ad55485bc6a285

See more details on using hashes here.

File details

Details for the file scyllaft-1.6.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for scyllaft-1.6.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 75f6bd6a94b6e7c0a1a8f8a7213f8dad6440976f4f91a2d494d77d01873b48d5
MD5 29b97b04e0d45c699b9eb045c6c4a9ef
BLAKE2b-256 7c4a63b3119e1d5a09047237a4de6a867bfe99d53ca8f7d67d9f2b4783c26b6f

See more details on using hashes here.

File details

Details for the file scyllaft-1.6.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl.

File metadata

File hashes

Hashes for scyllaft-1.6.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
Algorithm Hash digest
SHA256 8927ca78209dfe8f72d2f0b5c6cc54da4f723e83ed9bbf621a05ae2c17c04406
MD5 b07743d2060db64b2298bda604fe7ad8
BLAKE2b-256 64fb6f75a36cb4454d89fc948651cca6c4bfce9cec3680431b153b49758536cd

See more details on using hashes here.

File details

Details for the file scyllaft-1.6.3-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl.

File metadata

File hashes

Hashes for scyllaft-1.6.3-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 923e6c72f7866b3d49b9476e6a27efb99850bc42461231893dfca43f56fb75df
MD5 a4f61c54aba559f2a1985b2499c502ef
BLAKE2b-256 61699ef933046b3b24af5759185554686bea962ef828dcabef17fec1fcfaabf2

See more details on using hashes here.

File details

Details for the file scyllaft-1.6.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl.

File metadata

File hashes

Hashes for scyllaft-1.6.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm Hash digest
SHA256 940d737e96e8f7f2aca6895a60f0cad10c0c812b83d412b5b14cc41f3440ad9b
MD5 46b875ef1d9e97de5b781327f52676cd
BLAKE2b-256 468a24244d939bfb5192634d7264d19371c33df069edff16eb9f3f3db03425a7

See more details on using hashes here.

File details

Details for the file scyllaft-1.6.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for scyllaft-1.6.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 f04109a506e6a132a2ef8b31cb95cf193f35fbeb325829d09fec691f335a67be
MD5 cf2dd61949e215d7da782282a22885be
BLAKE2b-256 7380b55a7c6a38683ddd6f600f74c925168e8f98f6e4597da229f69434cea453

See more details on using hashes here.

File details

Details for the file scyllaft-1.6.3-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for scyllaft-1.6.3-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 269e6773636afa5882cbbf975223115452160c0fb7f432f4e309e3f955932243
MD5 ec3346b646a3530de9c15eac1ceba35f
BLAKE2b-256 64b1f1b41f6dfb42002722b48255a4abaf3c3c82212f3c3f5a263f45ed0cb27e

See more details on using hashes here.

File details

Details for the file scyllaft-1.6.3-cp38-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for scyllaft-1.6.3-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 fddc308c6afcc0d8a3d0a3583860147bdaecb38fd2945912eb268f17fce6f50b
MD5 553a4f3eccf21abe6f5e6e69b6e49fb4
BLAKE2b-256 3e32346bc34826c2834d20705addd47ec328914c5b77b8eecb1919c7aeeb8be3

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