Skip to content

Commit e7e1f68

Browse files
committed
fix(cassandra.io): fix all problems around DependencyException
Upstream introduced `DependencyException` and fixed some of the connection classes to raise it when required module is absent. It was done half-way and broke lots of tests. This commit fixes rest of the code and problems original commit created.
1 parent c33eef4 commit e7e1f68

File tree

11 files changed

+105
-124
lines changed

11 files changed

+105
-124
lines changed

benchmarks/base.py

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@
2828
sys.path.append(os.path.join(dirname, '..'))
2929

3030
import cassandra
31-
from cassandra.cluster import Cluster
32-
from cassandra.io.asyncorereactor import AsyncoreConnection
31+
from cassandra.cluster import Cluster, get_all_supported_connections_classes
3332

3433
log = logging.getLogger()
3534
handler = logging.StreamHandler()
@@ -48,31 +47,7 @@
4847
'NOTSET': logging.NOTSET,
4948
}
5049

51-
have_libev = False
52-
supported_reactors = [AsyncoreConnection]
53-
try:
54-
from cassandra.io.libevreactor import LibevConnection
55-
have_libev = True
56-
supported_reactors.append(LibevConnection)
57-
except ImportError as exc:
58-
pass
59-
60-
have_asyncio = False
61-
try:
62-
from cassandra.io.asyncioreactor import AsyncioConnection
63-
have_asyncio = True
64-
supported_reactors.append(AsyncioConnection)
65-
except (ImportError, SyntaxError):
66-
pass
67-
68-
have_twisted = False
69-
try:
70-
from cassandra.io.twistedreactor import TwistedConnection
71-
have_twisted = True
72-
supported_reactors.append(TwistedConnection)
73-
except ImportError as exc:
74-
log.exception("Error importing twisted")
75-
pass
50+
supported_reactors = get_all_supported_connections_classes()
7651

7752
KEYSPACE = "testkeyspace" + str(int(time.time()))
7853
TABLE = "testtable"
@@ -214,6 +189,15 @@ def benchmark(thread_class):
214189
log.info(" 99.9th: %0.4fs", request_timer['999percentile'])
215190

216191

192+
def get_connection_class(class_name):
193+
for cls in supported_reactors:
194+
if cls.__name__ == class_name:
195+
return cls
196+
else:
197+
log.error("unavailable reactor class: %s", class_name)
198+
sys.exit(f"{class_name} is not available")
199+
200+
217201
def parse_options():
218202
parser = OptionParser()
219203
parser.add_option('-H', '--hosts', default='127.0.0.1',
@@ -261,23 +245,15 @@ def parse_options():
261245
log.warning("Unknown log level specified: %s; specify one of %s", options.log_level, _log_levels.keys())
262246

263247
if options.asyncore_only:
264-
options.supported_reactors = [AsyncoreConnection]
248+
options.supported_reactors = [get_connection_class("AsyncoreConnection")]
265249
elif options.asyncio_only:
266-
options.supported_reactors = [AsyncioConnection]
250+
options.supported_reactors = [get_connection_class("AsyncioConnection")]
267251
elif options.libev_only:
268-
if not have_libev:
269-
log.error("libev is not available")
270-
sys.exit(1)
271-
options.supported_reactors = [LibevConnection]
252+
options.supported_reactors = [get_connection_class("LibevConnection")]
272253
elif options.twisted_only:
273-
if not have_twisted:
274-
log.error("Twisted is not available")
275-
sys.exit(1)
276-
options.supported_reactors = [TwistedConnection]
254+
options.supported_reactors = [get_connection_class("TwistedConnection")]
277255
else:
278256
options.supported_reactors = supported_reactors
279-
if not have_libev:
280-
log.warning("Not benchmarking libev reactor because libev is not available")
281257

282258
return options, args
283259

cassandra/cluster.py

Lines changed: 45 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from itertools import groupby, count, chain
3030
import json
3131
import logging
32+
from typing import NamedTuple, Type, Optional
3233
from warnings import warn
3334
from random import random
3435
import re
@@ -50,7 +51,7 @@
5051
from cassandra.connection import (ConnectionException, ConnectionShutdown,
5152
ConnectionHeartbeat, ProtocolVersionUnsupported,
5253
EndPoint, DefaultEndPoint, DefaultEndPointFactory,
53-
ContinuousPagingState, SniEndPointFactory, ConnectionBusy)
54+
ContinuousPagingState, SniEndPointFactory, ConnectionBusy, Connection)
5455
from cassandra.cqltypes import UserType
5556
import cassandra.cqltypes as types
5657
from cassandra.encoder import Encoder
@@ -103,7 +104,7 @@
103104

104105
try:
105106
from cassandra.io.eventletreactor import EventletConnection
106-
except (ImportError, AttributeError):
107+
except DependencyException:
107108
# AttributeError was add for handling python 3.12 https://github.com/eventlet/eventlet/issues/812
108109
# TODO: remove it when eventlet issue would be fixed
109110
EventletConnection = None
@@ -114,19 +115,30 @@
114115
from cassandra.util import WeakSet # NOQA
115116

116117

118+
class ClassImportResult(NamedTuple):
119+
name: str
120+
exception: Optional[Exception]
121+
connection_class: Optional[Type[Connection]]
122+
123+
117124
def _is_gevent_monkey_patched():
118125
if 'gevent.monkey' not in sys.modules:
119126
return False
120-
import gevent.socket
121-
return socket.socket is gevent.socket.socket
122-
127+
try:
128+
import gevent.socket
129+
return socket.socket is gevent.socket.socket
130+
except (ModuleNotFoundError, ImportError, AttributeError):
131+
return False
123132

124133
def _try_gevent_import():
125134
if _is_gevent_monkey_patched():
126-
from cassandra.io.geventreactor import GeventConnection
127-
return (GeventConnection,None)
135+
try:
136+
from cassandra.io.geventreactor import GeventConnection
137+
return ClassImportResult(name="GeventConnection", connection_class=GeventConnection, exception=None)
138+
except DependencyException as e:
139+
return ClassImportResult(name="GeventConnection", connection_class=None, exception=e)
128140
else:
129-
return (None,None)
141+
return ClassImportResult(name="GeventConnection", connection_class=None, exception=DependencyException("gevent is not patched"))
130142

131143

132144
def _is_eventlet_monkey_patched():
@@ -140,77 +152,56 @@ def _is_eventlet_monkey_patched():
140152
# TODO: remove it when eventlet issue would be fixed
141153
return False
142154

143-
144-
def _is_gevent_monkey_patched():
145-
if 'gevent.monkey' not in sys.modules:
146-
return False
147-
try:
148-
import eventlet.patcher
149-
return eventlet.patcher.is_monkey_patched('socket')
150-
# Another case related to PYTHON-1364
151-
except AttributeError:
152-
return False
153-
154-
155155
def _try_eventlet_import():
156-
if _is_eventlet_monkey_patched():
156+
try:
157157
from cassandra.io.eventletreactor import EventletConnection
158-
return (EventletConnection,None)
159-
else:
160-
return (None,None)
158+
except DependencyException as e:
159+
return ClassImportResult(name="EventletConnection", connection_class=None, exception=e)
160+
if _is_eventlet_monkey_patched():
161+
return ClassImportResult(name="EventletConnection", connection_class=EventletConnection, exception=None)
162+
return ClassImportResult(name="EventletConnection", connection_class=None, exception=DependencyException("eventlet is not patched"))
161163

162164
def _try_libev_import():
163165
try:
164166
from cassandra.io.libevreactor import LibevConnection
165-
return (LibevConnection,None)
167+
return ClassImportResult(name="LibevConnection", connection_class=LibevConnection, exception=None)
166168
except DependencyException as e:
167-
return (None, e)
169+
return ClassImportResult(name="LibevConnection", connection_class=None, exception=e)
168170

169171
def _try_asyncore_import():
170172
try:
171173
from cassandra.io.asyncorereactor import AsyncoreConnection
172-
return (AsyncoreConnection,None)
174+
return ClassImportResult(name="AsyncoreConnection", connection_class=AsyncoreConnection, exception=None)
173175
except DependencyException as e:
174-
return (None, e)
176+
return ClassImportResult(name="AsyncoreConnection", connection_class=None, exception=e)
175177

176178
def _try_twisted_import():
177179
try:
178180
from cassandra.io.twistedreactor import TwistedConnection
179-
return TwistedConnection, None
181+
return ClassImportResult(name="TwistedConnection", connection_class=TwistedConnection, exception=None)
180182
except DependencyException as e:
181-
return None, e
182-
183-
def _connection_reduce_fn(val,import_fn):
184-
(rv, excs) = val
185-
# If we've already found a workable Connection class return immediately
186-
if rv:
187-
return val
188-
(import_result, exc) = import_fn()
189-
if exc:
190-
excs.append(exc)
191-
return (rv or import_result, excs)
183+
return ClassImportResult(name="TwistedConnection", connection_class=None, exception=e)
184+
192185

193186
log = logging.getLogger(__name__)
194187

195-
def get_all_supported_connections_classes():
196-
classes = []
197-
excs = []
188+
189+
def load_all_connections_classes():
190+
results = []
198191
for try_fn in (_try_gevent_import, _try_eventlet_import, _try_libev_import, _try_asyncore_import, _try_twisted_import):
199-
conn, exc = try_fn()
200-
if conn is not None:
201-
classes.append(conn)
202-
else:
203-
excs.append(exc)
204-
return tuple(classes), tuple(excs)
192+
results.append(try_fn())
193+
return tuple(results)
194+
195+
def get_all_supported_connections_classes():
196+
return [res.connection_class for res in load_all_connections_classes() if res.connection_class]
205197

206198
def get_default_connection_class():
207199
excs = []
208200
for try_fn in (_try_gevent_import, _try_eventlet_import, _try_libev_import, _try_asyncore_import, _try_twisted_import):
209-
conn, exc = try_fn()
210-
if conn is not None:
211-
return conn, None
212-
else:
213-
excs.append(exc)
201+
res = try_fn()
202+
if res.connection_class:
203+
return res.connection_class, excs
204+
excs.append(res.exception)
214205
return None, tuple(excs)
215206

216207

cassandra/io/eventletreactor.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,20 @@
1515

1616
# Originally derived from MagnetoDB source:
1717
# https://github.com/stackforge/magnetodb/blob/2015.1.0b1/magnetodb/common/cassandra/io/eventletreactor.py
18-
import eventlet
19-
from eventlet.green import socket
20-
from eventlet.queue import Queue
21-
from greenlet import GreenletExit
18+
from cassandra import DependencyException
19+
20+
try:
21+
import eventlet
22+
from eventlet.green import socket
23+
from eventlet.queue import Queue
24+
except (ModuleNotFoundError, ImportError, AttributeError):
25+
raise DependencyException("Unable to import eventlet module. Try to install it via `pip install eventlet`")
26+
27+
try:
28+
from greenlet import GreenletExit
29+
except (ModuleNotFoundError, ImportError, AttributeError):
30+
raise DependencyException("Unable to import greenlet module. Try to install it via `pip install greenlet`")
31+
2232
import logging
2333
from threading import Event
2434
import time

cassandra/io/geventreactor.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,19 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
import gevent
15-
import gevent.event
16-
from gevent.queue import Queue
17-
from gevent import socket
18-
import gevent.ssl
14+
from cassandra import DependencyException
15+
16+
try:
17+
import gevent
18+
import gevent.event
19+
from gevent.queue import Queue
20+
from gevent import socket
21+
import gevent.ssl
22+
except (ImportError, ModuleNotFoundError, AttributeError):
23+
raise DependencyException(
24+
"Unable to import gevent module. This module is optional, but if you want to use GeventConnection you need to "
25+
"install it. Try to install it via `pip install gevent`."
26+
)
1927

2028
import logging
2129
import time

tests/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def is_monkey_patched():
6666
gevent.monkey.patch_all()
6767
from cassandra.io.geventreactor import GeventConnection
6868
connection_class = GeventConnection
69-
except ImportError:
69+
except DependencyException:
7070
connection_class = None
7171
elif "eventlet" in EVENT_LOOP_MANAGER:
7272
from eventlet import monkey_patch

tests/integration/long/test_ipv6.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,20 @@
1515
import os, socket, errno
1616
from ccmlib import common
1717

18+
from cassandra import DependencyException
1819
from cassandra.cluster import NoHostAvailable
1920

2021
try:
2122
from cassandra.io.asyncorereactor import AsyncoreConnection
22-
except ImportError:
23+
except DependencyException:
2324
AsyncoreConnection = None
2425

2526
from tests import is_monkey_patched
2627
from tests.integration import use_cluster, remove_cluster, TestCluster
2728

2829
try:
2930
from cassandra.io.libevreactor import LibevConnection
30-
except ImportError:
31+
except DependencyException:
3132
LibevConnection = None
3233

3334

tests/integration/standard/test_scylla_cloud.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import json
21
import logging
32
import os.path
43
from unittest import TestCase
@@ -7,18 +6,10 @@
76

87
from cassandra.policies import TokenAwarePolicy, RoundRobinPolicy, ConstantReconnectionPolicy
98
from tests.integration import use_cluster, PROTOCOL_VERSION
10-
from cassandra.cluster import Cluster, TwistedConnection
11-
from tests.integration import use_cluster
129
from cassandra.cluster import Cluster, get_all_supported_connections_classes
1310

14-
supported_connection_classes, _ = get_all_supported_connections_classes()
11+
supported_connection_classes = get_all_supported_connections_classes()
1512

16-
#from cassandra.io.geventreactor import GeventConnection
17-
#from cassandra.io.eventletreactor import EventletConnection
18-
#from cassandra.io.asyncioreactor import AsyncioConnection
19-
20-
# need to run them with specific configuration like `gevent.monkey.patch_all()` or under async functions
21-
# unsupported_connection_classes = [GeventConnection, AsyncioConnection, EventletConnection]
2213
LOGGER = logging.getLogger(__name__)
2314

2415

tests/unit/io/test_asyncorereactor.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,22 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import unittest
15+
import socket
1516

1617
from mock import patch
17-
import socket
18+
19+
from cassandra import DependencyException
20+
from tests import is_monkey_patched
21+
from tests.unit.io.utils import ReactorTestMixin, TimerTestMixin, noop_if_monkey_patched
22+
1823
try:
1924
import cassandra.io.asyncorereactor as asyncorereactor
2025
from cassandra.io.asyncorereactor import AsyncoreConnection
2126
ASYNCCORE_AVAILABLE = True
22-
except ImportError:
27+
except DependencyException:
2328
ASYNCCORE_AVAILABLE = False
2429
AsyncoreConnection = None
2530

26-
from tests import is_monkey_patched
27-
from tests.unit.io.utils import ReactorTestMixin, TimerTestMixin, noop_if_monkey_patched
28-
2931

3032
@unittest.skipIf(not ASYNCCORE_AVAILABLE, 'asyncore is deprecated')
3133
class AsyncorePatcher(unittest.TestCase):

0 commit comments

Comments
 (0)