Skip to content

Commit 3ca41e2

Browse files
authored
PYTHON-1366 Handle removal of asyncore in Python 3.12 (#1187)
1 parent 0e25845 commit 3ca41e2

File tree

6 files changed

+96
-32
lines changed

6 files changed

+96
-32
lines changed

cassandra/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,3 +728,19 @@ class UnresolvableContactPoints(DriverException):
728728
contact points, only when lookup fails for all hosts
729729
"""
730730
pass
731+
732+
class DependencyException(Exception):
733+
"""
734+
Specific exception class for handling issues with driver dependencies
735+
"""
736+
737+
excs = []
738+
"""
739+
A sequence of child exceptions
740+
"""
741+
742+
def __init__(self, msg, excs=[]):
743+
complete_msg = msg
744+
if excs:
745+
complete_msg += ("The following exceptions were observed: \n" + '\n'.join(str(e) for e in excs))
746+
Exception.__init__(self, complete_msg)

cassandra/cluster.py

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from collections.abc import Mapping
2525
from concurrent.futures import ThreadPoolExecutor, FIRST_COMPLETED, wait as wait_futures
2626
from copy import copy
27-
from functools import partial, wraps
27+
from functools import partial, reduce, wraps
2828
from itertools import groupby, count, chain
2929
import json
3030
import logging
@@ -44,7 +44,7 @@
4444
from cassandra import (ConsistencyLevel, AuthenticationFailed,
4545
OperationTimedOut, UnsupportedOperation,
4646
SchemaTargetType, DriverException, ProtocolVersion,
47-
UnresolvableContactPoints)
47+
UnresolvableContactPoints, DependencyException)
4848
from cassandra.auth import _proxy_execute_key, PlainTextAuthProvider
4949
from cassandra.connection import (ConnectionException, ConnectionShutdown,
5050
ConnectionHeartbeat, ProtocolVersionUnsupported,
@@ -111,6 +111,19 @@
111111
except ImportError:
112112
from cassandra.util import WeakSet # NOQA
113113

114+
def _is_gevent_monkey_patched():
115+
if 'gevent.monkey' not in sys.modules:
116+
return False
117+
import gevent.socket
118+
return socket.socket is gevent.socket.socket
119+
120+
def _try_gevent_import():
121+
if _is_gevent_monkey_patched():
122+
from cassandra.io.geventreactor import GeventConnection
123+
return (GeventConnection,None)
124+
else:
125+
return (None,None)
126+
114127
def _is_eventlet_monkey_patched():
115128
if 'eventlet.patcher' not in sys.modules:
116129
return False
@@ -121,26 +134,42 @@ def _is_eventlet_monkey_patched():
121134
except AttributeError:
122135
return False
123136

137+
def _try_eventlet_import():
138+
if _is_eventlet_monkey_patched():
139+
from cassandra.io.eventletreactor import EventletConnection
140+
return (EventletConnection,None)
141+
else:
142+
return (None,None)
124143

125-
def _is_gevent_monkey_patched():
126-
if 'gevent.monkey' not in sys.modules:
127-
return False
128-
import gevent.socket
129-
return socket.socket is gevent.socket.socket
130-
144+
def _try_libev_import():
145+
try:
146+
from cassandra.io.libevreactor import LibevConnection
147+
return (LibevConnection,None)
148+
except DependencyException as e:
149+
return (None, e)
131150

132-
# default to gevent when we are monkey patched with gevent, eventlet when
133-
# monkey patched with eventlet, otherwise if libev is available, use that as
134-
# the default because it's fastest. Otherwise, use asyncore.
135-
if _is_gevent_monkey_patched():
136-
from cassandra.io.geventreactor import GeventConnection as DefaultConnection
137-
elif _is_eventlet_monkey_patched():
138-
from cassandra.io.eventletreactor import EventletConnection as DefaultConnection
139-
else:
151+
def _try_asyncore_import():
140152
try:
141-
from cassandra.io.libevreactor import LibevConnection as DefaultConnection # NOQA
142-
except ImportError:
143-
from cassandra.io.asyncorereactor import AsyncoreConnection as DefaultConnection # NOQA
153+
from cassandra.io.asyncorereactor import AsyncoreConnection
154+
return (AsyncoreConnection,None)
155+
except DependencyException as e:
156+
return (None, e)
157+
158+
def _connection_reduce_fn(val,import_fn):
159+
(rv, excs) = val
160+
# If we've already found a workable Connection class return immediately
161+
if rv:
162+
return val
163+
(import_result, exc) = import_fn()
164+
if exc:
165+
excs.append(exc)
166+
return (rv or import_result, excs)
167+
168+
conn_fns = (_try_gevent_import, _try_eventlet_import, _try_libev_import, _try_asyncore_import)
169+
(conn_class, excs) = reduce(_connection_reduce_fn, conn_fns, (None,[]))
170+
if excs:
171+
raise DependencyException("Exception loading connection class dependencies", excs)
172+
DefaultConnection = conn_class
144173

145174
# Forces load of utf8 encoding module to avoid deadlock that occurs
146175
# 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: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import os
2121
from concurrent.futures import ThreadPoolExecutor
2222

23+
from cassandra import DependencyException
24+
2325
log = logging.getLogger()
2426
log.setLevel('DEBUG')
2527
# if nose didn't already attach a log handler, add one here
@@ -32,9 +34,12 @@
3234
def is_eventlet_monkey_patched():
3335
if 'eventlet.patcher' not in sys.modules:
3436
return False
35-
import eventlet.patcher
36-
return eventlet.patcher.is_monkey_patched('socket')
37-
37+
try:
38+
import eventlet.patcher
39+
return eventlet.patcher.is_monkey_patched('socket')
40+
# Yet another case related to PYTHON-1364
41+
except AttributeError:
42+
return False
3843

3944
def is_gevent_monkey_patched():
4045
if 'gevent.monkey' not in sys.modules:
@@ -86,17 +91,18 @@ def is_monkey_patched():
8691
elif "asyncio" in EVENT_LOOP_MANAGER:
8792
from cassandra.io.asyncioreactor import AsyncioConnection
8893
connection_class = AsyncioConnection
89-
9094
else:
95+
log.debug("Using default event loop (libev)")
9196
try:
9297
from cassandra.io.libevreactor import LibevConnection
9398
connection_class = LibevConnection
94-
except ImportError as e:
99+
except DependencyException as e:
95100
log.debug('Could not import LibevConnection, '
96101
'using connection_class=None; '
97102
'failed with error:\n {}'.format(
98103
repr(e)
99104
))
105+
log.debug("Will attempt to set connection class at cluster initialization")
100106
connection_class = None
101107

102108

tests/integration/standard/test_connection.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,26 @@
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
28-
import cassandra.io.asyncorereactor
29-
from cassandra.io.asyncorereactor import AsyncoreConnection
3028
from cassandra.protocol import QueryMessage
31-
from cassandra.connection import Connection
3229
from cassandra.policies import HostFilterPolicy, RoundRobinPolicy, HostStateListener
3330
from cassandra.pool import HostConnectionPool
3431

3532
from tests import is_monkey_patched
3633
from tests.integration import use_singledc, get_node, CASSANDRA_IP, local, \
3734
requiresmallclockgranularity, greaterthancass20, TestCluster
3835

36+
try:
37+
import cassandra.io.asyncorereactor
38+
from cassandra.io.asyncorereactor import AsyncoreConnection
39+
except DependencyException:
40+
AsyncoreConnection = None
41+
3942
try:
4043
from cassandra.io.libevreactor import LibevConnection
4144
import cassandra.io.libevreactor
42-
except ImportError:
45+
except DependencyException:
4346
LibevConnection = None
4447

4548

@@ -440,6 +443,8 @@ class AsyncoreConnectionTests(ConnectionTests, unittest.TestCase):
440443
def setUp(self):
441444
if is_monkey_patched():
442445
raise unittest.SkipTest("Can't test asyncore with monkey patching")
446+
if AsyncoreConnection is None:
447+
raise unittest.SkipTest('Unable to import asyncore module')
443448
ConnectionTests.setUp(self)
444449

445450
def clean_global_loop(self):

0 commit comments

Comments
 (0)