Skip to content

Commit 82a2f16

Browse files
committed
docs: update stale Python 2 comments and docstrings
Also ports the HashableMock thread-safety fix from PR #766 (fix/hashable-mock-thread-safety) to unblock this PR's own CI: the "Build wheels for macos-arm on macos-14" job was failing on tests/unit/test_host_connection_pool.py::HostConnectionTests::test_successful_wait_for_connection with `TypeError: __hash__ method should return an integer not 'MagicMock'`. This is a pre-existing, unrelated flaky-test bug (not introduced by this Python-2-cleanup PR); PR #766 is the canonical fix and should still land separately. MagicMixin.__init__ replaces __hash__ on the mock's type with a MagicMock object, which is not thread-safe under concurrent hash() calls (e.g. `connection in self._trash` in pool.py). Fix by restoring a plain, id-based __hash__ function on the class after super().__init__() runs.
1 parent 18b1810 commit 82a2f16

4 files changed

Lines changed: 40 additions & 12 deletions

File tree

cassandra/datastax/graph/graphson.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,25 @@
4848
------------ | -------------- | -------------- | ------------
4949
text | string | string | str
5050
boolean | | | bool
51-
bigint | g:Int64 | g:Int64 | long
51+
bigint | g:Int64 | g:Int64 | int
5252
int | g:Int32 | g:Int32 | int
5353
double | g:Double | g:Double | float
5454
float | g:Float | g:Float | float
5555
uuid | g:UUID | g:UUID | UUID
5656
bigdecimal | gx:BigDecimal | gx:BigDecimal | Decimal
5757
duration | gx:Duration | N/A | timedelta (Classic graph only)
5858
DSE Duration | N/A | dse:Duration | Duration (Core graph only)
59-
inet | gx:InetAddress | gx:InetAddress | str (unicode), IPV4Address/IPV6Address (PY3)
59+
inet | gx:InetAddress | gx:InetAddress | str, IPV4Address/IPV6Address
6060
timestamp | gx:Instant | gx:Instant | datetime.datetime
6161
date | gx:LocalDate | gx:LocalDate | datetime.date
6262
time | gx:LocalTime | gx:LocalTime | datetime.time
6363
smallint | gx:Int16 | gx:Int16 | int
64-
varint | gx:BigInteger | gx:BigInteger | long
65-
date | gx:LocalDate | gx:LocalDate | Date
64+
varint | gx:BigInteger | gx:BigInteger | int
6665
polygon | dse:Polygon | dse:Polygon | Polygon
6766
point | dse:Point | dse:Point | Point
6867
linestring | dse:Linestring | dse:LineString | LineString
69-
blob | dse:Blob | dse:Blob | bytearray, buffer (PY2), memoryview (PY3), bytes (PY3)
70-
blob | gx:ByteBuffer | gx:ByteBuffer | bytearray, buffer (PY2), memoryview (PY3), bytes (PY3)
68+
blob | dse:Blob | dse:Blob | bytearray, memoryview, bytes
69+
blob | gx:ByteBuffer | gx:ByteBuffer | bytearray, memoryview, bytes
7170
list | N/A | g:List | list (Core graph only)
7271
map | N/A | g:Map | dict (Core graph only)
7372
set | N/A | g:Set | set or list (Core graph only)
@@ -969,7 +968,7 @@ def serialize(self, value, writer=None):
969968
"""
970969
serializer = self.get_serializer(value)
971970
if not serializer:
972-
raise ValueError("Unable to find a serializer for value of type: ".format(type(value)))
971+
raise ValueError("Unable to find a serializer for value of type: {}".format(type(value)))
973972

974973
val = serializer.serialize(value, writer or self)
975974
if serializer is TypeWrapperTypeIO:

cassandra/metadata.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2270,7 +2270,7 @@ def _build_table_metadata(self, row, col_rows=None, trigger_rows=None):
22702270

22712271
# Some thrift tables define names in composite types (see PYTHON-192)
22722272
if not column_aliases and hasattr(comparator, 'fieldnames'):
2273-
column_aliases = filter(None, comparator.fieldnames)
2273+
column_aliases = list(filter(None, comparator.fieldnames))
22742274
else:
22752275
is_compact = True
22762276
if column_aliases or not col_rows or is_dct_comparator:

cassandra/util.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,8 @@ def __str__(self):
810810
inet_ntop = socket.inet_ntop
811811

812812

813-
# similar to collections.namedtuple, reproduced here because Python 2.6 did not have the rename logic
813+
# similar to collections.namedtuple, reproduced here to handle invalid identifiers
814+
# by renaming them to positional names
814815
def _positional_rename_invalid_identifiers(field_names):
815816
names_out = list(field_names)
816817
for index, name in enumerate(field_names):
@@ -1498,7 +1499,7 @@ class Version(object):
14981499
Internal minimalist class to compare versions.
14991500
A valid version is: <int>.<int>.<int>.<int or str>.
15001501
1501-
TODO: when python2 support is removed, use packaging.version.
1502+
TODO: consider using packaging.version instead.
15021503
"""
15031504

15041505
_version = None

tests/unit/util.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,34 @@ def _check_order_consistency(smaller, bigger, equal=False):
3232

3333

3434
class HashableMock(NonCallableMagicMock):
35+
"""A Mock subclass that is safely hashable and usable in sets/dicts.
3536
36-
def __hash__(self):
37-
return id(self)
37+
NonCallableMagicMock's __init__ (via MagicMixin) replaces __hash__
38+
on the *type* with a MagicMock object. That MagicMock is not
39+
thread-safe, so concurrent hash() calls on the same instance —
40+
e.g. ``connection in self._trash`` in pool.py — can raise
41+
``TypeError: __hash__ method should return an integer`` on Windows.
42+
43+
We fix this by restoring a plain function as the class-level
44+
__hash__ after super().__init__ runs, so hash() always resolves to
45+
a real function (id-based) instead of a MagicMock callable.
46+
47+
Note: NonCallableMock.__new__ already gives every mock instance its
48+
own private subclass (see cpython unittest/mock.py) specifically so
49+
that per-instance magic-method patching doesn't leak across mocks.
50+
``type(self)`` here is therefore that private, instance-specific
51+
subclass rather than the shared ``HashableMock`` class, so this
52+
assignment can't race with another ``HashableMock`` instance's
53+
__init__ call. Once __init__ returns, __hash__ is a plain function
54+
for the remaining lifetime of the instance, so it is safe to call
55+
from multiple threads with no further synchronization needed.
56+
"""
57+
58+
def __init__(self, *args, **kwargs):
59+
super().__init__(*args, **kwargs)
60+
# Restore a real __hash__ after MagicMixin overwrites it.
61+
type(self).__hash__ = HashableMock._id_hash
62+
63+
@staticmethod
64+
def _id_hash(self):
65+
return id(self)

0 commit comments

Comments
 (0)