Async scylla driver for python
Project description
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.
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!
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"],
)
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)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Hashes for scyllapy-1.1.1-cp38-abi3-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8d9be27e4877c8ed9964939a2a6a2881e26c75f083783e715dc6e6cb39a38045 |
|
MD5 | d81cd8800a677677e8c058a7a62e4bc6 |
|
BLAKE2b-256 | 73ebb75f99c1ee9809c8e6f02b36cfc359bc0e0b39567b854b14bd58bd043418 |
Hashes for scyllapy-1.1.1-cp38-abi3-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c6d8da397475e3edecd06b8c96929fbbb585701369d9f70103ccc18bef11e3f0 |
|
MD5 | 8ca1b854e5fada06144674746e3f7088 |
|
BLAKE2b-256 | 0e2377fc77a0cefdcc506b66f3dcc4ebdfdb1dcef5e29c0e9655d944eb2bc296 |
Hashes for scyllapy-1.1.1-cp38-abi3-musllinux_1_2_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 109ba396b7765377782e31a46596cdb3739ab45081beabb1ce5ce8a54df3dfbd |
|
MD5 | 26ced44e51cc20bc6ce171ebe48ec400 |
|
BLAKE2b-256 | f4aabaaa1f4f10382c96dbbc3d954cf50cfc7bdead2d95de3f2910d783c263ef |
Hashes for scyllapy-1.1.1-cp38-abi3-musllinux_1_2_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | b0aa6e8aff8faed0f323ceb52e80d7a9a08f1f48e3d239dce1ac76f5d90385d6 |
|
MD5 | 59f1af318da5b83a0392c6f293afad1c |
|
BLAKE2b-256 | 7048c37c8b19ffd3cefcb1efe33097c903b7e78098884a140ae98f06730ff801 |
Hashes for scyllapy-1.1.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ce71280af3343b0d9d572cc8725b3ecbb6e4e0760ebdb1e6e077e97922c55d3e |
|
MD5 | f3821a3b09e388051b7c6cf905ca4d9f |
|
BLAKE2b-256 | e4066e55f82f7257eb4f243f50846f0d6c9172c763b857e9d761de9cfff730d0 |
Hashes for scyllapy-1.1.1-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7459718c5a3425eef759356d7596c51b701413b4e99a1b2775724e1cbbf7d6c5 |
|
MD5 | 6e42995a7876a7b20b0f97d270976a67 |
|
BLAKE2b-256 | 91d57746978786aa2ece5f27ec2511fcdb7fdd2c82bcb4a88d5c9b8f10536cfa |
Hashes for scyllapy-1.1.1-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 92c3fd9862252a3a97e61b84730040f0052c1111fa4c142458aad31da422c4aa |
|
MD5 | e05b9f902479f7dd95f29ab941790507 |
|
BLAKE2b-256 | 2a534b3d7cdc774b5caa8fc67ba2a8258483c18483818bd3c8d68f9998a6ed40 |
Hashes for scyllapy-1.1.1-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a57b34e9c86b8ca94a4d1d546255e62958506d4391885bbfe54691dfadc86c9b |
|
MD5 | 8bc9fecfe6ecf43b8c78ed155e090c98 |
|
BLAKE2b-256 | 658a321ab1803b1e10eaa582f3bf6aa9c75c196cef302ae782ded7d7f898bce3 |
Hashes for scyllapy-1.1.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8c6e55f6878623f9130cdebc808a94a16e256f5bf8387a137791fb8b514bd6f3 |
|
MD5 | 2abbf3f80040987ae1ad59595dcd3951 |
|
BLAKE2b-256 | 0d15041bea7c4a62d344eea37f238fa3b332154a2ad97c9b4a1eda3ea13ac488 |
Hashes for scyllapy-1.1.1-cp38-abi3-macosx_11_0_arm64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c12b443985f8b1846a8e388398a3ced094d53b866653eec0cb18f442578b0557 |
|
MD5 | 8b2474d0cd3ef280ece116d59471e2e3 |
|
BLAKE2b-256 | e0a8a984ad5eb7e0579410b6f1bcd5acb21793218ca584dad0560a4b97f81055 |
Hashes for scyllapy-1.1.1-cp38-abi3-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ac4c237fc13b49dffb19c41eff551bf52bd0416fbf04a3f0f33085690ad881f4 |
|
MD5 | 6b90271aee2cad4213b4f943ba3edde2 |
|
BLAKE2b-256 | a1e56ce44fa8e314644187b7b4a6c450dba6a7b239ca83a78498bbe56d402b26 |