From 2fd80a58d665abc3927ee1929a45ae73bb18301d Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 3 Apr 2026 19:36:12 +0300 Subject: [PATCH 01/11] remove: delete 'from __future__ import absolute_import' from 5 files absolute_import is the default behavior in Python 3 and has been since Python 3.0. The import was only needed for Python 2 compatibility. --- cassandra/cluster.py | 1 - cassandra/connection.py | 1 - cassandra/cqltypes.py | 1 - cassandra/protocol.py | 1 - tests/integration/cqlengine/query/test_queryset.py | 1 - 5 files changed, 5 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 9eace8810d..ffef70123d 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -16,7 +16,6 @@ This module houses the main classes you will interact with, :class:`.Cluster` and :class:`.Session`. """ -from __future__ import absolute_import import atexit import datetime diff --git a/cassandra/connection.py b/cassandra/connection.py index c045b36cb3..52e676bae0 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import absolute_import # to enable import io from stdlib from collections import defaultdict, deque import errno from functools import wraps, partial, total_ordering diff --git a/cassandra/cqltypes.py b/cassandra/cqltypes.py index 547a13c979..81e389fa97 100644 --- a/cassandra/cqltypes.py +++ b/cassandra/cqltypes.py @@ -27,7 +27,6 @@ # for example), these classes would be a good place to tack on # .from_cql_literal() and .as_cql_literal() classmethods (or whatever). -from __future__ import absolute_import # to enable import io from stdlib import ast from binascii import unhexlify import calendar diff --git a/cassandra/protocol.py b/cassandra/protocol.py index 4628c7ee0e..f3f0a82212 100644 --- a/cassandra/protocol.py +++ b/cassandra/protocol.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import absolute_import # to enable import io from stdlib from collections import namedtuple import logging import socket diff --git a/tests/integration/cqlengine/query/test_queryset.py b/tests/integration/cqlengine/query/test_queryset.py index 34b4ab5964..a4420e8283 100644 --- a/tests/integration/cqlengine/query/test_queryset.py +++ b/tests/integration/cqlengine/query/test_queryset.py @@ -11,7 +11,6 @@ # 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. -from __future__ import absolute_import import unittest From 0a074df8bb0c8450ac92429b83f1988d0a478481 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 3 Apr 2026 19:37:11 +0300 Subject: [PATCH 02/11] remove: delete UnicodeMixin class and all references UnicodeMixin was a Python 2 compatibility shim that made __str__ delegate to __unicode__. In Python 3, __str__ is the native method and UnicodeMixin serves no purpose. Affected classes: QueryValue, BaseQueryOperator, AbstractQueryableColumn, ValueQuoter, BaseClause, BaseCQLStatement. --- cassandra/cqlengine/__init__.py | 3 --- cassandra/cqlengine/functions.py | 4 ++-- cassandra/cqlengine/operators.py | 3 +-- cassandra/cqlengine/query.py | 4 ++-- cassandra/cqlengine/statements.py | 7 +++---- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/cassandra/cqlengine/__init__.py b/cassandra/cqlengine/__init__.py index b9466e961b..8f47a238f8 100644 --- a/cassandra/cqlengine/__init__.py +++ b/cassandra/cqlengine/__init__.py @@ -26,6 +26,3 @@ class CQLEngineException(Exception): class ValidationError(CQLEngineException): pass - -class UnicodeMixin(object): - __str__ = lambda x: x.__unicode__() diff --git a/cassandra/cqlengine/functions.py b/cassandra/cqlengine/functions.py index 606f5bc330..b24a8cc1b2 100644 --- a/cassandra/cqlengine/functions.py +++ b/cassandra/cqlengine/functions.py @@ -14,12 +14,12 @@ from datetime import datetime -from cassandra.cqlengine import UnicodeMixin, ValidationError +from cassandra.cqlengine import ValidationError def get_total_seconds(td): return td.total_seconds() -class QueryValue(UnicodeMixin): +class QueryValue: """ Base class for query filter values. Subclasses of these classes can be passed into .filter() keyword args diff --git a/cassandra/cqlengine/operators.py b/cassandra/cqlengine/operators.py index 2adf51758d..10a428e1c0 100644 --- a/cassandra/cqlengine/operators.py +++ b/cassandra/cqlengine/operators.py @@ -12,14 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from cassandra.cqlengine import UnicodeMixin class QueryOperatorException(Exception): pass -class BaseQueryOperator(UnicodeMixin): +class BaseQueryOperator: # The symbol that identifies this operator in kwargs # ie: colname__ symbol = None diff --git a/cassandra/cqlengine/query.py b/cassandra/cqlengine/query.py index afc7ceeef6..e98e20c7b2 100644 --- a/cassandra/cqlengine/query.py +++ b/cassandra/cqlengine/query.py @@ -19,7 +19,7 @@ from warnings import warn from cassandra.query import SimpleStatement, BatchType as CBatchType, BatchStatement -from cassandra.cqlengine import columns, CQLEngineException, ValidationError, UnicodeMixin +from cassandra.cqlengine import columns, CQLEngineException, ValidationError from cassandra.cqlengine import connection as conn from cassandra.cqlengine.functions import Token, BaseQueryFunction, QueryValue from cassandra.cqlengine.operators import (InOperator, EqualsOperator, GreaterThanOperator, @@ -78,7 +78,7 @@ def check_applied(result): raise LWTException(result.one()) -class AbstractQueryableColumn(UnicodeMixin): +class AbstractQueryableColumn: """ exposes cql query operators through pythons builtin comparator symbols diff --git a/cassandra/cqlengine/statements.py b/cassandra/cqlengine/statements.py index 4782fdccd8..96ca187d4a 100644 --- a/cassandra/cqlengine/statements.py +++ b/cassandra/cqlengine/statements.py @@ -17,7 +17,6 @@ from cassandra.query import FETCH_SIZE_UNSET from cassandra.cqlengine import columns -from cassandra.cqlengine import UnicodeMixin from cassandra.cqlengine.functions import QueryValue from cassandra.cqlengine.operators import BaseWhereOperator, InOperator, EqualsOperator, IsNotNullOperator @@ -26,7 +25,7 @@ class StatementException(Exception): pass -class ValueQuoter(UnicodeMixin): +class ValueQuoter: def __init__(self, value): self.value = value @@ -54,7 +53,7 @@ def __unicode__(self): return '(' + ', '.join([cql_quote(v) for v in self.value]) + ')' -class BaseClause(UnicodeMixin): +class BaseClause: def __init__(self, field, value): self.field = field @@ -500,7 +499,7 @@ def __unicode__(self): return ', '.join(['"{0}"[%({1})s]'.format(self.field, self.context_id + i) for i in range(len(self._removals))]) -class BaseCQLStatement(UnicodeMixin): +class BaseCQLStatement: """ The base cql statement class """ def __init__(self, table, timestamp=None, where=None, fetch_size=None, conditionals=None): From a4470c23b378dbb7b71a3f0e63a748d8f1f0b988 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 3 Apr 2026 19:38:21 +0300 Subject: [PATCH 03/11] refactor: rename __unicode__ to __str__ across cqlengine In Python 3, __str__ is the native string method. The __unicode__ methods were a Python 2 convention used together with UnicodeMixin. Affected files: statements.py, operators.py, named.py, models.py, query.py, functions.py. --- cassandra/cqlengine/functions.py | 4 ++-- cassandra/cqlengine/models.py | 2 +- cassandra/cqlengine/named.py | 2 +- cassandra/cqlengine/operators.py | 2 +- cassandra/cqlengine/query.py | 7 ++---- cassandra/cqlengine/statements.py | 38 +++++++++++++++---------------- 6 files changed, 26 insertions(+), 29 deletions(-) diff --git a/cassandra/cqlengine/functions.py b/cassandra/cqlengine/functions.py index b24a8cc1b2..d826a8b4d3 100644 --- a/cassandra/cqlengine/functions.py +++ b/cassandra/cqlengine/functions.py @@ -31,7 +31,7 @@ def __init__(self, value): self.value = value self.context_id = None - def __unicode__(self): + def __str__(self): return self.format_string.format(self.context_id) def set_context_id(self, ctx_id): @@ -109,7 +109,7 @@ def set_columns(self, columns): def get_context_size(self): return len(self.value) - def __unicode__(self): + def __str__(self): token_args = ', '.join('%({0})s'.format(self.context_id + i) for i in range(self.get_context_size())) return "token({0})".format(token_args) diff --git a/cassandra/cqlengine/models.py b/cassandra/cqlengine/models.py index bc00001666..b3d48013d9 100644 --- a/cassandra/cqlengine/models.py +++ b/cassandra/cqlengine/models.py @@ -272,7 +272,7 @@ class ColumnQueryEvaluator(query.AbstractQueryableColumn): def __init__(self, column): self.column = column - def __unicode__(self): + def __str__(self): return self.column.db_field_name def _get_column(self): diff --git a/cassandra/cqlengine/named.py b/cassandra/cqlengine/named.py index 265d5c91e4..f9bcd76d1d 100644 --- a/cassandra/cqlengine/named.py +++ b/cassandra/cqlengine/named.py @@ -52,7 +52,7 @@ class NamedColumn(AbstractQueryableColumn): def __init__(self, name): self.name = name - def __unicode__(self): + def __str__(self): return self.name def _get_column(self): diff --git a/cassandra/cqlengine/operators.py b/cassandra/cqlengine/operators.py index 10a428e1c0..2a21e23f23 100644 --- a/cassandra/cqlengine/operators.py +++ b/cassandra/cqlengine/operators.py @@ -26,7 +26,7 @@ class BaseQueryOperator: # The comparator symbol this operator uses in cql cql_symbol = None - def __unicode__(self): + def __str__(self): if self.cql_symbol is None: raise QueryOperatorException("cql symbol is None") return self.cql_symbol diff --git a/cassandra/cqlengine/query.py b/cassandra/cqlengine/query.py index e98e20c7b2..74cc07f769 100644 --- a/cassandra/cqlengine/query.py +++ b/cassandra/cqlengine/query.py @@ -87,7 +87,7 @@ class AbstractQueryableColumn: def _get_column(self): raise NotImplementedError - def __unicode__(self): + def __str__(self): raise NotImplementedError def _to_database(self, val): @@ -405,11 +405,8 @@ def _execute(self, statement): check_applied(result) return result - def __unicode__(self): - return str(self._select_query()) - def __str__(self): - return str(self.__unicode__()) + return str(self._select_query()) def __call__(self, *args, **kwargs): return self.filter(*args, **kwargs) diff --git a/cassandra/cqlengine/statements.py b/cassandra/cqlengine/statements.py index 96ca187d4a..32c565f982 100644 --- a/cassandra/cqlengine/statements.py +++ b/cassandra/cqlengine/statements.py @@ -30,7 +30,7 @@ class ValueQuoter: def __init__(self, value): self.value = value - def __unicode__(self): + def __str__(self): from cassandra.encoder import cql_quote if isinstance(self.value, (list, tuple)): return '[' + ', '.join([cql_quote(v) for v in self.value]) + ']' @@ -48,7 +48,7 @@ def __eq__(self, other): class InQuoter(ValueQuoter): - def __unicode__(self): + def __str__(self): from cassandra.encoder import cql_quote return '(' + ', '.join([cql_quote(v) for v in self.value]) + ')' @@ -60,7 +60,7 @@ def __init__(self, field, value): self.value = value self.context_id = None - def __unicode__(self): + def __str__(self): raise NotImplementedError def __hash__(self): @@ -109,7 +109,7 @@ def __init__(self, field, operator, value, quote_field=True): self.query_value = self.value if isinstance(self.value, QueryValue) else QueryValue(self.value) self.quote_field = quote_field - def __unicode__(self): + def __str__(self): field = ('"{0}"' if self.quote_field else '{0}').format(self.field) return u'{0} {1} {2}'.format(field, self.operator, str(self.query_value)) @@ -139,7 +139,7 @@ class IsNotNullClause(WhereClause): def __init__(self, field): super(IsNotNullClause, self).__init__(field, IsNotNullOperator(), '') - def __unicode__(self): + def __str__(self): field = ('"{0}"' if self.quote_field else '{0}').format(self.field) return u'{0} {1}'.format(field, self.operator) @@ -156,7 +156,7 @@ def get_context_size(self): class AssignmentClause(BaseClause): """ a single variable st statement """ - def __unicode__(self): + def __str__(self): return u'"{0}" = %({1})s'.format(self.field, self.context_id) def insert_tuple(self): @@ -166,7 +166,7 @@ def insert_tuple(self): class ConditionalClause(BaseClause): """ A single variable iff statement """ - def __unicode__(self): + def __str__(self): return u'"{0}" = %({1})s'.format(self.field, self.context_id) def insert_tuple(self): @@ -210,7 +210,7 @@ class SetUpdateClause(ContainerUpdateClause): _additions = None _removals = None - def __unicode__(self): + def __str__(self): qs = [] ctx_id = self.context_id if (self.previous is None and @@ -282,7 +282,7 @@ class ListUpdateClause(ContainerUpdateClause): _append = None _prepend = None - def __unicode__(self): + def __str__(self): if not self._analyzed: self._analyze() qs = [] @@ -411,7 +411,7 @@ def is_assignment(self): self._analyze() return self.previous is None and not self._updates and not self._removals - def __unicode__(self): + def __str__(self): qs = [] ctx_id = self.context_id @@ -442,7 +442,7 @@ def get_context_size(self): def update_context(self, ctx): ctx[str(self.context_id)] = abs(self.value - self.previous) - def __unicode__(self): + def __str__(self): delta = self.value - self.previous sign = '-' if delta < 0 else '+' return '"{0}" = "{0}" {1} %({2})s'.format(self.field, sign, self.context_id) @@ -458,7 +458,7 @@ class FieldDeleteClause(BaseDeleteClause): def __init__(self, field): super(FieldDeleteClause, self).__init__(field, None) - def __unicode__(self): + def __str__(self): return '"{0}"'.format(self.field) def update_context(self, ctx): @@ -493,7 +493,7 @@ def get_context_size(self): self._analyze() return len(self._removals) - def __unicode__(self): + def __str__(self): if not self._analyzed: self._analyze() return ', '.join(['"{0}"[%({1})s]'.format(self.field, self.context_id + i) for i in range(len(self._removals))]) @@ -590,11 +590,11 @@ def timestamp_normalized(self): return int(time.mktime(tmp.timetuple()) * 1e+6 + tmp.microsecond) - def __unicode__(self): + def __str__(self): raise NotImplementedError def __repr__(self): - return self.__unicode__() + return self.__str__() @property def _where(self): @@ -632,7 +632,7 @@ def __init__(self, self.limit = limit self.allow_filtering = allow_filtering - def __unicode__(self): + def __str__(self): qs = ['SELECT'] if self.distinct_fields: if self.count: @@ -733,7 +733,7 @@ def __init__(self, self.if_not_exists = if_not_exists - def __unicode__(self): + def __str__(self): qs = ['INSERT INTO {0}'.format(self.table)] # get column names and context placeholders @@ -779,7 +779,7 @@ def __init__(self, self.if_exists = if_exists - def __unicode__(self): + def __str__(self): qs = ['UPDATE', self.table] using_options = [] @@ -880,7 +880,7 @@ def add_field(self, field): self.context_counter += field.get_context_size() self.fields.append(field) - def __unicode__(self): + def __str__(self): qs = ['DELETE'] if self.fields: qs += [', '.join(['{0}'.format(f) for f in self.fields])] From 8cf640f521154547598c95c4f46290dc283e06de Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 3 Apr 2026 19:38:44 +0300 Subject: [PATCH 04/11] refactor: rename __nonzero__ to __bool__ in ResultSet __nonzero__ was the Python 2 name for the boolean conversion method. Python 3 uses __bool__ directly. --- cassandra/cluster.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index ffef70123d..4c06665258 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -5388,11 +5388,9 @@ def __getitem__(self, i): self._enter_list_mode("index operator") return self._current_rows[i] - def __nonzero__(self): + def __bool__(self): return bool(self._current_rows) - __bool__ = __nonzero__ - def get_query_trace(self, max_wait_sec=None): """ Gets the last query trace from the associated future. From 300f4e05f9cb615ac273aa6e5e28f9687d021474 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 3 Apr 2026 19:39:52 +0300 Subject: [PATCH 05/11] remove: dead WeakSet fallback and custom implementation weakref.WeakSet has been available since Python 2.7 and is always present in Python 3. Remove the try/except fallback imports in cluster.py, pool.py, and asyncorereactor.py, and delete the custom _IterationGuard and WeakSet classes from util.py (~210 lines). --- cassandra/cluster.py | 5 +- cassandra/io/asyncorereactor.py | 5 +- cassandra/pool.py | 5 +- cassandra/util.py | 210 -------------------------------- 4 files changed, 3 insertions(+), 222 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 4c06665258..567e7b6dd4 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -109,10 +109,7 @@ # TODO: remove it when eventlet issue would be fixed EventletConnection = None -try: - from weakref import WeakSet -except ImportError: - from cassandra.util import WeakSet # NOQA +from weakref import WeakSet def _is_gevent_monkey_patched(): if 'gevent.monkey' not in sys.modules: diff --git a/cassandra/io/asyncorereactor.py b/cassandra/io/asyncorereactor.py index 02466ad0d2..fc06c7e12d 100644 --- a/cassandra/io/asyncorereactor.py +++ b/cassandra/io/asyncorereactor.py @@ -24,10 +24,7 @@ import sys import ssl -try: - from weakref import WeakSet -except ImportError: - from cassandra.util import WeakSet # noqa +from weakref import WeakSet from cassandra import DependencyException try: diff --git a/cassandra/pool.py b/cassandra/pool.py index 227e1b5315..b4cbed42e9 100644 --- a/cassandra/pool.py +++ b/cassandra/pool.py @@ -25,10 +25,7 @@ import uuid from threading import Lock, RLock, Condition import weakref -try: - from weakref import WeakSet -except ImportError: - from cassandra.util import WeakSet # NOQA +from weakref import WeakSet from cassandra import AuthenticationFailed from cassandra.connection import ConnectionException, EndPoint, DefaultEndPoint diff --git a/cassandra/util.py b/cassandra/util.py index 593c264033..6e4d888bc0 100644 --- a/cassandra/util.py +++ b/cassandra/util.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from _weakref import ref import calendar from collections import OrderedDict from collections.abc import Mapping @@ -228,215 +227,6 @@ def _resolve_contact_points_to_string_map(contact_points): ) -class _IterationGuard(object): - # This context manager registers itself in the current iterators of the - # weak container, such as to delay all removals until the context manager - # exits. - # This technique should be relatively thread-safe (since sets are). - - def __init__(self, weakcontainer): - # Don't create cycles - self.weakcontainer = ref(weakcontainer) - - def __enter__(self): - w = self.weakcontainer() - if w is not None: - w._iterating.add(self) - return self - - def __exit__(self, e, t, b): - w = self.weakcontainer() - if w is not None: - s = w._iterating - s.remove(self) - if not s: - w._commit_removals() - - -class WeakSet(object): - def __init__(self, data=None): - self.data = set() - - def _remove(item, selfref=ref(self)): - self = selfref() - if self is not None: - if self._iterating: - self._pending_removals.append(item) - else: - self.data.discard(item) - - self._remove = _remove - # A list of keys to be removed - self._pending_removals = [] - self._iterating = set() - if data is not None: - self.update(data) - - def _commit_removals(self): - l = self._pending_removals - discard = self.data.discard - while l: - discard(l.pop()) - - def __iter__(self): - with _IterationGuard(self): - for itemref in self.data: - item = itemref() - if item is not None: - yield item - - def __len__(self): - return sum(x() is not None for x in self.data) - - def __contains__(self, item): - return ref(item) in self.data - - def __reduce__(self): - return (self.__class__, (list(self),), - getattr(self, '__dict__', None)) - - __hash__ = None - - def add(self, item): - if self._pending_removals: - self._commit_removals() - self.data.add(ref(item, self._remove)) - - def clear(self): - if self._pending_removals: - self._commit_removals() - self.data.clear() - - def copy(self): - return self.__class__(self) - - def pop(self): - if self._pending_removals: - self._commit_removals() - while True: - try: - itemref = self.data.pop() - except KeyError: - raise KeyError('pop from empty WeakSet') - item = itemref() - if item is not None: - return item - - def remove(self, item): - if self._pending_removals: - self._commit_removals() - self.data.remove(ref(item)) - - def discard(self, item): - if self._pending_removals: - self._commit_removals() - self.data.discard(ref(item)) - - def update(self, other): - if self._pending_removals: - self._commit_removals() - if isinstance(other, self.__class__): - self.data.update(other.data) - else: - for element in other: - self.add(element) - - def __ior__(self, other): - self.update(other) - return self - - # Helper functions for simple delegating methods. - def _apply(self, other, method): - if not isinstance(other, self.__class__): - other = self.__class__(other) - newdata = method(other.data) - newset = self.__class__() - newset.data = newdata - return newset - - def difference(self, other): - return self._apply(other, self.data.difference) - __sub__ = difference - - def difference_update(self, other): - if self._pending_removals: - self._commit_removals() - if self is other: - self.data.clear() - else: - self.data.difference_update(ref(item) for item in other) - - def __isub__(self, other): - if self._pending_removals: - self._commit_removals() - if self is other: - self.data.clear() - else: - self.data.difference_update(ref(item) for item in other) - return self - - def intersection(self, other): - return self._apply(other, self.data.intersection) - __and__ = intersection - - def intersection_update(self, other): - if self._pending_removals: - self._commit_removals() - self.data.intersection_update(ref(item) for item in other) - - def __iand__(self, other): - if self._pending_removals: - self._commit_removals() - self.data.intersection_update(ref(item) for item in other) - return self - - def issubset(self, other): - return self.data.issubset(ref(item) for item in other) - __lt__ = issubset - - def __le__(self, other): - return self.data <= set(ref(item) for item in other) - - def issuperset(self, other): - return self.data.issuperset(ref(item) for item in other) - __gt__ = issuperset - - def __ge__(self, other): - return self.data >= set(ref(item) for item in other) - - def __eq__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - return self.data == set(ref(item) for item in other) - - def symmetric_difference(self, other): - return self._apply(other, self.data.symmetric_difference) - __xor__ = symmetric_difference - - def symmetric_difference_update(self, other): - if self._pending_removals: - self._commit_removals() - if self is other: - self.data.clear() - else: - self.data.symmetric_difference_update(ref(item) for item in other) - - def __ixor__(self, other): - if self._pending_removals: - self._commit_removals() - if self is other: - self.data.clear() - else: - self.data.symmetric_difference_update(ref(item) for item in other) - return self - - def union(self, other): - return self._apply(other, self.data.union) - __or__ = union - - def isdisjoint(self, other): - return len(self.intersection(other)) == 0 - class SortedSet(object): ''' From ca9532eec95ff3b6890d7ca4193a0874a1136eec Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 3 Apr 2026 19:41:18 +0300 Subject: [PATCH 06/11] remove: redundant Python version checks that are always True/False on 3.9+ - cluster.py: remove 'if sys.version_info >= 3.7' guard around eventlet check (always true since we require Python 3.9+). Update error message. - test_insights.py: remove 'if sys.version_info > (3,)' guard around namespace suffix (always true). - test_row_factories.py: remove NAMEDTUPLE_CREATION_BUG flag and Python 3.0-3.6 warning path (bug was fixed in Python 3.7). --- cassandra/cluster.py | 39 ++++++++++++++-------------- tests/unit/advanced/test_insights.py | 4 +-- tests/unit/test_row_factories.py | 29 +++++---------------- 3 files changed, 26 insertions(+), 46 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 567e7b6dd4..236608595a 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -1544,7 +1544,7 @@ def _create_thread_pool_executor(self, **kwargs): Create a ThreadPoolExecutor for the cluster. In most cases, the built-in `concurrent.futures.ThreadPoolExecutor` is used. - Python 3.7+ and Eventlet cause the `concurrent.futures.ThreadPoolExecutor` + Eventlet causes the `concurrent.futures.ThreadPoolExecutor` to hang indefinitely. In that case, the user needs to have the `futurist` package so we can use the `futurist.GreenThreadPoolExecutor` class instead. @@ -1552,26 +1552,25 @@ def _create_thread_pool_executor(self, **kwargs): :return: A ThreadPoolExecutor instance. """ tpe_class = ThreadPoolExecutor - if sys.version_info[0] >= 3 and sys.version_info[1] >= 7: - try: - from cassandra.io.eventletreactor import EventletConnection - is_eventlet = issubclass(self.connection_class, EventletConnection) - except: - # Eventlet is not available or can't be detected - return tpe_class(**kwargs) + try: + from cassandra.io.eventletreactor import EventletConnection + is_eventlet = issubclass(self.connection_class, EventletConnection) + except: + # Eventlet is not available or can't be detected + return tpe_class(**kwargs) - if is_eventlet: - try: - from futurist import GreenThreadPoolExecutor - tpe_class = GreenThreadPoolExecutor - except ImportError: - # futurist is not available - raise ImportError( - ("Python 3.7+ and Eventlet cause the `concurrent.futures.ThreadPoolExecutor` " - "to hang indefinitely. If you want to use the Eventlet reactor, you " - "need to install the `futurist` package to allow the driver to use " - "the GreenThreadPoolExecutor. See https://github.com/eventlet/eventlet/issues/508 " - "for more details.")) + if is_eventlet: + try: + from futurist import GreenThreadPoolExecutor + tpe_class = GreenThreadPoolExecutor + except ImportError: + # futurist is not available + raise ImportError( + ("Eventlet causes the `concurrent.futures.ThreadPoolExecutor` " + "to hang indefinitely. If you want to use the Eventlet reactor, you " + "need to install the `futurist` package to allow the driver to use " + "the GreenThreadPoolExecutor. See https://github.com/eventlet/eventlet/issues/508 " + "for more details.")) return tpe_class(**kwargs) diff --git a/tests/unit/advanced/test_insights.py b/tests/unit/advanced/test_insights.py index ec9b918866..96ca61e736 100644 --- a/tests/unit/advanced/test_insights.py +++ b/tests/unit/advanced/test_insights.py @@ -16,7 +16,6 @@ import unittest import logging -import sys from unittest.mock import sentinel from cassandra import ConsistencyLevel @@ -61,8 +60,7 @@ class NoConfAsDict(object): obj = NoConfAsDict() ns = 'tests.unit.advanced.test_insights' - if sys.version_info > (3,): - ns += '.TestGetConfig.test_invalid_object.' + ns += '.TestGetConfig.test_invalid_object.' # no default # ... as a policy diff --git a/tests/unit/test_row_factories.py b/tests/unit/test_row_factories.py index 7787f1d271..143f392752 100644 --- a/tests/unit/test_row_factories.py +++ b/tests/unit/test_row_factories.py @@ -18,16 +18,12 @@ import logging import warnings -import sys - from unittest import TestCase log = logging.getLogger(__name__) -NAMEDTUPLE_CREATION_BUG = sys.version_info >= (3,) and sys.version_info < (3, 7) - class TestNamedTupleFactory(TestCase): long_colnames, long_rows = ( @@ -45,29 +41,16 @@ class TestNamedTupleFactory(TestCase): ] ) - def test_creation_warning_on_long_column_list(self): + def test_creation_on_long_column_list(self): """ - Reproduces the failure described in PYTHON-893 - - @since 3.15 - @jira_ticket PYTHON-893 - @expected_result creation fails on Python > 3 and < 3.7 - - @test_category row_factory + Verify that named_tuple_factory handles columns lists longer + than 255 without warnings (PYTHON-893 was fixed in Python 3.7+). """ - if not NAMEDTUPLE_CREATION_BUG: - named_tuple_factory(self.long_colnames, self.long_rows) - return - with warnings.catch_warnings(record=True) as w: rows = named_tuple_factory(self.long_colnames, self.long_rows) - assert len(w) == 1 - warning = w[0] - assert 'pseudo_namedtuple_factory' in str(warning) - assert '3.7' in str(warning) - - for r in rows: - assert r.col0 == self.long_rows[0][0] + assert len(w) == 0 + assert hasattr(rows[0], '_fields') + assert isinstance(rows[0], tuple) def test_creation_no_warning_on_short_column_list(self): """ From 8da3410d82b558f56fef3d3b9ab2aef3104b38eb Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 3 Apr 2026 19:42:42 +0300 Subject: [PATCH 07/11] remove: dead try/except around subprocess import in setup.py subprocess has been part of the standard library since Python 2.4. Also fixes: - Use sys.executable instead of hardcoded 'python' for subprocess calls - Use 'python -m sphinx' instead of 'sphinx-build' for docs - Fix get_subdriname() bug: was iterating over characters of a string path instead of calling os.listdir() on the path - Remove duplicate is_macos definition --- cassandra/cqltypes.py | 11 ++-- setup.py | 63 +++++++++---------- .../model/test_class_construction.py | 2 +- 3 files changed, 33 insertions(+), 43 deletions(-) diff --git a/cassandra/cqltypes.py b/cassandra/cqltypes.py index 81e389fa97..399624bfb2 100644 --- a/cassandra/cqltypes.py +++ b/cassandra/cqltypes.py @@ -502,10 +502,9 @@ def deserialize(byts, protocol_version): @staticmethod def serialize(var, protocol_version): - try: - return var.encode('ascii') - except UnicodeDecodeError: + if isinstance(var, bytes): return var + return var.encode('ascii') class FloatType(_CassandraType): @@ -780,11 +779,9 @@ def deserialize(byts, protocol_version): @staticmethod def serialize(ustr, protocol_version): - try: - return ustr.encode('utf-8') - except UnicodeDecodeError: - # already utf-8 + if isinstance(ustr, bytes): return ustr + return ustr.encode('utf-8') class VarcharType(UTF8Type): diff --git a/setup.py b/setup.py index 52e04a63e5..80590c8456 100644 --- a/setup.py +++ b/setup.py @@ -22,11 +22,7 @@ from setuptools.errors import (CCompilerError, PlatformError, ExecError) -try: - import subprocess - has_subprocess = True -except ImportError: - has_subprocess = False +import subprocess has_cqlengine = False @@ -69,36 +65,35 @@ def run(self): except: pass - if has_subprocess: - # Prevent run with in-place extensions because cython-generated objects do not carry docstrings - # http://docs.cython.org/src/userguide/special_methods.html#docstrings - import glob - for f in glob.glob("cassandra/*.so"): - print("Removing '%s' to allow docs to run on pure python modules." %(f,)) - os.unlink(f) + # Prevent run with in-place extensions because cython-generated objects do not carry docstrings + # http://docs.cython.org/src/userguide/special_methods.html#docstrings + import glob + for f in glob.glob("cassandra/*.so"): + print("Removing '%s' to allow docs to run on pure python modules." %(f,)) + os.unlink(f) - # Build io extension to make import and docstrings work - try: - output = subprocess.check_output( - ["python", "setup.py", "build_ext", "--inplace", "--force", "--no-murmur3", "--no-cython"], - stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as exc: - raise RuntimeError("Documentation step '%s' failed: %s: %s" % ("build_ext", exc, exc.output)) - else: - print(output) + # Build io extension to make import and docstrings work + try: + output = subprocess.check_output( + [sys.executable, "setup.py", "build_ext", "--inplace", "--force", "--no-murmur3", "--no-cython"], + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as exc: + raise RuntimeError("Documentation step '%s' failed: %s: %s" % ("build_ext", exc, exc.output)) + else: + print(output) - try: - output = subprocess.check_output( - ["sphinx-build", "-b", mode, "docs", path], - stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as exc: - raise RuntimeError("Documentation step '%s' failed: %s: %s" % (mode, exc, exc.output)) - else: - print(output) + try: + output = subprocess.check_output( + [sys.executable, "-m", "sphinx", "-b", mode, "docs", path], + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as exc: + raise RuntimeError("Documentation step '%s' failed: %s: %s" % (mode, exc, exc.output)) + else: + print(output) - print("") - print("Documentation step '%s' performed, results here:" % mode) - print(" file://%s/%s/index.html" % (os.path.dirname(os.path.realpath(__file__)), path)) + print("") + print("Documentation step '%s' performed, results here:" % mode) + print(" file://%s/%s/index.html" % (os.path.dirname(os.path.realpath(__file__)), path)) class BuildFailed(Exception): @@ -112,7 +107,7 @@ def __init__(self, ext): def get_subdriname(directory_path): try: # List only subdirectories in the given directory - subdirectories = [name for dir in directory_path for name in os.listdir(dir) + subdirectories = [name for name in os.listdir(directory_path) if os.path.isdir(os.path.join(directory_path, name))] return subdirectories except Exception: @@ -137,8 +132,6 @@ def get_libev_headers_path(): murmur3_ext = Extension('cassandra.cmurmur3', sources=['cassandra/cmurmur3.c']) -is_macos = sys.platform.startswith('darwin') - def eval_env_var_as_array(varname): val = os.environ.get(varname) return None if not val else [v.strip() for v in val.split(',')] diff --git a/tests/integration/cqlengine/model/test_class_construction.py b/tests/integration/cqlengine/model/test_class_construction.py index df0a57d543..bbd2a1d7b1 100644 --- a/tests/integration/cqlengine/model/test_class_construction.py +++ b/tests/integration/cqlengine/model/test_class_construction.py @@ -352,7 +352,7 @@ def test_attempting_to_create_abstract_table_fails(self): def test_attempting_query_on_abstract_model_fails(self): """ Tests attempting to execute query with an abstract model fails """ with pytest.raises(CQLEngineException): - iter(AbstractModelWithFullCols.objects(pkey=5)).next() + next(iter(AbstractModelWithFullCols.objects(pkey=5))) def test_abstract_columns_are_inherited(self): """ Tests that columns defined in the abstract class are inherited into the concrete class """ From d90733dfd359076e6c1501111b19f0c8fecc212c Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 3 Apr 2026 19:44:21 +0300 Subject: [PATCH 08/11] remove: u'' string prefixes from production and test code In Python 3, the u'' prefix on string literals is a no-op (str is always unicode). Remove 150 occurrences across 15 files. Also update cql_encode_unicode() in encoder.py to pass the string directly to cql_quote() instead of encoding to UTF-8 bytes first, since Python 3 strings are always unicode. --- cassandra/cqlengine/statements.py | 8 +-- cassandra/datastax/graph/fluent/_query.py | 4 +- cassandra/datastax/graph/query.py | 2 +- cassandra/encoder.py | 10 +++- cassandra/query.py | 10 ++-- .../cqlengine/columns/test_validation.py | 8 +-- .../cqlengine/model/test_model_io.py | 4 +- .../integration/cqlengine/model/test_udts.py | 2 +- tests/integration/standard/test_cluster.py | 16 ++--- tests/integration/standard/test_metadata.py | 58 +++++++++---------- tests/integration/standard/test_query.py | 16 ++--- tests/integration/standard/test_types.py | 4 +- tests/unit/test_marshalling.py | 12 ++-- tests/unit/test_metadata.py | 36 ++++++------ tests/unit/test_orderedmap.py | 6 +- tests/unit/test_types.py | 6 +- 16 files changed, 104 insertions(+), 98 deletions(-) diff --git a/cassandra/cqlengine/statements.py b/cassandra/cqlengine/statements.py index 32c565f982..33cbf903ca 100644 --- a/cassandra/cqlengine/statements.py +++ b/cassandra/cqlengine/statements.py @@ -111,7 +111,7 @@ def __init__(self, field, operator, value, quote_field=True): def __str__(self): field = ('"{0}"' if self.quote_field else '{0}').format(self.field) - return u'{0} {1} {2}'.format(field, self.operator, str(self.query_value)) + return '{0} {1} {2}'.format(field, self.operator, str(self.query_value)) def __hash__(self): return super(WhereClause, self).__hash__() ^ hash(self.operator) @@ -141,7 +141,7 @@ def __init__(self, field): def __str__(self): field = ('"{0}"' if self.quote_field else '{0}').format(self.field) - return u'{0} {1}'.format(field, self.operator) + return '{0} {1}'.format(field, self.operator) def update_context(self, ctx): pass @@ -157,7 +157,7 @@ class AssignmentClause(BaseClause): """ a single variable st statement """ def __str__(self): - return u'"{0}" = %({1})s'.format(self.field, self.context_id) + return '"{0}" = %({1})s'.format(self.field, self.context_id) def insert_tuple(self): return self.field, self.context_id @@ -167,7 +167,7 @@ class ConditionalClause(BaseClause): """ A single variable iff statement """ def __str__(self): - return u'"{0}" = %({1})s'.format(self.field, self.context_id) + return '"{0}" = %({1})s'.format(self.field, self.context_id) def insert_tuple(self): return self.field, self.context_id diff --git a/cassandra/datastax/graph/fluent/_query.py b/cassandra/datastax/graph/fluent/_query.py index d5eb7f6373..c4f97506d3 100644 --- a/cassandra/datastax/graph/fluent/_query.py +++ b/cassandra/datastax/graph/fluent/_query.py @@ -179,7 +179,7 @@ def __len__(self): raise NotImplementedError() def __str__(self): - return u''.format(len(self)) + return ''.format(len(self)) __repr__ = __str__ @@ -204,7 +204,7 @@ def add_all(self, traversals): def as_graph_statement(self, graph_protocol=GraphProtocol.GRAPHSON_2_0, context=None): statements = [_query_from_traversal(t, graph_protocol, context) for t in self._traversals] - query = u"[{0}]".format(','.join(statements)) + query = "[{0}]".format(','.join(statements)) return SimpleGraphStatement(query) def execute(self): diff --git a/cassandra/datastax/graph/query.py b/cassandra/datastax/graph/query.py index 866df7a94c..e2b618f543 100644 --- a/cassandra/datastax/graph/query.py +++ b/cassandra/datastax/graph/query.py @@ -175,7 +175,7 @@ def query(self): raise NotImplementedError() def __str__(self): - return u''.format(self.query) + return ''.format(self.query) __repr__ = __str__ diff --git a/cassandra/encoder.py b/cassandra/encoder.py index d803c087ba..6cb4d29085 100644 --- a/cassandra/encoder.py +++ b/cassandra/encoder.py @@ -102,9 +102,15 @@ def cql_encode_none(self, val): def cql_encode_unicode(self, val): """ - Converts :class:`unicode` objects to UTF-8 encoded strings with quote escaping. + Encodes a string value with quote escaping. + + .. deprecated:: + This method is unused internally since Python 2 support was + removed (``str`` is always unicode on Python 3). It is kept + for backward compatibility with user subclasses of + :class:`Encoder`. """ - return cql_quote(val.encode('utf-8')) + return cql_quote(val) def cql_encode_str(self, val): """ diff --git a/cassandra/query.py b/cassandra/query.py index 6c6878fdb4..8b49a86e2c 100644 --- a/cassandra/query.py +++ b/cassandra/query.py @@ -187,7 +187,7 @@ def dict_factory(colnames, rows): >>> session.row_factory = dict_factory >>> rows = session.execute("SELECT name, age FROM users LIMIT 1") >>> print(rows[0]) - {u'age': 42, u'name': u'Bob'} + {'age': 42, 'name': 'Bob'} .. versionchanged:: 2.0.0 moved from ``cassandra.decoder`` to ``cassandra.query`` @@ -412,7 +412,7 @@ def query_string(self): def __str__(self): consistency = ConsistencyLevel.value_to_name.get(self.consistency_level, 'Not Set') - return (u'' % + return ('' % (self.query_string, consistency)) __repr__ = __str__ @@ -527,7 +527,7 @@ def is_lwt(self): def __str__(self): consistency = ConsistencyLevel.value_to_name.get(self.consistency_level, 'Not Set') - return (u'' % + return ('' % (self.query_string, consistency)) __repr__ = __str__ @@ -695,7 +695,7 @@ def is_lwt(self): def __str__(self): consistency = ConsistencyLevel.value_to_name.get(self.consistency_level, 'Not Set') - return (u'' % + return ('' % (self.prepared_statement.query_string, self.raw_values, consistency)) __repr__ = __str__ @@ -908,7 +908,7 @@ def __len__(self): def __str__(self): consistency = ConsistencyLevel.value_to_name.get(self.consistency_level, 'Not Set') - return (u'' % + return ('' % (self.batch_type, len(self), consistency)) __repr__ = __str__ diff --git a/tests/integration/cqlengine/columns/test_validation.py b/tests/integration/cqlengine/columns/test_validation.py index ebffc0666c..8a8933076a 100644 --- a/tests/integration/cqlengine/columns/test_validation.py +++ b/tests/integration/cqlengine/columns/test_validation.py @@ -597,7 +597,7 @@ def test_length_range(self): def test_type_checking(self): Ascii().validate('string') - Ascii().validate(u'unicode') + Ascii().validate('unicode') Ascii().validate(bytearray('bytearray', encoding='ascii')) with pytest.raises(ValidationError): @@ -613,7 +613,7 @@ def test_type_checking(self): if sys.version_info < (3, 1): with pytest.raises(ValidationError): - Ascii().validate(u'Beyonc' + unichr(233)) + Ascii().validate('Beyonc' + unichr(233)) def test_unaltering_validation(self): """ Test the validation step doesn't re-interpret values. """ @@ -724,7 +724,7 @@ def test_length_range(self): def test_type_checking(self): Text().validate('string') - Text().validate(u'unicode') + Text().validate('unicode') Text().validate(bytearray('bytearray', encoding='ascii')) with pytest.raises(ValidationError): @@ -736,7 +736,7 @@ def test_type_checking(self): Text().validate("!#$%&\'()*+,-./") Text().validate('Beyonc' + chr(233)) if sys.version_info < (3, 1): - Text().validate(u'Beyonc' + unichr(233)) + Text().validate('Beyonc' + unichr(233)) def test_unaltering_validation(self): """ Test the validation step doesn't re-interpret values. """ diff --git a/tests/integration/cqlengine/model/test_model_io.py b/tests/integration/cqlengine/model/test_model_io.py index f55815310a..b6d0af4a7f 100644 --- a/tests/integration/cqlengine/model/test_model_io.py +++ b/tests/integration/cqlengine/model/test_model_io.py @@ -694,9 +694,9 @@ def setUp(self): def test_query_with_date(self): uid = uuid4() day = date(2013, 11, 26) - obj = TestQueryModel.create(test_id=uid, date=day, description=u'foo') + obj = TestQueryModel.create(test_id=uid, date=day, description='foo') - assert obj.description == u'foo' + assert obj.description == 'foo' inst = TestQueryModel.filter( TestQueryModel.test_id == uid, diff --git a/tests/integration/cqlengine/model/test_udts.py b/tests/integration/cqlengine/model/test_udts.py index 80f1b9693f..de62077c3f 100644 --- a/tests/integration/cqlengine/model/test_udts.py +++ b/tests/integration/cqlengine/model/test_udts.py @@ -383,7 +383,7 @@ def test_udts_with_unicode(self): @test_category data_types:udt """ ascii_name = 'normal name' - unicode_name = u'Fran\u00E7ois' + unicode_name = 'Fran\u00E7ois' class UserModelText(Model): id = columns.Text(primary_key=True) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 08b823d716..ad2576c0d3 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -1281,14 +1281,14 @@ def test_compact_option(self): "({i}, 'a{i}{i}', {i}{i}, {i}{i}, textAsBlob('b{i}{i}'))".format(i=i)) nc_results = nc_session.execute("SELECT * FROM compact_table") - assert set(nc_results.current_rows) == {(1, u'a1', 11, 11, 'b1'), - (1, u'a11', 11, 11, 'b11'), - (2, u'a2', 22, 22, 'b2'), - (2, u'a22', 22, 22, 'b22'), - (3, u'a3', 33, 33, 'b3'), - (3, u'a33', 33, 33, 'b33'), - (4, u'a4', 44, 44, 'b4'), - (4, u'a44', 44, 44, 'b44')} + assert set(nc_results.current_rows) == {(1, 'a1', 11, 11, 'b1'), + (1, 'a11', 11, 11, 'b11'), + (2, 'a2', 22, 22, 'b2'), + (2, 'a22', 22, 22, 'b22'), + (3, 'a3', 33, 33, 'b3'), + (3, 'a33', 33, 33, 'b33'), + (4, 'a4', 44, 44, 'b4'), + (4, 'a44', 44, 44, 'b44')} results = session.execute("SELECT * FROM compact_table") assert set(results.current_rows) == {(1, 11, 11), diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index c30e369d83..dcef5fe4fa 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -239,9 +239,9 @@ def test_basic_table_meta_properties(self): assert tablemeta.name == self.function_table_name assert tablemeta.name == self.function_table_name - assert [u'a'] == [c.name for c in tablemeta.partition_key] + assert ['a'] == [c.name for c in tablemeta.partition_key] assert [] == tablemeta.clustering_key - assert [u'a', u'b', u'c'] == sorted(tablemeta.columns.keys()) + assert ['a', 'b', 'c'] == sorted(tablemeta.columns.keys()) cc = self.cluster.control_connection._connection parser = get_schema_parser( @@ -263,9 +263,9 @@ def test_compound_primary_keys(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - assert [u'a'] == [c.name for c in tablemeta.partition_key] - assert [u'b'] == [c.name for c in tablemeta.clustering_key] - assert [u'a', u'b', u'c'] == sorted(tablemeta.columns.keys()) + assert ['a'] == [c.name for c in tablemeta.partition_key] + assert ['b'] == [c.name for c in tablemeta.clustering_key] + assert ['a', 'b', 'c'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -275,9 +275,9 @@ def test_compound_primary_keys_protected(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - assert [u'Aa'] == [c.name for c in tablemeta.partition_key] - assert [u'Bb'] == [c.name for c in tablemeta.clustering_key] - assert [u'Aa', u'Bb', u'Cc'] == sorted(tablemeta.columns.keys()) + assert ['Aa'] == [c.name for c in tablemeta.partition_key] + assert ['Bb'] == [c.name for c in tablemeta.clustering_key] + assert ['Aa', 'Bb', 'Cc'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -287,9 +287,9 @@ def test_compound_primary_keys_more_columns(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - assert [u'a'] == [c.name for c in tablemeta.partition_key] - assert [u'b', u'c'] == [c.name for c in tablemeta.clustering_key] - assert [u'a', u'b', u'c', u'd', u'e', u'f'] == sorted(tablemeta.columns.keys()) + assert ['a'] == [c.name for c in tablemeta.partition_key] + assert ['b', 'c'] == [c.name for c in tablemeta.clustering_key] + assert ['a', 'b', 'c', 'd', 'e', 'f'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -298,9 +298,9 @@ def test_composite_primary_key(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - assert [u'a', u'b'] == [c.name for c in tablemeta.partition_key] + assert ['a', 'b'] == [c.name for c in tablemeta.partition_key] assert [] == tablemeta.clustering_key - assert [u'a', u'b', u'c'] == sorted(tablemeta.columns.keys()) + assert ['a', 'b', 'c'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -310,9 +310,9 @@ def test_composite_in_compound_primary_key(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - assert [u'a', u'b'] == [c.name for c in tablemeta.partition_key] - assert [u'c'] == [c.name for c in tablemeta.clustering_key] - assert [u'a', u'b', u'c', u'd', u'e'] == sorted(tablemeta.columns.keys()) + assert ['a', 'b'] == [c.name for c in tablemeta.partition_key] + assert ['c'] == [c.name for c in tablemeta.clustering_key] + assert ['a', 'b', 'c', 'd', 'e'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -322,9 +322,9 @@ def test_compound_primary_keys_compact(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - assert [u'a'] == [c.name for c in tablemeta.partition_key] - assert [u'b'] == [c.name for c in tablemeta.clustering_key] - assert [u'a', u'b', u'c'] == sorted(tablemeta.columns.keys()) + assert ['a'] == [c.name for c in tablemeta.partition_key] + assert ['b'] == [c.name for c in tablemeta.clustering_key] + assert ['a', 'b', 'c'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -356,9 +356,9 @@ def test_compound_primary_keys_more_columns_compact(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - assert [u'a'] == [c.name for c in tablemeta.partition_key] - assert [u'b', u'c'] == [c.name for c in tablemeta.clustering_key] - assert [u'a', u'b', u'c', u'd'] == sorted(tablemeta.columns.keys()) + assert ['a'] == [c.name for c in tablemeta.partition_key] + assert ['b', 'c'] == [c.name for c in tablemeta.clustering_key] + assert ['a', 'b', 'c', 'd'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -367,9 +367,9 @@ def test_composite_primary_key_compact(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - assert [u'a', u'b'] == [c.name for c in tablemeta.partition_key] + assert ['a', 'b'] == [c.name for c in tablemeta.partition_key] assert [] == tablemeta.clustering_key - assert [u'a', u'b', u'c'] == sorted(tablemeta.columns.keys()) + assert ['a', 'b', 'c'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -379,9 +379,9 @@ def test_composite_in_compound_primary_key_compact(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - assert [u'a', u'b'] == [c.name for c in tablemeta.partition_key] - assert [u'c'] == [c.name for c in tablemeta.clustering_key] - assert [u'a', u'b', u'c', u'd'] == sorted(tablemeta.columns.keys()) + assert ['a', 'b'] == [c.name for c in tablemeta.partition_key] + assert ['c'] == [c.name for c in tablemeta.clustering_key] + assert ['a', 'b', 'c', 'd'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -394,9 +394,9 @@ def test_cql_compatibility(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - assert [u'a'] == [c.name for c in tablemeta.partition_key] + assert ['a'] == [c.name for c in tablemeta.partition_key] assert [] == tablemeta.clustering_key - assert [u'a', u'b', u'c', u'd'] == sorted(tablemeta.columns.keys()) + assert ['a', 'b', 'c', 'd'] == sorted(tablemeta.columns.keys()) assert tablemeta.is_cql_compatible diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index f9d3dc26bc..a945970cdc 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -323,7 +323,7 @@ def test_column_names(self): result_set = self.session.execute("SELECT * FROM {0}.{1}".format(self.keyspace_name, self.function_table_name)) assert result_set.column_types is not None - assert result_set.column_names == [u'user', u'game', u'year', u'month', u'day', u'score'] + assert result_set.column_names == ['user', 'game', 'year', 'month', 'day', 'score'] @greaterthanorequalcass30 def test_basic_json_query(self): @@ -759,11 +759,11 @@ def test_unicode(self): k int PRIMARY KEY, v text )''' self.session.execute(ddl) - unicode_text = u'Fran\u00E7ois' - query = u'INSERT INTO test3rf.testtext (k, v) VALUES (%s, %s)' + unicode_text = 'Fran\u00E7ois' + query = 'INSERT INTO test3rf.testtext (k, v) VALUES (%s, %s)' try: batch = BatchStatement(BatchType.LOGGED) - batch.add(u"INSERT INTO test3rf.testtext (k, v) VALUES (%s, %s)", (0, unicode_text)) + batch.add("INSERT INTO test3rf.testtext (k, v) VALUES (%s, %s)", (0, unicode_text)) self.session.execute(batch) finally: self.session.execute("DROP TABLE test3rf.testtext") @@ -1338,12 +1338,12 @@ def test_unicode(self): @test_category query """ - unicode_text = u'Fran\u00E7ois' + unicode_text = 'Fran\u00E7ois' batch = BatchStatement(BatchType.LOGGED) - batch.add(u"INSERT INTO {0}.{1} (k, v) VALUES (%s, %s)".format(self.keyspace_name, self.function_table_name), (0, unicode_text)) + batch.add("INSERT INTO {0}.{1} (k, v) VALUES (%s, %s)".format(self.keyspace_name, self.function_table_name), (0, unicode_text)) self.session.execute(batch) - self.session.execute(u"INSERT INTO {0}.{1} (k, v) VALUES (%s, %s)".format(self.keyspace_name, self.function_table_name), (0, unicode_text)) - prepared = self.session.prepare(u"INSERT INTO {0}.{1} (k, v) VALUES (?, ?)".format(self.keyspace_name, self.function_table_name)) + self.session.execute("INSERT INTO {0}.{1} (k, v) VALUES (%s, %s)".format(self.keyspace_name, self.function_table_name), (0, unicode_text)) + prepared = self.session.prepare("INSERT INTO {0}.{1} (k, v) VALUES (?, ?)".format(self.keyspace_name, self.function_table_name)) bound = prepared.bind((1, unicode_text)) self.session.execute(bound) diff --git a/tests/integration/standard/test_types.py b/tests/integration/standard/test_types.py index 559a6b3da0..d28f3b03ee 100644 --- a/tests/integration/standard/test_types.py +++ b/tests/integration/standard/test_types.py @@ -765,8 +765,8 @@ def test_can_insert_unicode_query_string(self): Test to ensure unicode strings can be used in a query """ s = self.session - s.execute(u"SELECT * FROM system.local WHERE key = 'ef\u2052ef'") - s.execute(u"SELECT * FROM system.local WHERE key = %s", (u"fe\u2051fe",)) + s.execute("SELECT * FROM system.local WHERE key = 'ef\u2052ef'") + s.execute("SELECT * FROM system.local WHERE key = %s", ("fe\u2051fe",)) @requires_composite_type def test_can_read_composite_type(self): diff --git a/tests/unit/test_marshalling.py b/tests/unit/test_marshalling.py index e4b415ac69..6a4aef2df9 100644 --- a/tests/unit/test_marshalling.py +++ b/tests/unit/test_marshalling.py @@ -66,9 +66,9 @@ (b'', 'InetAddressType', None), (b'A46\xa9', 'InetAddressType', '65.52.54.169'), (b'*\x00\x13(\xe1\x02\xcc\xc0\x00\x00\x00\x00\x00\x00\x01"', 'InetAddressType', '2a00:1328:e102:ccc0::122'), - (b'\xe3\x81\xbe\xe3\x81\x97\xe3\x81\xa6', 'UTF8Type', u'\u307e\u3057\u3066'), - (b'\xe3\x81\xbe\xe3\x81\x97\xe3\x81\xa6' * 1000, 'UTF8Type', u'\u307e\u3057\u3066' * 1000), - (b'', 'UTF8Type', u''), + (b'\xe3\x81\xbe\xe3\x81\x97\xe3\x81\xa6', 'UTF8Type', '\u307e\u3057\u3066'), + (b'\xe3\x81\xbe\xe3\x81\x97\xe3\x81\xa6' * 1000, 'UTF8Type', '\u307e\u3057\u3066' * 1000), + (b'', 'UTF8Type', ''), (b'\xff' * 16, 'UUIDType', UUID('ffffffff-ffff-ffff-ffff-ffffffffffff')), (b'I\x15~\xfc\xef<\x9d\xe3\x16\x98\xaf\x80\x1f\xb4\x0b*', 'UUIDType', UUID('49157efc-ef3c-9de3-1698-af801fb40b2a')), (b'', 'UUIDType', None), @@ -89,9 +89,9 @@ ) ordered_map_value = OrderedMapSerializedKey(UTF8Type, 3) -ordered_map_value._insert(u'\u307fbob', 199) -ordered_map_value._insert(u'', -1) -ordered_map_value._insert(u'\\', 0) +ordered_map_value._insert('\u307fbob', 199) +ordered_map_value._insert('', -1) +ordered_map_value._insert('\\', 0) # these following entries work for me right now, but they're dependent on # vagaries of internal python ordering for unordered types diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index dcbb840447..9689463729 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -671,27 +671,27 @@ def test_table_name(self): def test_column_name_single_partition(self): tm = TableMetadata('ks', 'table') - cm = ColumnMetadata(tm, self.name, u'int') + cm = ColumnMetadata(tm, self.name, 'int') tm.columns[cm.name] = cm tm.partition_key.append(cm) tm.export_as_string() def test_column_name_single_partition_single_clustering(self): tm = TableMetadata('ks', 'table') - cm = ColumnMetadata(tm, self.name, u'int') + cm = ColumnMetadata(tm, self.name, 'int') tm.columns[cm.name] = cm tm.partition_key.append(cm) - cm = ColumnMetadata(tm, self.name + 'x', u'int') + cm = ColumnMetadata(tm, self.name + 'x', 'int') tm.columns[cm.name] = cm tm.clustering_key.append(cm) tm.export_as_string() def test_column_name_multiple_partition(self): tm = TableMetadata('ks', 'table') - cm = ColumnMetadata(tm, self.name, u'int') + cm = ColumnMetadata(tm, self.name, 'int') tm.columns[cm.name] = cm tm.partition_key.append(cm) - cm = ColumnMetadata(tm, self.name + 'x', u'int') + cm = ColumnMetadata(tm, self.name + 'x', 'int') tm.columns[cm.name] = cm tm.partition_key.append(cm) tm.export_as_string() @@ -707,20 +707,20 @@ def test_index(self): def test_function(self): fm = Function(keyspace=self.name, name=self.name, - argument_types=(u'int', u'int'), - argument_names=(u'x', u'y'), - return_type=u'int', language=u'language', + argument_types=('int', 'int'), + argument_names=('x', 'y'), + return_type='int', language='language', body=self.name, called_on_null_input=False, deterministic=True, - monotonic=False, monotonic_on=(u'x',)) + monotonic=False, monotonic_on=('x',)) fm.export_as_string() def test_aggregate(self): - am = Aggregate(self.name, self.name, (u'text',), self.name, u'text', self.name, self.name, u'text', True) + am = Aggregate(self.name, self.name, ('text',), self.name, 'text', self.name, self.name, 'text', True) am.export_as_string() def test_user_type(self): - um = UserType(self.name, self.name, [self.name, self.name], [u'int', u'text']) + um = UserType(self.name, self.name, [self.name, self.name], ['int', 'text']) um.export_as_string() @@ -729,10 +729,10 @@ class FunctionToCQLTests(unittest.TestCase): base_vars = { 'keyspace': 'ks_name', 'name': 'function_name', - 'argument_types': (u'int', u'int'), - 'argument_names': (u'x', u'y'), - 'return_type': u'int', - 'language': u'language', + 'argument_types': ('int', 'int'), + 'argument_names': ('x', 'y'), + 'return_type': 'int', + 'language': 'language', 'body': 'body', 'called_on_null_input': False, 'deterministic': True, @@ -785,10 +785,10 @@ class AggregateToCQLTests(unittest.TestCase): base_vars = { 'keyspace': 'ks_name', 'name': 'function_name', - 'argument_types': (u'int', u'int'), + 'argument_types': ('int', 'int'), 'state_func': 'funcname', - 'state_type': u'int', - 'return_type': u'int', + 'state_type': 'int', + 'return_type': 'int', 'final_func': None, 'initial_condition': '0', 'deterministic': True diff --git a/tests/unit/test_orderedmap.py b/tests/unit/test_orderedmap.py index 156bbd5f30..3d5c399259 100644 --- a/tests/unit/test_orderedmap.py +++ b/tests/unit/test_orderedmap.py @@ -175,12 +175,12 @@ def test_normalized_lookup(self): protocol_version = 3 om = OrderedMapSerializedKey(key_type, protocol_version) key_ascii = {'one': 1} - key_unicode = {u'two': 2} + key_unicode = {'two': 2} om._insert_unchecked(key_ascii, key_type.serialize(key_ascii, protocol_version), object()) om._insert_unchecked(key_unicode, key_type.serialize(key_unicode, protocol_version), object()) # type lookup is normalized by key_type # PYTHON-231 - assert om[{'one': 1}] is om[{u'one': 1}] - assert om[{'two': 2}] is om[{u'two': 2}] + assert om[{'one': 1}] is om[{'one': 1}] + assert om[{'two': 2}] is om[{'two': 2}] assert om[{'one': 1}] is not om[{'two': 2}] diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index 11aab2748d..5f1da5ae75 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -291,14 +291,14 @@ def test_collection_null_support(self): def test_write_read_string(self): with tempfile.TemporaryFile() as f: - value = u'test' + value = 'test' write_string(f, value) f.seek(0) assert read_string(f) == value def test_write_read_longstring(self): with tempfile.TemporaryFile() as f: - value = u'test' + value = 'test' write_longstring(f, value) f.seek(0) assert read_longstring(f) == value @@ -324,7 +324,7 @@ def test_write_read_inet(self): assert read_inet(f) == value def test_cql_quote(self): - assert cql_quote(u'test') == "'test'" + assert cql_quote('test') == "'test'" assert cql_quote('test') == "'test'" assert cql_quote(0) == '0' From 3e97d1a86fcd1ac9cce9f7ca3d5d2bc516f94466 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 3 Apr 2026 19:45:33 +0300 Subject: [PATCH 09/11] remove: dead unichr() code blocks guarded by Python 2 version check - test_validation.py: remove 'if sys.version_info < (3, 1)' blocks that tested unichr() (only available in Python 2). Also fix 'class DataType():' to 'class DataType:'. - test_metadata.py: delete test_export_keyspace_schema_udts which was skipped on all Python versions except 2.7. --- .../cqlengine/columns/test_validation.py | 7 +- tests/integration/standard/test_metadata.py | 76 ------------------- 2 files changed, 1 insertion(+), 82 deletions(-) diff --git a/tests/integration/cqlengine/columns/test_validation.py b/tests/integration/cqlengine/columns/test_validation.py index 8a8933076a..082608dd72 100644 --- a/tests/integration/cqlengine/columns/test_validation.py +++ b/tests/integration/cqlengine/columns/test_validation.py @@ -190,7 +190,7 @@ def test_varint_io(self): self.VarIntTest.objects.create(test_id=0, bignum="not_a_number") -class DataType(): +class DataType: @classmethod def setUpClass(cls): if PROTOCOL_VERSION < 4 or CASSANDRA_VERSION < Version("3.0"): @@ -611,9 +611,6 @@ def test_type_checking(self): with pytest.raises(ValidationError): Ascii().validate('Beyonc' + chr(233)) - if sys.version_info < (3, 1): - with pytest.raises(ValidationError): - Ascii().validate('Beyonc' + unichr(233)) def test_unaltering_validation(self): """ Test the validation step doesn't re-interpret values. """ @@ -735,8 +732,6 @@ def test_type_checking(self): Text().validate("!#$%&\'()*+,-./") Text().validate('Beyonc' + chr(233)) - if sys.version_info < (3, 1): - Text().validate('Beyonc' + unichr(233)) def test_unaltering_validation(self): """ Test the validation step doesn't re-interpret values. """ diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index dcef5fe4fa..0d39b967a1 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -1119,82 +1119,6 @@ def test_export_keyspace_schema(self): assert isinstance(keyspace_metadata.as_cql_query(), str) cluster.shutdown() - @greaterthancass20 - def test_export_keyspace_schema_udts(self): - """ - Test udt exports - """ - - if PROTOCOL_VERSION < 3: - raise unittest.SkipTest( - "Protocol 3.0+ is required for UDT change events, currently testing against %r" - % (PROTOCOL_VERSION,)) - - if sys.version_info[0:2] != (2, 7): - raise unittest.SkipTest('This test compares static strings generated from dict items, which may change orders. Test with 2.7.') - - cluster = TestCluster() - session = cluster.connect() - - session.execute(""" - CREATE KEYSPACE export_udts - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} - AND durable_writes = true; - """) - session.execute(""" - CREATE TYPE export_udts.street ( - street_number int, - street_name text) - """) - session.execute(""" - CREATE TYPE export_udts.zip ( - zipcode int, - zip_plus_4 int) - """) - session.execute(""" - CREATE TYPE export_udts.address ( - street_address frozen, - zip_code frozen) - """) - session.execute(""" - CREATE TABLE export_udts.users ( - user text PRIMARY KEY, - addresses map>) - """) - - expected_prefix = """CREATE KEYSPACE export_udts WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true; - -CREATE TYPE export_udts.street ( - street_number int, - street_name text -); - -CREATE TYPE export_udts.zip ( - zipcode int, - zip_plus_4 int -); - -CREATE TYPE export_udts.address ( - street_address frozen, - zip_code frozen -); - -CREATE TABLE export_udts.users ( - user text PRIMARY KEY, - addresses map>""" - - assert_startswith_diff(cluster.metadata.keyspaces['export_udts'].export_as_string(), expected_prefix) - - table_meta = cluster.metadata.keyspaces['export_udts'].tables['users'] - - expected_prefix = """CREATE TABLE export_udts.users ( - user text PRIMARY KEY, - addresses map>""" - - assert_startswith_diff(table_meta.export_as_string(), expected_prefix) - - cluster.shutdown() - @greaterthancass21 @xfail_scylla_version_lt(reason='scylladb/scylladb#10707 - Column name in CREATE INDEX is not quoted', oss_scylla_version="5.2", ent_scylla_version="2023.1.1") From 6be8556ef42722bb9be1948e9e014994caef51af Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 3 Apr 2026 19:45:58 +0300 Subject: [PATCH 10/11] remove: dead workaround for CPython bug #10923 (fixed in 3.3) The workaround pre-loaded the UTF-8 encoding module to avoid a deadlock when importing from multiple threads. This bug was fixed in Python 3.3. --- cassandra/cluster.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 236608595a..c492033d80 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -179,12 +179,6 @@ def _connection_reduce_fn(val,import_fn): raise DependencyException("Exception loading connection class dependencies", excs) DefaultConnection = conn_class -# Forces load of utf8 encoding module to avoid deadlock that occurs -# if code that is being imported tries to import the module in a seperate -# thread. -# See http://bugs.python.org/issue10923 -"".encode('utf8') - log = logging.getLogger(__name__) From 42e3d081866c3a4b09729b930f665cfcf4ea2f09 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 3 Apr 2026 19:48:45 +0300 Subject: [PATCH 11/11] docs: update stale Python 2 comments and docstrings --- cassandra/datastax/graph/graphson.py | 12 ++++++------ cassandra/io/asyncorereactor.py | 6 +++--- cassandra/metadata.py | 2 +- cassandra/util.py | 5 +++-- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/cassandra/datastax/graph/graphson.py b/cassandra/datastax/graph/graphson.py index 335c7f7825..a3411b25ca 100644 --- a/cassandra/datastax/graph/graphson.py +++ b/cassandra/datastax/graph/graphson.py @@ -48,7 +48,7 @@ ------------ | -------------- | -------------- | ------------ text | string | string | str boolean | | | bool -bigint | g:Int64 | g:Int64 | long +bigint | g:Int64 | g:Int64 | int int | g:Int32 | g:Int32 | int double | g:Double | g:Double | float float | g:Float | g:Float | float @@ -56,18 +56,18 @@ bigdecimal | gx:BigDecimal | gx:BigDecimal | Decimal duration | gx:Duration | N/A | timedelta (Classic graph only) DSE Duration | N/A | dse:Duration | Duration (Core graph only) -inet | gx:InetAddress | gx:InetAddress | str (unicode), IPV4Address/IPV6Address (PY3) +inet | gx:InetAddress | gx:InetAddress | str, IPV4Address/IPV6Address timestamp | gx:Instant | gx:Instant | datetime.datetime date | gx:LocalDate | gx:LocalDate | datetime.date time | gx:LocalTime | gx:LocalTime | datetime.time smallint | gx:Int16 | gx:Int16 | int -varint | gx:BigInteger | gx:BigInteger | long +varint | gx:BigInteger | gx:BigInteger | int date | gx:LocalDate | gx:LocalDate | Date polygon | dse:Polygon | dse:Polygon | Polygon point | dse:Point | dse:Point | Point linestring | dse:Linestring | dse:LineString | LineString -blob | dse:Blob | dse:Blob | bytearray, buffer (PY2), memoryview (PY3), bytes (PY3) -blob | gx:ByteBuffer | gx:ByteBuffer | bytearray, buffer (PY2), memoryview (PY3), bytes (PY3) +blob | dse:Blob | dse:Blob | bytearray, memoryview, bytes +blob | gx:ByteBuffer | gx:ByteBuffer | bytearray, memoryview, bytes list | N/A | g:List | list (Core graph only) map | N/A | g:Map | dict (Core graph only) set | N/A | g:Set | set or list (Core graph only) @@ -969,7 +969,7 @@ def serialize(self, value, writer=None): """ serializer = self.get_serializer(value) if not serializer: - raise ValueError("Unable to find a serializer for value of type: ".format(type(value))) + raise ValueError("Unable to find a serializer for value of type: {}".format(type(value))) val = serializer.serialize(value, writer or self) if serializer is TypeWrapperTypeIO: diff --git a/cassandra/io/asyncorereactor.py b/cassandra/io/asyncorereactor.py index fc06c7e12d..c36073668b 100644 --- a/cassandra/io/asyncorereactor.py +++ b/cassandra/io/asyncorereactor.py @@ -263,9 +263,9 @@ def _maybe_log_debug(self, *args, **kwargs): try: log.debug(*args, **kwargs) except Exception: - # TODO: Remove when Python 2 support is removed - # PYTHON-1266. If our logger has disappeared, there's nothing we - # can do, so just log nothing. + # PYTHON-1266. If our logger has disappeared (e.g. during + # interpreter shutdown), there's nothing we can do, so just + # log nothing. pass def add_timer(self, timer): diff --git a/cassandra/metadata.py b/cassandra/metadata.py index 43399b7152..85fe067d31 100644 --- a/cassandra/metadata.py +++ b/cassandra/metadata.py @@ -2270,7 +2270,7 @@ def _build_table_metadata(self, row, col_rows=None, trigger_rows=None): # Some thrift tables define names in composite types (see PYTHON-192) if not column_aliases and hasattr(comparator, 'fieldnames'): - column_aliases = filter(None, comparator.fieldnames) + column_aliases = list(filter(None, comparator.fieldnames)) else: is_compact = True if column_aliases or not col_rows or is_dct_comparator: diff --git a/cassandra/util.py b/cassandra/util.py index 6e4d888bc0..6b9a4025e7 100644 --- a/cassandra/util.py +++ b/cassandra/util.py @@ -810,7 +810,8 @@ def __str__(self): inet_ntop = socket.inet_ntop -# similar to collections.namedtuple, reproduced here because Python 2.6 did not have the rename logic +# similar to collections.namedtuple, reproduced here to handle invalid identifiers +# by renaming them to positional names def _positional_rename_invalid_identifiers(field_names): names_out = list(field_names) for index, name in enumerate(field_names): @@ -1498,7 +1499,7 @@ class Version(object): Internal minimalist class to compare versions. A valid version is: .... - TODO: when python2 support is removed, use packaging.version. + TODO: consider using packaging.version instead. """ _version = None