Skip to content

Commit 8f87683

Browse files
absurdfarcedkropachev
authored andcommitted
PYTHON-1366 Handle removal of asyncore in Python 3.12 (datastax#1187)
1 parent 2d4a7c9 commit 8f87683

File tree

7 files changed

+99
-45
lines changed

7 files changed

+99
-45
lines changed

cassandra/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,7 @@ class OperationType(Enum):
735735
Read = 0
736736
Write = 1
737737

738+
738739
class RateLimitReached(ConfigurationException):
739740
'''
740741
Rate limit was exceeded for a partition affected by the request.
@@ -747,3 +748,20 @@ def __init__(self, op_type=None, rejected_by_coordinator=False):
747748
self.rejected_by_coordinator = rejected_by_coordinator
748749
message = f"[request_error_rate_limit_reached OpType={op_type.name} RejectedByCoordinator={rejected_by_coordinator}]"
749750
Exception.__init__(self, message)
751+
752+
753+
class DependencyException(Exception):
754+
"""
755+
Specific exception class for handling issues with driver dependencies
756+
"""
757+
758+
excs = []
759+
"""
760+
A sequence of child exceptions
761+
"""
762+
763+
def __init__(self, msg, excs=[]):
764+
complete_msg = msg
765+
if excs:
766+
complete_msg += ("The following exceptions were observed: \n" + '\n'.join(str(e) for e in excs))
767+
Exception.__init__(self, complete_msg)

cassandra/cluster.py

Lines changed: 52 additions & 25 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,19 @@
113113
except ImportError:
114114
from cassandra.util import WeakSet # NOQA
115115

116+
def _is_gevent_monkey_patched():
117+
if 'gevent.monkey' not in sys.modules:
118+
return False
119+
import gevent.socket
120+
return socket.socket is gevent.socket.socket
121+
122+
def _try_gevent_import():
123+
if _is_gevent_monkey_patched():
124+
from cassandra.io.geventreactor import GeventConnection
125+
return (GeventConnection,None)
126+
else:
127+
return (None,None)
128+
116129
def _is_eventlet_monkey_patched():
117130
if 'eventlet.patcher' not in sys.modules:
118131
return False
@@ -124,32 +137,46 @@ def _is_eventlet_monkey_patched():
124137
# TODO: remove it when eventlet issue would be fixed
125138
return False
126139

127-
def _is_gevent_monkey_patched():
128-
if 'gevent.monkey' not in sys.modules:
129-
return False
130-
try:
131-
import eventlet.patcher
132-
return eventlet.patcher.is_monkey_patched('socket')
133-
# Another case related to PYTHON-1364
134-
except AttributeError:
135-
return False
140+
def _try_eventlet_import():
141+
if _is_eventlet_monkey_patched():
142+
from cassandra.io.eventletreactor import EventletConnection
143+
return (EventletConnection,None)
144+
else:
145+
return (None,None)
136146

147+
def _try_libev_import():
148+
try:
149+
from cassandra.io.libevreactor import LibevConnection
150+
return (LibevConnection,None)
151+
except DependencyException as e:
152+
return (None, e)
137153

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:
154+
def _try_asyncore_import():
146155
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
156+
from cassandra.io.asyncorereactor import AsyncoreConnection
157+
return (AsyncoreConnection,None)
158+
except DependencyException as e:
159+
return (None, e)
160+
161+
def _try_asyncio_import():
162+
from cassandra.io.asyncioreactor import AsyncioConnection
163+
return (AsyncioConnection, None)
164+
165+
def _connection_reduce_fn(val,import_fn):
166+
(rv, excs) = val
167+
# If we've already found a workable Connection class return immediately
168+
if rv:
169+
return val
170+
(import_result, exc) = import_fn()
171+
if exc:
172+
excs.append(exc)
173+
return (rv or import_result, excs)
174+
175+
conn_fns = (_try_gevent_import, _try_eventlet_import, _try_libev_import, _try_asyncore_import, _try_asyncio_import)
176+
(conn_class, excs) = reduce(_connection_reduce_fn, conn_fns, (None,[]))
177+
if excs:
178+
raise DependencyException("Exception loading connection class dependencies", excs)
179+
DefaultConnection = conn_class
153180

154181
# Forces load of utf8 encoding module to avoid deadlock that occurs
155182
# 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 & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +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-
29-
try:
30-
from cassandra.io.asyncorereactor import AsyncoreConnection
31-
except ImportError:
32-
AsyncoreConnection = None
33-
3428
from cassandra.protocol import QueryMessage
35-
from cassandra.connection import Connection
3629
from cassandra.policies import HostFilterPolicy, RoundRobinPolicy, HostStateListener
3730
from cassandra.pool import HostConnectionPool
3831

3932
from tests import is_monkey_patched
4033
from tests.integration import use_singledc, get_node, CASSANDRA_IP, local, \
4134
requiresmallclockgranularity, greaterthancass20, TestCluster
4235

36+
try:
37+
import cassandra.io.asyncorereactor
38+
from cassandra.io.asyncorereactor import AsyncoreConnection
39+
except DependencyException:
40+
AsyncoreConnection = None
41+
4342
try:
4443
from cassandra.io.libevreactor import LibevConnection
4544
import cassandra.io.libevreactor
46-
except ImportError:
45+
except DependencyException:
4746
LibevConnection = None
4847

4948

@@ -447,8 +446,7 @@ def setUp(self):
447446
if is_monkey_patched():
448447
raise unittest.SkipTest("Can't test asyncore with monkey patching")
449448
if AsyncoreConnection is None:
450-
raise unittest.SkipTest(
451-
'asyncore does not appear to be installed properly')
449+
raise unittest.SkipTest('Unable to import asyncore module')
452450
ConnectionTests.setUp(self)
453451

454452
def clean_global_loop(self):

tests/integration/standard/test_scylla_cloud.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
try:
1616
from cassandra.io.libevreactor import LibevConnection
1717
supported_connection_classes += [LibevConnection]
18-
except ImportError:
18+
except DeprecationWarning:
1919
pass
2020

2121

2222
try:
2323
from cassandra.io.asyncorereactor import AsyncoreConnection
2424
supported_connection_classes += [AsyncoreConnection]
25-
except ImportError:
25+
except DeprecationWarning:
2626
pass
2727

2828
#from cassandra.io.geventreactor import GeventConnection

0 commit comments

Comments
 (0)