Skip to main content

Async scylla driver for python

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.

Installation

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

pip install scyllapy

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

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 scyllapy 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.

Here's the list of scylla types and corresponding python types that you should use while passing parameters to queries:

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, becuase months are variable length.

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 scyllapy 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 scyllapy 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 scyllapy 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 scyllapy import Consistency, ExecutionProfile, Query, Scylla, SerialConsistency
from scyllapy.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 scyllapy.

from scyllapy 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 scyllapy.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 scyllapy.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

ScyllaPy 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 scyllapy.query_builder.

Usage example:

from scyllapy import Scylla
from scyllapy.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 scyllapy import Scylla, InlineBatch
from scyllapy.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.

    rows = await Select("test").execute(scylla, paged=True)
    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

scyllapy-1.3.3.tar.gz (58.8 kB view details)

Uploaded Source

Built Distributions

scyllapy-1.3.3-cp38-abi3-win_amd64.whl (2.5 MB view details)

Uploaded CPython 3.8+ Windows x86-64

scyllapy-1.3.3-cp38-abi3-win32.whl (2.2 MB view details)

Uploaded CPython 3.8+ Windows x86

scyllapy-1.3.3-cp38-abi3-musllinux_1_2_x86_64.whl (3.4 MB view details)

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

scyllapy-1.3.3-cp38-abi3-musllinux_1_2_i686.whl (3.3 MB view details)

Uploaded CPython 3.8+ musllinux: musl 1.2+ i686

scyllapy-1.3.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

scyllapy-1.3.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (3.1 MB view details)

Uploaded CPython 3.8+ manylinux: glibc 2.17+ ppc64le

scyllapy-1.3.3-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl (3.2 MB view details)

Uploaded CPython 3.8+ manylinux: glibc 2.17+ i686

scyllapy-1.3.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (2.8 MB view details)

Uploaded CPython 3.8+ manylinux: glibc 2.17+ ARMv7l

scyllapy-1.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (3.4 MB view details)

Uploaded CPython 3.8+ manylinux: glibc 2.17+ ARM64

scyllapy-1.3.3-cp38-abi3-macosx_11_0_arm64.whl (3.1 MB view details)

Uploaded CPython 3.8+ macOS 11.0+ ARM64

scyllapy-1.3.3-cp38-abi3-macosx_10_12_x86_64.whl (2.9 MB view details)

Uploaded CPython 3.8+ macOS 10.12+ x86-64

File details

Details for the file scyllapy-1.3.3.tar.gz.

File metadata

  • Download URL: scyllapy-1.3.3.tar.gz
  • Upload date:
  • Size: 58.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.7.4

File hashes

Hashes for scyllapy-1.3.3.tar.gz
Algorithm Hash digest
SHA256 652a130c845757b24a865c1a219de8b025a568b6ca989a0630d22f265bbc7d0c
MD5 c4fb4c4aa244edf55b18675e7f98bcc6
BLAKE2b-256 b7a35d52649957558319716f7038389c847ff38a189ced1963bd7c560393b30f

See more details on using hashes here.

File details

Details for the file scyllapy-1.3.3-cp38-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for scyllapy-1.3.3-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 e75d46bcf0de911b15e91af2bd325ab75f663c4f1a7e57fe78a9f64ddee697e0
MD5 ce542a50581c6d4a5e2051a2e0edcb64
BLAKE2b-256 626d48a3499c2c242baa2b6f028cecd997794637cf4d1e7ebd3adfd160b7f7dd

See more details on using hashes here.

File details

Details for the file scyllapy-1.3.3-cp38-abi3-win32.whl.

File metadata

  • Download URL: scyllapy-1.3.3-cp38-abi3-win32.whl
  • Upload date:
  • Size: 2.2 MB
  • Tags: CPython 3.8+, Windows x86
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.7.4

File hashes

Hashes for scyllapy-1.3.3-cp38-abi3-win32.whl
Algorithm Hash digest
SHA256 6683e83d13ae285b82be265431d21d7b19e44848b574d8d07b5cf0aa3cfa1228
MD5 c2dcb429c8748389f3494e2be5c9585c
BLAKE2b-256 724691bd20d8bcbbf6007288e246d887c37090e36cd57da2c84db02f4b8f7338

See more details on using hashes here.

File details

Details for the file scyllapy-1.3.3-cp38-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for scyllapy-1.3.3-cp38-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 1da4ba5417f565c5e8fd585b34c14c7631c2339406acfa38fd6845567a7fbb16
MD5 c2ef90a5e6e0a8b38a48da40b8cef975
BLAKE2b-256 63686bce59f07395c7d21810058827e2c1ae4c37f7e8f88832579d03f47beb16

See more details on using hashes here.

File details

Details for the file scyllapy-1.3.3-cp38-abi3-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for scyllapy-1.3.3-cp38-abi3-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 574c58b221e61b9cb6421f729bb353be6e2f4bfb77ff6a738c00d4c755f479cf
MD5 3748de96b5dd5badb3ab4178fdb59b61
BLAKE2b-256 58991f8c410c7ab71fd6fc37c7bd55a5909892ae38cade376b1bfd8ab2dbf707

See more details on using hashes here.

File details

Details for the file scyllapy-1.3.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for scyllapy-1.3.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 685712d9c11ea2dc84ef94a5702dfebf32f6197064d39cb81ba1bc307b4a0ba9
MD5 6094551b2859be1678bfb8ea1d5e6c3b
BLAKE2b-256 73e2a3dd5bd274d0f283212bcdbd566be04d7ed682a06a731f3a6220872cee47

See more details on using hashes here.

File details

Details for the file scyllapy-1.3.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl.

File metadata

File hashes

Hashes for scyllapy-1.3.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
Algorithm Hash digest
SHA256 fcd023cbbcc01168f226170873dc75b676861927c51eb0d3d55af196bbe8c6ba
MD5 54da964042615be6e9bf2060e20fb11c
BLAKE2b-256 416751e44c34dd6f48abc822a6dde7aae313d459158dba8212f09ca0163544d5

See more details on using hashes here.

File details

Details for the file scyllapy-1.3.3-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl.

File metadata

File hashes

Hashes for scyllapy-1.3.3-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 7e0333f865213d16e54eeac87d522a1593cc438aac5c54ea79736a36c8c6e3e7
MD5 ae935b2ca6a9e45d4e7fcb69b2332a89
BLAKE2b-256 5ed436f977aa75acf804373efc19b36967dc43a30e33fa69bc87b4a123614753

See more details on using hashes here.

File details

Details for the file scyllapy-1.3.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl.

File metadata

File hashes

Hashes for scyllapy-1.3.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm Hash digest
SHA256 579e4a8805b6bb3f02af6201589e434b93c685efccfaf4e215d92c8fa41c1d2b
MD5 ca724414c6b4c482f4537a24e46f0ccf
BLAKE2b-256 d0b90e666a8dfa786e0ef87f32eb6e56890985053e54a51a25ea09364d70ec87

See more details on using hashes here.

File details

Details for the file scyllapy-1.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for scyllapy-1.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 910f66f1e8782aa27c459b1b0c7713c504b2c6c8862f367c3476182582844b08
MD5 687b3e02e3876a7966612310da5420b7
BLAKE2b-256 3e7583e03aaa6967e1b3b351dcd6aeb48fc2de9c4436955b984a851d826b59f7

See more details on using hashes here.

File details

Details for the file scyllapy-1.3.3-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for scyllapy-1.3.3-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 7a0ffb592d07ab834ee1c5403aad0b982bf3717133b05ee34c289cf13e7fec4b
MD5 08e17068410fbc729f21ae5c235d5464
BLAKE2b-256 de2c2a2bc351f0681009394a901925df0026d117ca9f9d3dfb5d4ca60369c0f2

See more details on using hashes here.

File details

Details for the file scyllapy-1.3.3-cp38-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for scyllapy-1.3.3-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 2de87f6c96ed2d3e272031e0814b3bb82bcec20efd649c616a2b385d0d906930
MD5 330c0f028ac5cac16b922c0382b8d684
BLAKE2b-256 6bc315bb6285b5d95ab2d8c62240eb7f70791f9b3f767c4f6abe93e82cf08eab

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 Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page