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.2.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.2-cp38-abi3-win_amd64.whl (2.5 MB view details)

Uploaded CPython 3.8+Windows x86-64

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

Uploaded CPython 3.8+Windows x86

scyllaft-1.6.2-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.2-cp38-abi3-musllinux_1_2_i686.whl (3.4 MB view details)

Uploaded CPython 3.8+musllinux: musl 1.2+ i686

scyllaft-1.6.2-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.2-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.2-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.2-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.2-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.2-cp38-abi3-macosx_11_0_arm64.whl (3.2 MB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

scyllaft-1.6.2-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 scyllaft-1.6.2.tar.gz.

File metadata

  • Download URL: scyllaft-1.6.2.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.2.tar.gz
Algorithm Hash digest
SHA256 e53b65b3f62a372e08f0e4a306ab6f4f3a46b779decda447010f1ac487acc31f
MD5 217923201faafe65b8ffcd83e450bab1
BLAKE2b-256 dbfda0b5b87e649bbd81a26a10c2ecd1ecebbdf849d9e828e522f5a5b3285359

See more details on using hashes here.

File details

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

File metadata

  • Download URL: scyllaft-1.6.2-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.2-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 8f84fb3c17b35385cce485820b1a52263aa41eec667658f329acc7e844059c0e
MD5 305d3d8948ca4fd456a5a0fe831e7327
BLAKE2b-256 ef6c6f910ec9cfaa669c7ecdf95309c8260bd2a17327303396db40467d89dde5

See more details on using hashes here.

File details

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

File metadata

  • Download URL: scyllaft-1.6.2-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.2-cp38-abi3-win32.whl
Algorithm Hash digest
SHA256 d82f666ff23456236ad214d9b1ca17a7c3c3d0f01071b44c031208652f8452d2
MD5 59771274022b22dab6ba50296a3eacc3
BLAKE2b-256 26934bc0c8357ee5589a880d760ed52524f9882a8eaf4337b636d72455dd6e1d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for scyllaft-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 376bcb63b7305e6eaa163b53bc474735a09777301d3049266dfe08e029454c50
MD5 fa294cb80dccc3db74a3cd0135e18ba6
BLAKE2b-256 ef7d93faa99d22e6e01760ce93f160882641b7f0967b8986904aa8478893007b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for scyllaft-1.6.2-cp38-abi3-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 8f343cf868efa8754b3a66ef6b91a0f3e5faf6d938844419bde2e5af8513609c
MD5 80344df4aab179a12ddd8cacf19cde80
BLAKE2b-256 d2ecf86ffb0ec51e5c79b560620ef0e24086396c7485cc88900e2857ec291df0

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for scyllaft-1.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a8d7ea661b2ea9034928c965a683017725a2ef8c2804fbdfeb4f51253ab35154
MD5 df807b72f9a1b8a1840fae0972e47222
BLAKE2b-256 47bcd5011eba54ad2cceeb0f2c68948aa22fe3cd5e01e82d1c4540ce05493534

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for scyllaft-1.6.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
Algorithm Hash digest
SHA256 4eaca4b6d6e8423aad6d28fa30bcf7562b8ff718af8fed983133c1954f4dd868
MD5 4117253279fc3c9f98935390af3d95bc
BLAKE2b-256 0b42ed69da6435daaacb944e0982e7ab4f664425623c6ffed220bab246e949a9

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for scyllaft-1.6.2-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 94ecdc183a625d2259af04902f7a033121a2ed3bf25aa18580d3c32de017a5a4
MD5 3ac6d09cc7791b61307852438f2712bb
BLAKE2b-256 aaf8602cb1fbb7576c4ff08082ca07289acc2a4424ee48e40ef313a04f5a7b7c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for scyllaft-1.6.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm Hash digest
SHA256 476dda17b96010fe5dc26b17e37d13f7aa56af10338bf23c5371e62a5c6601ea
MD5 ca94b499075e9474d3c4f9059218511d
BLAKE2b-256 5a6fdd1746f07d0106b14b0f56d1a574efc382cbad72c5b1f2e392b5a04c8c56

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for scyllaft-1.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 b80ac1f49d05986849961a3b9f94398bd18c89a2604d173707cce0b2339c5a68
MD5 d0f5d476fb83ebf51bafcaab0b140e9a
BLAKE2b-256 84f182af451ecb7f0d892073c7bade4a76fc461ce7717d58eb6b5f0ea0fb8914

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for scyllaft-1.6.2-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 62b055057a872be48b34b77eff8d78dea4170a470973ce7fa30947d9d570ae38
MD5 a3a5682c59adeae7ccd2d18362750562
BLAKE2b-256 febdf071412d61d85da2efa9fe660e4616092502cd0fcd06cfd41ca36da0d613

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for scyllaft-1.6.2-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 366de9dfa671f7fcff90c1abce0bce5e5f57c1cfa65c8d9a2b4039f8689a1c81
MD5 2d07b35d6bed40621aa4bcf856a039d6
BLAKE2b-256 4695dcee4adf7de0e40a29b7976decbb6745369b77da8b54f73dfe21dbc72843

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