Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9aa5ddc
Add optional query_params parameter to QueryMessage
sylwiaszunejko Mar 12, 2026
8bba6eb
Introduce skip_scylla_version_lt for integration tests
sylwiaszunejko Mar 13, 2026
bc864c1
Add client routes data types and route store
sylwiaszunejko Mar 17, 2026
ac91295
Add client routes handler for Private Link support
sylwiaszunejko Mar 17, 2026
1e0e6ca
Add ClientRoutesEndPoint and ClientRoutesEndPointFactory
sylwiaszunejko Mar 10, 2026
3202302
Integrate client routes handler into Cluster and ControlConnection
sylwiaszunejko Mar 10, 2026
b205f83
tests: add unit tests for client routes
sylwiaszunejko Mar 10, 2026
6743edd
tests: add integration tests for client routes
sylwiaszunejko Mar 11, 2026
efdc08a
Release 3.29.9: changelog, version and documentation
dkropachev Mar 18, 2026
fec90ae
Specify auth superuser name for tests (#759)
sylwiaszunejko Mar 23, 2026
153c913
(improvement) cqltypes: fast-path lookup_casstype() for simple type n…
mykaul Mar 6, 2026
70995bd
tests: remove redundant 10s sleep from setup_keyspace()
mykaul Mar 27, 2026
7931113
tests: replace high-priority time.sleep() calls with polling
mykaul Mar 27, 2026
4a23f72
tests: replace medium-priority time.sleep() calls with polling
mykaul Mar 27, 2026
9fe9931
tests: fix flaky tablet tests by increasing trace timeout and polling…
mykaul Mar 29, 2026
d31ea37
tests: replace fixed time.sleep() calls with polling (~17s saving)
mykaul Mar 29, 2026
e2a9511
Replace SCYLLA_EXT_OPTS env var with ccm updateconf options for auth …
sylwiaszunejko Mar 31, 2026
44cf752
chore(deps): update dependency pygments to v2.20.0 [security]
renovate[bot] Mar 30, 2026
d5f9d37
(fix) cluster: handle None control_connection_timeout in wait_for_sch…
mykaul Mar 26, 2026
94438c6
tests: fix flaky TestTwistedConnection.test_connection_initialization
mykaul Mar 26, 2026
4bff340
fix: correct 'clustering_key' to 'clustering' in column kind filter
mykaul Mar 24, 2026
ad12bed
chore(deps): update dependency tornado to v6.5.5 [security]
renovate[bot] Mar 30, 2026
c898583
metadata: conditionally skip triggers query for ScyllaDB
mykaul Dec 23, 2025
c3e2378
Fix code quality issues in test_cluster.py
Copilot Dec 23, 2025
8e6c4d4
Fix additional '== None' comparison for consistency
Copilot Dec 23, 2025
00813ef
(improvement) cache deserializer instances in find_deserializer and m…
mykaul Apr 3, 2026
4a7b199
Address review: bound caches, add clear API, fix benchmark imports
mykaul Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
3.29.9
======
March 18, 2026

Features
--------
* Add Private Link support via client routes handler
* Add optional query_params parameter to QueryMessage

Bug Fixes
---------
* Fix segmentation fault in libev prepare_callback during shutdown
* Add null checks to io_callback and timer_callback in libev wrapper
* Fix RecursionError in execute_concurrent on synchronous errbacks
* Fix floating-point precision loss for timestamps far from epoch

Others
------
* Cache parsed tablet routing type in ResponseFuture
* Remove deprecated setup_requires in favor of PEP 517 build-system.requires
* Update dependency hatchling to v1.29.0

3.29.8
======
February 09, 2026
Expand Down
212 changes: 212 additions & 0 deletions benchmarks/test_deserializer_cache_benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# Copyright ScyllaDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Benchmarks for find_deserializer / make_deserializers with and without caching.

Run with: pytest benchmarks/test_deserializer_cache_benchmark.py -v

Requires the ``pytest-benchmark`` plugin and Cython extensions to be built.
Skipped automatically when either dependency is unavailable.
"""

import pytest

pytest.importorskip("pytest_benchmark")
pytest.importorskip("cassandra.deserializers")

from cassandra import cqltypes
from cassandra.deserializers import (
find_deserializer,
make_deserializers,
)


# ---------------------------------------------------------------------------
# Reference: original uncached implementations (copied from master)
# ---------------------------------------------------------------------------

_classes = {}


def _init_classes():
"""Lazily initialize the class lookup dict from deserializers module."""
if not _classes:
from cassandra import deserializers as mod

for name in dir(mod):
obj = getattr(mod, name)
if isinstance(obj, type):
_classes[name] = obj


def find_deserializer_uncached(cqltype):
"""Original implementation without caching."""
_init_classes()

name = "Des" + cqltype.__name__
if name in _classes:
cls = _classes[name]
elif issubclass(cqltype, cqltypes.ListType):
from cassandra.deserializers import DesListType

cls = DesListType
elif issubclass(cqltype, cqltypes.SetType):
from cassandra.deserializers import DesSetType

cls = DesSetType
elif issubclass(cqltype, cqltypes.MapType):
from cassandra.deserializers import DesMapType

cls = DesMapType
elif issubclass(cqltype, cqltypes.UserType):
from cassandra.deserializers import DesUserType

cls = DesUserType
elif issubclass(cqltype, cqltypes.TupleType):
from cassandra.deserializers import DesTupleType

cls = DesTupleType
elif issubclass(cqltype, cqltypes.DynamicCompositeType):
from cassandra.deserializers import DesDynamicCompositeType

cls = DesDynamicCompositeType
elif issubclass(cqltype, cqltypes.CompositeType):
from cassandra.deserializers import DesCompositeType

cls = DesCompositeType
elif issubclass(cqltype, cqltypes.ReversedType):
from cassandra.deserializers import DesReversedType

cls = DesReversedType
elif issubclass(cqltype, cqltypes.FrozenType):
from cassandra.deserializers import DesFrozenType

cls = DesFrozenType
else:
from cassandra.deserializers import GenericDeserializer

cls = GenericDeserializer

return cls(cqltype)


def make_deserializers_uncached(ctypes):
"""Original implementation without caching."""
from cassandra.deserializers import obj_array

return obj_array([find_deserializer_uncached(ct) for ct in ctypes])


# ---------------------------------------------------------------------------
# Test type sets
# ---------------------------------------------------------------------------

SIMPLE_TYPES = [
cqltypes.Int32Type,
cqltypes.UTF8Type,
cqltypes.BooleanType,
cqltypes.DoubleType,
cqltypes.LongType,
]

MIXED_TYPES = [
cqltypes.Int32Type,
cqltypes.UTF8Type,
cqltypes.BooleanType,
cqltypes.DoubleType,
cqltypes.LongType,
cqltypes.FloatType,
cqltypes.TimestampType,
cqltypes.UUIDType,
cqltypes.InetAddressType,
cqltypes.DecimalType,
]


# ---------------------------------------------------------------------------
# Correctness tests
# ---------------------------------------------------------------------------


class TestDeserializerCacheCorrectness:
"""Verify the cached implementation returns equivalent deserializers."""

@pytest.mark.parametrize("cqltype", SIMPLE_TYPES + MIXED_TYPES)
def test_find_deserializer_returns_correct_type(self, cqltype):
cached = find_deserializer(cqltype)
uncached = find_deserializer_uncached(cqltype)
assert type(cached).__name__ == type(uncached).__name__

def test_find_deserializer_cache_hit_same_object(self):
d1 = find_deserializer(cqltypes.Int32Type)
d2 = find_deserializer(cqltypes.Int32Type)
assert d1 is d2

def test_make_deserializers_returns_correct_length(self):
result = make_deserializers(SIMPLE_TYPES)
assert len(result) == len(SIMPLE_TYPES)

def test_make_deserializers_cache_hit_same_object(self):
r1 = make_deserializers(SIMPLE_TYPES)
r2 = make_deserializers(SIMPLE_TYPES)
# Should be the exact same cached object
assert r1 is r2


# ---------------------------------------------------------------------------
# Benchmarks
# ---------------------------------------------------------------------------


class TestFindDeserializerBenchmark:
"""Benchmark find_deserializer cached vs uncached."""

# --- Single simple type ---

@pytest.mark.benchmark(group="find_deser_simple")
def test_uncached_simple(self, benchmark):
benchmark(find_deserializer_uncached, cqltypes.Int32Type)

@pytest.mark.benchmark(group="find_deser_simple")
def test_cached_simple(self, benchmark):
# Cache is already warm from correctness tests or previous iterations
find_deserializer(cqltypes.Int32Type) # ensure warm
benchmark(find_deserializer, cqltypes.Int32Type)


class TestMakeDeserializersBenchmark:
"""Benchmark make_deserializers cached vs uncached."""

# --- 5 simple types ---

@pytest.mark.benchmark(group="make_deser_5types")
def test_uncached_5types(self, benchmark):
benchmark(make_deserializers_uncached, SIMPLE_TYPES)

@pytest.mark.benchmark(group="make_deser_5types")
def test_cached_5types(self, benchmark):
make_deserializers(SIMPLE_TYPES) # ensure warm
benchmark(make_deserializers, SIMPLE_TYPES)

# --- 10 mixed types ---

@pytest.mark.benchmark(group="make_deser_10types")
def test_uncached_10types(self, benchmark):
benchmark(make_deserializers_uncached, MIXED_TYPES)

@pytest.mark.benchmark(group="make_deser_10types")
def test_cached_10types(self, benchmark):
make_deserializers(MIXED_TYPES) # ensure warm
benchmark(make_deserializers, MIXED_TYPES)
2 changes: 1 addition & 1 deletion cassandra/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def emit(self, record):

logging.getLogger('cassandra').addHandler(NullHandler())

__version_info__ = (3, 29, 8)
__version_info__ = (3, 29, 9)
__version__ = '.'.join(map(str, __version_info__))


Expand Down
Loading
Loading