Skip to content

Commit 941424e

Browse files
committed
(improvement) serializers: address PR review feedback
- Chain original exception in _raise_bind_serialize_error (raise from exc) - Change _check_int32_range to raise struct.error instead of OverflowError, matching the behaviour of struct.pack('>i', value) - Clarify docstrings for _check_float_range/_check_int32_range - Expand _raise_bind_serialize_error docstring with specific exception types - Document __getitem__ requirement in vector serialize methods - Move io and uvint_pack imports to module scope in serializers.pyx - Add struct import to serializers.pyx for struct.error - Fix test_plain_path_overflow_error_wrapped docstring (struct.error, not OverflowError) - Update OverflowSerializer stub to raise struct.error - Replace name-mangled __serializers with _cached_serializers
1 parent 2472724 commit 941424e

2 files changed

Lines changed: 28 additions & 18 deletions

File tree

cassandra/query.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -494,15 +494,15 @@ def _serializers(self):
494494
if self.column_encryption_policy:
495495
return None
496496
try:
497-
return self.__serializers
497+
return self._cached_serializers
498498
except AttributeError:
499499
pass
500500
if _HAVE_CYTHON_SERIALIZERS and self.column_metadata:
501-
self.__serializers = _cython_make_serializers(
501+
self._cached_serializers = _cython_make_serializers(
502502
[col.type for col in self.column_metadata])
503503
else:
504-
self.__serializers = None
505-
return self.__serializers
504+
self._cached_serializers = None
505+
return self._cached_serializers
506506

507507
@classmethod
508508
def from_message(cls, query_id, column_metadata, pk_indexes, cluster_metadata,
@@ -563,11 +563,19 @@ def __str__(self):
563563

564564

565565
def _raise_bind_serialize_error(col_spec, value, exc):
566-
"""Wrap serialization errors with column context for all bind loop paths."""
566+
"""Wrap TypeError, struct.error, or OverflowError with column context.
567+
568+
Called from all three bind loop paths (CE, Cython, plain Python) to
569+
provide a uniform error message that includes the column name and
570+
expected type. struct.error arises from int32 out-of-range values;
571+
OverflowError from float out-of-range values. Other exception types
572+
(e.g. ValueError from VectorType dimension mismatch) propagate
573+
without wrapping.
574+
"""
567575
actual_type = type(value)
568576
message = ('Received an argument of invalid type for column "%s". '
569577
'Expected: %s, Got: %s; (%s)' % (col_spec.name, col_spec.type, actual_type, exc))
570-
raise TypeError(message)
578+
raise TypeError(message) from exc
571579

572580

573581
class BoundStatement(Statement):
@@ -698,7 +706,7 @@ def bind(self, values):
698706
else:
699707
col_bytes = col_spec.type.serialize(value, proto_version)
700708
self.values[idx] = col_bytes
701-
# OverflowError: Cython int32/float casts may raise on out-of-range values
709+
# struct.error: int32 out-of-range; OverflowError: float out-of-range
702710
except (TypeError, struct.error, OverflowError) as exc:
703711
_raise_bind_serialize_error(col_spec, value, exc)
704712
idx += 1
@@ -719,7 +727,7 @@ def bind(self, values):
719727
try:
720728
col_bytes = ser.serialize(value, proto_version)
721729
self.values[idx] = col_bytes
722-
# OverflowError: Cython int32/float casts may raise on out-of-range values
730+
# struct.error: int32 out-of-range; OverflowError: float out-of-range
723731
except (TypeError, struct.error, OverflowError) as exc:
724732
_raise_bind_serialize_error(col_spec, value, exc)
725733
idx += 1
@@ -737,7 +745,7 @@ def bind(self, values):
737745
try:
738746
col_bytes = col_spec.type.serialize(value, proto_version)
739747
self.values[idx] = col_bytes
740-
# OverflowError: Cython int32/float casts may raise on out-of-range values
748+
# struct.error: int32 out-of-range; OverflowError: float out-of-range
741749
except (TypeError, struct.error, OverflowError) as exc:
742750
_raise_bind_serialize_error(col_spec, value, exc)
743751
idx += 1

tests/unit/test_parameter_binding.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import unittest
16+
import struct
1617
import pytest
1718

1819
from cassandra.encoder import Encoder
@@ -229,20 +230,20 @@ def serialize(self, value, protocol_version):
229230

230231

231232
class OverflowSerializer:
232-
"""Stub that raises OverflowError, mimicking Cython <int32_t> cast overflow."""
233+
"""Stub that raises struct.error, mimicking Cython int32 range check."""
233234

234235
def __init__(self, cqltype):
235236
self.cqltype = cqltype
236237

237238
def serialize(self, value, protocol_version):
238-
raise OverflowError('value too large to convert to int32_t')
239+
raise struct.error("'i' format requires -2147483648 <= number <= 2147483647")
239240

240241

241242
class CythonBindPathTest(unittest.TestCase):
242243
"""Tests for the Cython serializer fast path in BoundStatement.bind().
243244
244245
These tests inject stub serializers via the PreparedStatement's cached
245-
__serializers attribute to exercise the Cython bind branch without
246+
_cached_serializers attribute to exercise the Cython bind branch without
246247
requiring compiled Cython.
247248
"""
248249

@@ -258,9 +259,9 @@ def _make_prepared(self, column_metadata, serializers=None):
258259
protocol_version=self.protocol_version,
259260
result_metadata=None,
260261
result_metadata_id=None)
261-
# Inject directly into the name-mangled cache attribute used by
262-
# the _serializers property, bypassing the lazy initialization.
263-
prepared._PreparedStatement__serializers = serializers
262+
# Inject directly into the cache attribute used by the _serializers
263+
# property, bypassing the lazy initialization.
264+
prepared._cached_serializers = serializers
264265
return prepared
265266

266267
def test_cython_path_normal_serialization(self):
@@ -298,7 +299,7 @@ def test_cython_path_unset_value(self):
298299
assert bound.values[1] == UNSET_VALUE
299300

300301
def test_cython_path_overflow_error_wrapped(self):
301-
"""OverflowError from Cython cast is caught and wrapped with column context."""
302+
"""struct.error from Cython int32 range check is caught and wrapped with column context."""
302303
column_metadata = [ColumnMetadata('keyspace', 'cf', 'v0', Int32Type)]
303304
serializers = [OverflowSerializer(Int32Type)]
304305
prepared = self._make_prepared(column_metadata, serializers)
@@ -325,7 +326,8 @@ def test_cython_path_type_error_wrapped(self):
325326
assert 'Int32Type' in msg
326327

327328
def test_plain_path_overflow_error_wrapped(self):
328-
"""OverflowError in the plain Python path is also caught and wrapped."""
329+
"""Out-of-range int in the plain Python path raises struct.error (caught
330+
alongside OverflowError) and is wrapped with column context."""
329331
column_metadata = [ColumnMetadata('keyspace', 'cf', 'v0', Int32Type)]
330332
# Force the plain Python path (no Cython serializers)
331333
prepared = self._make_prepared(column_metadata, serializers=None)
@@ -356,7 +358,7 @@ def _make_prepared(self, column_metadata, serializers=None, routing_key_indexes=
356358
protocol_version=self.protocol_version,
357359
result_metadata=None,
358360
result_metadata_id=None)
359-
prepared._PreparedStatement__serializers = serializers
361+
prepared._cached_serializers = serializers
360362
return prepared
361363

362364
def _three_column_metadata(self):

0 commit comments

Comments
 (0)