Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PYTHON-1366 Handle removal of asyncore in Python 3.12 #1187

Merged
merged 4 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions cassandra/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -728,3 +728,19 @@ class UnresolvableContactPoints(DriverException):
contact points, only when lookup fails for all hosts
"""
pass

class DependencyException(Exception):
"""
Specific exception class for handling issues with driver dependencies
"""

excs = []
"""
A sequence of child exceptions
"""

def __init__(self, msg, excs=[]):
complete_msg = msg
if excs:
complete_msg += ("The following exceptions were observed: \n" + '\n'.join(str(e) for e in excs))
Exception.__init__(self, complete_msg)
67 changes: 48 additions & 19 deletions cassandra/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from collections.abc import Mapping
from concurrent.futures import ThreadPoolExecutor, FIRST_COMPLETED, wait as wait_futures
from copy import copy
from functools import partial, wraps
from functools import partial, reduce, wraps
from itertools import groupby, count, chain
import json
import logging
Expand All @@ -44,7 +44,7 @@
from cassandra import (ConsistencyLevel, AuthenticationFailed,
OperationTimedOut, UnsupportedOperation,
SchemaTargetType, DriverException, ProtocolVersion,
UnresolvableContactPoints)
UnresolvableContactPoints, DependencyException)
from cassandra.auth import _proxy_execute_key, PlainTextAuthProvider
from cassandra.connection import (ConnectionException, ConnectionShutdown,
ConnectionHeartbeat, ProtocolVersionUnsupported,
Expand Down Expand Up @@ -111,6 +111,19 @@
except ImportError:
from cassandra.util import WeakSet # NOQA

def _is_gevent_monkey_patched():
if 'gevent.monkey' not in sys.modules:
return False
import gevent.socket
return socket.socket is gevent.socket.socket

def _try_gevent_import():
if _is_gevent_monkey_patched():
from cassandra.io.geventreactor import GeventConnection
return (GeventConnection,None)
else:
return (None,None)

def _is_eventlet_monkey_patched():
if 'eventlet.patcher' not in sys.modules:
return False
Expand All @@ -121,26 +134,42 @@ def _is_eventlet_monkey_patched():
except AttributeError:
return False

def _try_eventlet_import():
if _is_eventlet_monkey_patched():
from cassandra.io.eventletreactor import EventletConnection
return (EventletConnection,None)
else:
return (None,None)

def _is_gevent_monkey_patched():
if 'gevent.monkey' not in sys.modules:
return False
import gevent.socket
return socket.socket is gevent.socket.socket

def _try_libev_import():
try:
from cassandra.io.libevreactor import LibevConnection
return (LibevConnection,None)
except DependencyException as e:
return (None, e)

# default to gevent when we are monkey patched with gevent, eventlet when
# monkey patched with eventlet, otherwise if libev is available, use that as
# the default because it's fastest. Otherwise, use asyncore.
if _is_gevent_monkey_patched():
from cassandra.io.geventreactor import GeventConnection as DefaultConnection
elif _is_eventlet_monkey_patched():
from cassandra.io.eventletreactor import EventletConnection as DefaultConnection
else:
def _try_asyncore_import():
try:
from cassandra.io.libevreactor import LibevConnection as DefaultConnection # NOQA
except ImportError:
from cassandra.io.asyncorereactor import AsyncoreConnection as DefaultConnection # NOQA
from cassandra.io.asyncorereactor import AsyncoreConnection
return (AsyncoreConnection,None)
except DependencyException as e:
return (None, e)

def _connection_reduce_fn(val,import_fn):
(rv, excs) = val
# If we've already found a workable Connection class return immediately
if rv:
return val
(import_result, exc) = import_fn()
if exc:
excs.append(exc)
return (rv or import_result, excs)

conn_fns = (_try_gevent_import, _try_eventlet_import, _try_libev_import, _try_asyncore_import)
(conn_class, excs) = reduce(_connection_reduce_fn, conn_fns, (None,[]))
if excs:
raise DependencyException("Exception loading connection class dependencies", excs)
DefaultConnection = conn_class

# Forces load of utf8 encoding module to avoid deadlock that occurs
# if code that is being imported tries to import the module in a seperate
Expand Down
10 changes: 9 additions & 1 deletion cassandra/io/asyncorereactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,15 @@
except ImportError:
from cassandra.util import WeakSet # noqa

import asyncore
from cassandra import DependencyException
try:
import asyncore
except ModuleNotFoundError:
raise DependencyException(
"Unable to import asyncore module. Note that this module has been removed in Python 3.12 "
"so when using the driver with this version (or anything newer) you will need to use one of the "
"other event loop implementations."
)

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

Expand Down
4 changes: 2 additions & 2 deletions cassandra/io/libevreactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
from threading import Lock, Thread
import time


from cassandra import DependencyException
from cassandra.connection import (Connection, ConnectionShutdown,
NONBLOCKING, Timer, TimerManager)
try:
import cassandra.io.libevwrapper as libev
except ImportError:
raise ImportError(
raise DependencyException(
"The C extension needed to use libev was not found. This "
"probably means that you didn't have the required build dependencies "
"when installing the driver. See "
Expand Down
16 changes: 11 additions & 5 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import os
from concurrent.futures import ThreadPoolExecutor

from cassandra import DependencyException

log = logging.getLogger()
log.setLevel('DEBUG')
# if nose didn't already attach a log handler, add one here
Expand All @@ -32,9 +34,12 @@
def is_eventlet_monkey_patched():
if 'eventlet.patcher' not in sys.modules:
return False
import eventlet.patcher
return eventlet.patcher.is_monkey_patched('socket')

try:
import eventlet.patcher
return eventlet.patcher.is_monkey_patched('socket')
# Yet another case related to PYTHON-1364
except AttributeError:
return False

def is_gevent_monkey_patched():
if 'gevent.monkey' not in sys.modules:
Expand Down Expand Up @@ -86,17 +91,18 @@ def is_monkey_patched():
elif "asyncio" in EVENT_LOOP_MANAGER:
from cassandra.io.asyncioreactor import AsyncioConnection
connection_class = AsyncioConnection

else:
log.debug("Using default event loop (libev)")
try:
from cassandra.io.libevreactor import LibevConnection
connection_class = LibevConnection
except ImportError as e:
except DependencyException as e:
log.debug('Could not import LibevConnection, '
'using connection_class=None; '
'failed with error:\n {}'.format(
repr(e)
))
log.debug("Will attempt to set connection class at cluster initialization")
connection_class = None


Expand Down
15 changes: 10 additions & 5 deletions tests/integration/standard/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,26 @@
import time
from unittest import SkipTest

from cassandra import ConsistencyLevel, OperationTimedOut
from cassandra import ConsistencyLevel, OperationTimedOut, DependencyException
from cassandra.cluster import NoHostAvailable, ConnectionShutdown, ExecutionProfile, EXEC_PROFILE_DEFAULT
import cassandra.io.asyncorereactor
from cassandra.io.asyncorereactor import AsyncoreConnection
from cassandra.protocol import QueryMessage
from cassandra.connection import Connection
from cassandra.policies import HostFilterPolicy, RoundRobinPolicy, HostStateListener
from cassandra.pool import HostConnectionPool

from tests import is_monkey_patched
from tests.integration import use_singledc, get_node, CASSANDRA_IP, local, \
requiresmallclockgranularity, greaterthancass20, TestCluster

try:
import cassandra.io.asyncorereactor
from cassandra.io.asyncorereactor import AsyncoreConnection
except DependencyException:
AsyncoreConnection = None

try:
from cassandra.io.libevreactor import LibevConnection
import cassandra.io.libevreactor
except ImportError:
except DependencyException:
LibevConnection = None


Expand Down Expand Up @@ -440,6 +443,8 @@ class AsyncoreConnectionTests(ConnectionTests, unittest.TestCase):
def setUp(self):
if is_monkey_patched():
raise unittest.SkipTest("Can't test asyncore with monkey patching")
if AsyncoreConnection is None:
raise unittest.SkipTest('Unable to import asyncore module')
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Necessary to avoid executing this test on Python 3.12

ConnectionTests.setUp(self)

def clean_global_loop(self):
Expand Down