Skip to content

Commit ac39968

Browse files
absurdfarcedkropachev
authored andcommitted
PYTHON-1366 Handle removal of asyncore in Python 3.12 (datastax#1187)
1 parent b4b2272 commit ac39968

File tree

7 files changed

+100
-35
lines changed

7 files changed

+100
-35
lines changed

cassandra/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,3 +747,20 @@ def __init__(self, op_type=None, rejected_by_coordinator=False):
747747
self.rejected_by_coordinator = rejected_by_coordinator
748748
message = f"[request_error_rate_limit_reached OpType={op_type.name} RejectedByCoordinator={rejected_by_coordinator}]"
749749
Exception.__init__(self, message)
750+
751+
752+
class DependencyException(Exception):
753+
"""
754+
Specific exception class for handling issues with driver dependencies
755+
"""
756+
757+
excs = []
758+
"""
759+
A sequence of child exceptions
760+
"""
761+
762+
def __init__(self, msg, excs=[]):
763+
complete_msg = msg
764+
if excs:
765+
complete_msg += ("The following exceptions were observed: \n" + '\n'.join(str(e) for e in excs))
766+
Exception.__init__(self, complete_msg)

cassandra/cluster.py

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from collections.abc import Mapping
2626
from concurrent.futures import ThreadPoolExecutor, FIRST_COMPLETED, wait as wait_futures
2727
from copy import copy
28-
from functools import partial, wraps
28+
from functools import partial, reduce, wraps
2929
from itertools import groupby, count, chain
3030
import json
3131
import logging
@@ -45,7 +45,7 @@
4545
from cassandra import (ConsistencyLevel, AuthenticationFailed, InvalidRequest,
4646
OperationTimedOut, UnsupportedOperation,
4747
SchemaTargetType, DriverException, ProtocolVersion,
48-
UnresolvableContactPoints)
48+
UnresolvableContactPoints, DependencyException)
4949
from cassandra.auth import _proxy_execute_key, PlainTextAuthProvider
5050
from cassandra.connection import (ConnectionException, ConnectionShutdown,
5151
ConnectionHeartbeat, ProtocolVersionUnsupported,
@@ -113,6 +113,22 @@
113113
except ImportError:
114114
from cassandra.util import WeakSet # NOQA
115115

116+
117+
def _is_gevent_monkey_patched():
118+
if 'gevent.monkey' not in sys.modules:
119+
return False
120+
import gevent.socket
121+
return socket.socket is gevent.socket.socket
122+
123+
124+
def _try_gevent_import():
125+
if _is_gevent_monkey_patched():
126+
from cassandra.io.geventreactor import GeventConnection
127+
return (GeventConnection,None)
128+
else:
129+
return (None,None)
130+
131+
116132
def _is_eventlet_monkey_patched():
117133
if 'eventlet.patcher' not in sys.modules:
118134
return False
@@ -124,6 +140,7 @@ def _is_eventlet_monkey_patched():
124140
# TODO: remove it when eventlet issue would be fixed
125141
return False
126142

143+
127144
def _is_gevent_monkey_patched():
128145
if 'gevent.monkey' not in sys.modules:
129146
return False
@@ -135,21 +152,42 @@ def _is_gevent_monkey_patched():
135152
return False
136153

137154

138-
# default to gevent when we are monkey patched with gevent, eventlet when
139-
# monkey patched with eventlet, otherwise if libev is available, use that as
140-
# the default because it's fastest. Otherwise, use asyncore.
141-
if _is_gevent_monkey_patched():
142-
from cassandra.io.geventreactor import GeventConnection as DefaultConnection
143-
elif _is_eventlet_monkey_patched():
144-
from cassandra.io.eventletreactor import EventletConnection as DefaultConnection
145-
else:
155+
def _try_eventlet_import():
156+
if _is_eventlet_monkey_patched():
157+
from cassandra.io.eventletreactor import EventletConnection
158+
return (EventletConnection,None)
159+
else:
160+
return (None,None)
161+
162+
def _try_libev_import():
146163
try:
147-
from cassandra.io.libevreactor import LibevConnection as DefaultConnection # NOQA
148-
except ImportError:
149-
try:
150-
from cassandra.io.asyncorereactor import AsyncoreConnection as DefaultConnection # NOQA
151-
except ImportError:
152-
from cassandra.io.asyncioreactor import AsyncioConnection as DefaultConnection # NOQA
164+
from cassandra.io.libevreactor import LibevConnection
165+
return (LibevConnection,None)
166+
except DependencyException as e:
167+
return (None, e)
168+
169+
def _try_asyncore_import():
170+
try:
171+
from cassandra.io.asyncorereactor import AsyncoreConnection
172+
return (AsyncoreConnection,None)
173+
except DependencyException as e:
174+
return (None, e)
175+
176+
def _connection_reduce_fn(val,import_fn):
177+
(rv, excs) = val
178+
# If we've already found a workable Connection class return immediately
179+
if rv:
180+
return val
181+
(import_result, exc) = import_fn()
182+
if exc:
183+
excs.append(exc)
184+
return (rv or import_result, excs)
185+
186+
conn_fns = (_try_gevent_import, _try_eventlet_import, _try_libev_import, _try_asyncore_import)
187+
(conn_class, excs) = reduce(_connection_reduce_fn, conn_fns, (None,[]))
188+
if excs:
189+
raise DependencyException("Exception loading connection class dependencies", excs)
190+
DefaultConnection = conn_class
153191

154192
# Forces load of utf8 encoding module to avoid deadlock that occurs
155193
# if code that is being imported tries to import the module in a seperate

cassandra/io/asyncorereactor.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,15 @@
3030
except ImportError:
3131
from cassandra.util import WeakSet # noqa
3232

33-
import asyncore
33+
from cassandra import DependencyException
34+
try:
35+
import asyncore
36+
except ModuleNotFoundError:
37+
raise DependencyException(
38+
"Unable to import asyncore module. Note that this module has been removed in Python 3.12 "
39+
"so when using the driver with this version (or anything newer) you will need to use one of the "
40+
"other event loop implementations."
41+
)
3442

3543
from cassandra.connection import Connection, ConnectionShutdown, NONBLOCKING, Timer, TimerManager
3644

cassandra/io/libevreactor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121
from threading import Lock, Thread
2222
import time
2323

24-
24+
from cassandra import DependencyException
2525
from cassandra.connection import (Connection, ConnectionShutdown,
2626
NONBLOCKING, Timer, TimerManager)
2727
try:
2828
import cassandra.io.libevwrapper as libev
2929
except ImportError:
30-
raise ImportError(
30+
raise DependencyException(
3131
"The C extension needed to use libev was not found. This "
3232
"probably means that you didn't have the required build dependencies "
3333
"when installing the driver. See "

tests/__init__.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,19 @@
2020
import os
2121
from concurrent.futures import ThreadPoolExecutor
2222

23+
from cassandra import DependencyException
24+
2325
log = logging.getLogger()
2426

2527
def is_eventlet_monkey_patched():
2628
if 'eventlet.patcher' not in sys.modules:
2729
return False
2830
try:
2931
import eventlet.patcher
32+
return eventlet.patcher.is_monkey_patched('socket')
33+
# Yet another case related to PYTHON-1364
3034
except AttributeError:
3135
return False
32-
return eventlet.patcher.is_monkey_patched('socket')
33-
3436

3537
def is_gevent_monkey_patched():
3638
if 'gevent.monkey' not in sys.modules:
@@ -88,17 +90,18 @@ def is_monkey_patched():
8890
elif "asyncio" in EVENT_LOOP_MANAGER:
8991
from cassandra.io.asyncioreactor import AsyncioConnection
9092
connection_class = AsyncioConnection
91-
9293
else:
94+
log.debug("Using default event loop (libev)")
9395
try:
9496
from cassandra.io.libevreactor import LibevConnection
9597
connection_class = LibevConnection
96-
except ImportError as e:
98+
except DependencyException as e:
9799
log.debug('Could not import LibevConnection, '
98100
'using connection_class=None; '
99101
'failed with error:\n {}'.format(
100102
repr(e)
101103
))
104+
log.debug("Will attempt to set connection class at cluster initialization")
102105
connection_class = None
103106

104107

tests/integration/standard/test_connection.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,27 @@
2323
import time
2424
from unittest import SkipTest
2525

26-
from cassandra import ConsistencyLevel, OperationTimedOut
26+
from cassandra import ConsistencyLevel, OperationTimedOut, DependencyException
2727
from cassandra.cluster import NoHostAvailable, ConnectionShutdown, ExecutionProfile, EXEC_PROFILE_DEFAULT
2828

29-
try:
30-
from cassandra.io.asyncorereactor import AsyncoreConnection
31-
except ImportError:
32-
AsyncoreConnection = None
33-
3429
from cassandra.protocol import QueryMessage
35-
from cassandra.connection import Connection
3630
from cassandra.policies import HostFilterPolicy, RoundRobinPolicy, HostStateListener
3731
from cassandra.pool import HostConnectionPool
3832

3933
from tests import is_monkey_patched
4034
from tests.integration import use_singledc, get_node, CASSANDRA_IP, local, \
4135
requiresmallclockgranularity, greaterthancass20, TestCluster
4236

37+
try:
38+
import cassandra.io.asyncorereactor
39+
from cassandra.io.asyncorereactor import AsyncoreConnection
40+
except DependencyException:
41+
AsyncoreConnection = None
42+
4343
try:
4444
from cassandra.io.libevreactor import LibevConnection
4545
import cassandra.io.libevreactor
46-
except ImportError:
46+
except DependencyException:
4747
LibevConnection = None
4848

4949

@@ -447,8 +447,7 @@ def setUp(self):
447447
if is_monkey_patched():
448448
raise unittest.SkipTest("Can't test asyncore with monkey patching")
449449
if AsyncoreConnection is None:
450-
raise unittest.SkipTest(
451-
'asyncore does not appear to be installed properly')
450+
raise unittest.SkipTest('Unable to import asyncore module')
452451
ConnectionTests.setUp(self)
453452

454453
def clean_global_loop(self):

tests/unit/io/test_libevreactor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@
1414
import unittest
1515

1616
from mock import patch, Mock
17-
import weakref
1817
import socket
1918

19+
from cassandra import DependencyException
2020
from tests import is_monkey_patched
2121
from tests.unit.io.utils import ReactorTestMixin, TimerTestMixin, noop_if_monkey_patched
2222

2323

2424
try:
2525
from cassandra.io.libevreactor import _cleanup as libev__cleanup
2626
from cassandra.io.libevreactor import LibevConnection
27-
except ImportError:
27+
except DependencyException:
2828
LibevConnection = None # noqa
2929

3030

0 commit comments

Comments
 (0)