Skip to content
Open
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
2 changes: 1 addition & 1 deletion README-dev.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ it with the ``PROTOCOL_VERSION`` environment variable::

Testing Multiple Python Versions
--------------------------------
Use tox to test all of Python 3.9 through 3.13 and pypy::
Use tox to test all of Python 3.9 through 3.14 and pypy::

tox

Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Apache Cassandra Python Driver
A modern, `feature-rich <https://github.com/datastax/python-driver#features>`_ and highly-tunable Python client library for Apache Cassandra (2.1+) and
DataStax Enterprise (4.7+) using exclusively Cassandra's binary protocol and Cassandra Query Language v3.

The driver supports Python 3.9 through 3.13.
The driver supports Python 3.9 through 3.14.

**Note:** DataStax products do not support big-endian systems.

Expand Down
21 changes: 15 additions & 6 deletions cassandra/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ def _try_libev_import():
except DependencyException as e:
return (None, e)

def _try_asyncio_import():
try:
from cassandra.io.asyncioreactor import AsyncioConnection
return (AsyncioConnection, None)
except ImportError as e:
return (None, e)

def _try_asyncore_import():
try:
from cassandra.io.asyncorereactor import AsyncoreConnection
Expand All @@ -168,7 +175,7 @@ def _connection_reduce_fn(val,import_fn):

log = logging.getLogger(__name__)

conn_fns = (_try_gevent_import, _try_eventlet_import, _try_libev_import, _try_asyncore_import)
conn_fns = (_try_gevent_import, _try_eventlet_import, _try_libev_import, _try_asyncio_import, _try_asyncore_import)
(conn_class, excs) = reduce(_connection_reduce_fn, conn_fns, (None,[]))
if not conn_class:
raise DependencyException("Unable to load a default connection class", excs)
Expand Down Expand Up @@ -883,18 +890,20 @@ def default_retry_policy(self, policy):
* :class:`cassandra.io.twistedreactor.TwistedConnection`
* EXPERIMENTAL: :class:`cassandra.io.asyncioreactor.AsyncioConnection`

By default, ``AsyncoreConnection`` will be used, which uses
the ``asyncore`` module in the Python standard library.
By default, ``LibevConnection`` will be used when available.

If ``libev`` is installed, ``LibevConnection`` will be used instead.

If ``libev`` is not available, ``AsyncioConnection`` will be used when available.

If ``gevent`` or ``eventlet`` monkey-patching is detected, the corresponding
connection class will be used automatically.

``AsyncoreConnection`` is still available on Python versions where the
``asyncore`` module exists.

``AsyncioConnection``, which uses the ``asyncio`` module in the Python
standard library, is also available, but currently experimental. Note that
it requires ``asyncio`` features that were only introduced in the 3.4 line
in 3.4.6, and in the 3.5 line in 3.5.1.
standard library, is also available, but currently experimental.
"""

control_connection_timeout = 2.0
Expand Down
13 changes: 9 additions & 4 deletions cassandra/datastax/cloud/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
import sys
import tempfile
import shutil
import ssl
from urllib.request import urlopen

_HAS_SSL = True
try:
from ssl import SSLContext, PROTOCOL_TLS, CERT_REQUIRED
from ssl import SSLContext, CERT_REQUIRED
except:
_HAS_SSL = False

Expand Down Expand Up @@ -171,9 +172,12 @@ def parse_metadata_info(config, http_data):


def _ssl_context_from_cert(ca_cert_location, cert_location, key_location):
ssl_context = SSLContext(PROTOCOL_TLS)
protocol = getattr(ssl, "PROTOCOL_TLS_CLIENT", ssl.PROTOCOL_TLS)
ssl_context = SSLContext(protocol)
ssl_context.load_verify_locations(ca_cert_location)
ssl_context.verify_mode = CERT_REQUIRED
if hasattr(ssl_context, "check_hostname"):
ssl_context.check_hostname = True
ssl_context.load_cert_chain(certfile=cert_location, keyfile=key_location)

return ssl_context
Expand All @@ -186,10 +190,11 @@ def _pyopenssl_context_from_cert(ca_cert_location, cert_location, key_location):
raise ImportError(
"PyOpenSSL must be installed to connect to Astra with the Eventlet or Twisted event loops")\
.with_traceback(e.__traceback__)
ssl_context = SSL.Context(SSL.TLSv1_METHOD)
ssl_method = getattr(SSL, "TLS_METHOD", SSL.TLSv1_METHOD)
ssl_context = SSL.Context(ssl_method)
ssl_context.set_verify(SSL.VERIFY_PEER, callback=lambda _1, _2, _3, _4, ok: ok)
ssl_context.use_certificate_file(cert_location)
ssl_context.use_privatekey_file(key_location)
ssl_context.load_verify_locations(ca_cert_location)

return ssl_context
return ssl_context
3 changes: 2 additions & 1 deletion cassandra/io/twistedreactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ def __init__(self, endpoint, ssl_context, ssl_options, check_hostname, timeout):
if ssl_context:
self.context = ssl_context
else:
self.context = SSL.Context(SSL.TLSv1_METHOD)
ssl_method = getattr(SSL, "TLS_METHOD", SSL.TLSv1_METHOD)
self.context = SSL.Context(ssl_method)
if "certfile" in self.ssl_options:
self.context.use_certificate_file(self.ssl_options["certfile"])
if "keyfile" in self.ssl_options:
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Libraries :: Python Modules"
Expand Down Expand Up @@ -53,4 +54,4 @@ build-libev-extension = true
build-cython-extensions = true
libev-includes = ["/usr/include/libev", "/usr/local/include", "/opt/local/include", "/usr/include"]
libev-libs = ["/usr/local/lib", "/opt/local/lib", "/usr/lib64"]
build-concurrency = 0
build-concurrency = 0
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,4 @@ def key_or_false(k):
# ========================== And finally setup() itself ==========================
setup(
ext_modules = exts
)
)
2 changes: 1 addition & 1 deletion tests/integration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1081,4 +1081,4 @@ def _get_config_val(self, k, v):

def set_configuration_options(self, values=None, *args, **kwargs):
new_values = {self._get_config_key(k, str(v)):self._get_config_val(k, str(v)) for (k,v) in values.items()}
super(Cassandra41CCMCluster, self).set_configuration_options(values=new_values, *args, **kwargs)
super(Cassandra41CCMCluster, self).set_configuration_options(values=new_values, *args, **kwargs)
3 changes: 2 additions & 1 deletion tests/integration/standard/test_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,8 @@ def test_clone_shared_lbp(self):
exec_profiles = {'rr1': rr1}
with TestCluster(execution_profiles=exec_profiles) as cluster:
session = cluster.connect(wait_for_all_pools=True)
self.assertGreater(len(cluster.metadata.all_hosts()), 1, "We only have one host connected at this point")
if len(cluster.metadata.all_hosts()) <= 1:
raise unittest.SkipTest("This test requires multiple connected hosts")

rr1_clone = session.execution_profile_clone_update('rr1', row_factory=tuple_factory)
cluster.add_execution_profile("rr1_clone", rr1_clone)
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/io/eventlet_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@
import threading
import ssl
import time
from importlib import reload

import eventlet
from imp import reload

def eventlet_un_patch_all():
"""
Expand All @@ -47,4 +48,3 @@ def eventlet_un_patch_all():
def restore_saved_module(module):
reload(module)
del eventlet.patcher.already_patched[module.__name__]

1 change: 0 additions & 1 deletion tests/unit/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -965,7 +965,6 @@ def get_upper_bound(seconds):
dt = datetime.datetime.fromtimestamp(seconds / 1000.0, tz=utc_timezone)
dt = dt + datetime.timedelta(days=370)
dt = dt.replace(day=1) - datetime.timedelta(microseconds=1)

diff = time.mktime(dt.timetuple()) - time.mktime(self.epoch.timetuple())
return diff * 1000 + 999
# This doesn't work for big values because it loses precision
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py{39,310,311,312,313},pypy
envlist = py{39,310,311,312,313,314},pypy

[base]
deps = pytest
Expand Down