Skip to content

Commit

Permalink
Merge branch 'release/1.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
s3rius committed Sep 1, 2023
2 parents b0198a4 + a06c896 commit e6ac00e
Show file tree
Hide file tree
Showing 27 changed files with 1,927 additions and 125 deletions.
10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "scyllapy"
version = "1.0.7"
version = "1.1.0"
edition = "2021"

[lib]
Expand All @@ -14,7 +14,13 @@ chrono = "0.4.26"
eq-float = "0.1.0"
log = "0.4.20"
openssl = { version = "0.10.56", features = ["vendored"] }
pyo3 = { version = "0.19.2", features = ["auto-initialize", "anyhow", "abi3-py38", "extension-module", "chrono"] }
pyo3 = { version = "0.19.2", features = [
"auto-initialize",
"anyhow",
"abi3-py38",
"extension-module",
"chrono",
] }
pyo3-asyncio = { version = "0.19.0", features = ["tokio-runtime"] }
pyo3-log = "0.8.3"
scylla = { version = "0.9.0", features = ["ssl"] }
Expand Down
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,71 @@ async def execute(scylla: Scylla) -> None:
"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:

```python
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.

```python
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)

```
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ classifiers = [
[tool.maturin]
python-source = "python"
module-name = "scyllapy._internal"
features = ["pyo3/extension-module"]

[build-system]
requires = ["maturin>=1.0,<2.0"]
Expand Down
7 changes: 6 additions & 1 deletion python/scyllapy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
PreparedQuery,
Batch,
BatchType,
extra_types,
QueryResult,
InlineBatch,
)

from importlib.metadata import version

__version__ = version("scyllapy")
Expand All @@ -21,5 +23,8 @@
"PreparedQuery",
"Batch",
"BatchType",
"QueryResult",
"extra_types",
"InlineBatch",
"query_builder",
]
25 changes: 18 additions & 7 deletions python/scyllapy/_internal/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class Scylla:
"""
async def batch(
self,
batch: Batch,
batch: Batch | InlineBatch,
params: Optional[Iterable[Iterable[Any] | dict[str, Any]]] = None,
) -> QueryResult:
"""
Expand Down Expand Up @@ -166,12 +166,6 @@ class BatchType:
class Batch:
"""Class for batching queries together."""

consistency: Consistency | None
serial_consistency: SerialConsistency | None
request_timeout: int | None
is_idempotent: bool | None
tracing: bool | None

def __init__(
self,
batch_type: BatchType = BatchType.UNLOGGED,
Expand All @@ -184,6 +178,23 @@ class Batch:
) -> None: ...
def add_query(self, query: Query | PreparedQuery | str) -> None: ...

class InlineBatch:
def __init__(
self,
batch_type: BatchType = BatchType.UNLOGGED,
consistency: Consistency | None = None,
serial_consistency: SerialConsistency | None = None,
request_timeout: int | None = None,
timestamp: int | None = None,
is_idempotent: bool | None = None,
tracing: bool | None = None,
) -> None: ...
def add_query(
self,
query: Query | PreparedQuery | str,
values: list[Any] | None = None,
) -> None: ...

class Consistency:
"""Consistency for query."""

Expand Down
94 changes: 94 additions & 0 deletions python/scyllapy/_internal/query_builder.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from typing import Any

from scyllapy._internal import (
Consistency,
SerialConsistency,
Scylla,
QueryResult,
InlineBatch,
)

class Select:
def __init__(self, table: str) -> None: ...
def only(self, *columns: str) -> Select: ...
def where(self, clause: str, params: list[Any] | None = None) -> Select: ...
def group_by(self, group: str) -> Select: ...
def order_by(self, order: str, desc: bool = False) -> Select: ...
def per_partition_limit(self, per_partition_limit: int) -> Select: ...
def limit(self, limit: int) -> Select: ...
def allow_filtering(self) -> Select: ...
def distinct(self) -> Select: ...
def timeout(self, timeout: int | str) -> Select: ...
def request_params(
self,
consistency: Consistency | None = None,
serial_consistency: SerialConsistency | None = None,
request_timeout: int | None = None,
timestamp: int | None = None,
is_idempotent: bool | None = None,
tracing: bool | None = None,
) -> Select: ...
def add_to_batch(self, batch: InlineBatch) -> None: ...
async def execute(self, scylla: Scylla) -> QueryResult: ...

class Insert:
def __init__(self, table: str) -> None: ...
def if_not_exists(self) -> Insert: ...
def set(self, name: str, value: Any) -> Insert: ...
def timeout(self, timeout: int | str) -> Insert: ...
def timestamp(self, timestamp: int) -> Insert: ...
def ttl(self, ttl: int) -> Insert: ...
def request_params(
self,
consistency: Consistency | None = None,
serial_consistency: SerialConsistency | None = None,
request_timeout: int | None = None,
timestamp: int | None = None,
is_idempotent: bool | None = None,
tracing: bool | None = None,
) -> Insert: ...
def add_to_batch(self, batch: InlineBatch) -> None: ...
async def execute(self, scylla: Scylla) -> QueryResult: ...

class Delete:
def __init__(self, table: str) -> None: ...
def cols(self, *cols: str) -> Delete: ...
def where(self, clause: str, values: list[Any] | None = None) -> Delete: ...
def timeout(self, timeout: int | str) -> Delete: ...
def timestamp(self, timestamp: int) -> Delete: ...
def if_exists(self) -> Delete: ...
def if_(self, clause: str, values: list[Any] | None = None) -> Delete: ...
def request_params(
self,
consistency: Consistency | None = None,
serial_consistency: SerialConsistency | None = None,
request_timeout: int | None = None,
timestamp: int | None = None,
is_idempotent: bool | None = None,
tracing: bool | None = None,
) -> Delete: ...
def add_to_batch(self, batch: InlineBatch) -> None: ...
async def execute(self, scylla: Scylla) -> QueryResult: ...

class Update:
def __init__(self, table: str) -> None: ...
def set(self, name: str, value: Any) -> Update: ...
def inc(self, column: str, value: Any) -> Update: ...
def dec(self, column: str, value: Any) -> Update: ...
def where(self, clause: str, values: list[Any] | None = None) -> Update: ...
def timeout(self, timeout: int | str) -> Update: ...
def timestamp(self, timestamp: int) -> Update: ...
def ttl(self, ttl: int) -> Update: ...
def request_params(
self,
consistency: Consistency | None = None,
serial_consistency: SerialConsistency | None = None,
request_timeout: int | None = None,
timestamp: int | None = None,
is_idempotent: bool | None = None,
tracing: bool | None = None,
) -> Update: ...
def if_exists(self) -> Update: ...
def if_(self, clause: str, values: list[Any] | None = None) -> Update: ...
def add_to_batch(self, batch: InlineBatch) -> None: ...
async def execute(self, scylla: Scylla) -> QueryResult: ...
3 changes: 3 additions & 0 deletions python/scyllapy/extra_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from ._internal.extra_types import BigInt, Counter, Double, SmallInt, TinyInt, Unset

__all__ = ["BigInt", "Counter", "Double", "SmallInt", "TinyInt", "Unset"]
3 changes: 3 additions & 0 deletions python/scyllapy/query_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from ._internal.query_builder import Select, Delete, Insert, Update

__all__ = ["Select", "Delete", "Insert", "Update"]
File renamed without changes.
56 changes: 56 additions & 0 deletions python/tests/query_builders/test_delete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import pytest
from scyllapy import Scylla
from scyllapy.query_builder import Delete
from tests.utils import random_string


@pytest.mark.anyio
async def test_success(scylla: Scylla) -> None:
table_name = random_string(4)
await scylla.execute(f"CREATE TABLE {table_name} (id INT PRIMARY KEY, name TEXT)")
await scylla.execute(
f"INSERT INTO {table_name}(id, name) VALUES (?, ?)", [1, "meme"]
)
await Delete(table_name).where("id = ?", [1]).execute(scylla)
res = await scylla.execute(f"SELECT * FROM {table_name}")
assert not res.all()


@pytest.mark.anyio
async def test_if_exists(scylla: Scylla) -> None:
table_name = random_string(4)
await scylla.execute(f"CREATE TABLE {table_name} (id INT PRIMARY KEY, name TEXT)")
await scylla.execute(
f"INSERT INTO {table_name}(id, name) VALUES (?, ?)", [1, "meme"]
)
await Delete(table_name).where("id = ?", [1]).if_exists().execute(scylla)
res = await scylla.execute(f"SELECT * FROM {table_name}")
assert not res.all()


@pytest.mark.anyio
async def test_custom_if(scylla: Scylla) -> None:
table_name = random_string(4)
await scylla.execute(f"CREATE TABLE {table_name} (id INT PRIMARY KEY, name TEXT)")
await scylla.execute(
f"INSERT INTO {table_name}(id, name) VALUES (?, ?)", [1, "meme"]
)
await Delete(table_name).where("id = ?", [1]).if_("name != ?", [None]).execute(
scylla
)
res = await scylla.execute(f"SELECT * FROM {table_name}")
assert not res.all()


@pytest.mark.anyio
async def test_custom_custom_if(scylla: Scylla) -> None:
table_name = random_string(4)
await scylla.execute(f"CREATE TABLE {table_name} (id INT PRIMARY KEY, name TEXT)")
await scylla.execute(
f"INSERT INTO {table_name}(id, name) VALUES (?, ?)", [1, "meme"]
)
await Delete(table_name).where("id = ?", [1]).if_("name != ?", [None]).execute(
scylla
)
res = await scylla.execute(f"SELECT * FROM {table_name}")
assert not res.all()
43 changes: 43 additions & 0 deletions python/tests/query_builders/test_inserts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import pytest
from scyllapy import Scylla
from scyllapy.query_builder import Insert
from tests.utils import random_string


@pytest.mark.anyio
async def test_insert_success(scylla: Scylla) -> None:
table_name = random_string(4)
await scylla.execute(f"CREATE TABLE {table_name} (id INT PRIMARY KEY, name TEXT)")
await Insert(table_name).set("id", 1).set("name", "random").execute(scylla)
result = await scylla.execute(f"SELECT * FROM {table_name}")
assert result.all() == [{"id": 1, "name": "random"}]


@pytest.mark.anyio
async def test_insert_if_not_exists(scylla: Scylla) -> None:
table_name = random_string(4)
await scylla.execute(f"CREATE TABLE {table_name} (id INT PRIMARY KEY, name TEXT)")
await Insert(table_name).set("id", 1).set("name", "random").execute(scylla)
await Insert(table_name).set("id", 1).set(
"name",
"random2",
).if_not_exists().execute(scylla)
res = await scylla.execute(f"SELECT * FROM {table_name}")
assert res.all() == [{"id": 1, "name": "random"}]


@pytest.mark.anyio
async def test_insert_request_params(scylla: Scylla) -> None:
table_name = random_string(4)
await scylla.execute(f"CREATE TABLE {table_name} (id INT PRIMARY KEY, name TEXT)")
await Insert(table_name).set("id", 1).set("name", "random").execute(scylla)
res = (
await Insert(table_name)
.set("id", 1)
.set("name", "random2")
.request_params(
tracing=True,
)
.execute(scylla)
)
assert res.trace_id
Loading

0 comments on commit e6ac00e

Please sign in to comment.