From c7ea3c6e1a82067d5b70af2fdbe03a63c72ec810 Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Wed, 28 May 2025 12:27:26 +0300 Subject: [PATCH 001/298] docs: update ResultSet.current_rows wrt empty pages ScyllaDB may send empty pages when processing long spans of tombstones to avoid timing out; adjust the documentation not to imply that an empty page means there is no more data. --- cassandra/cluster.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index fd73803eb8..56e4377478 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -5405,8 +5405,8 @@ def has_more_pages(self): @property def current_rows(self): """ - The list of current page rows. May be empty if the result was empty, - or this is the last page. + The list of current page rows. May be empty; this does not mean + there is no more data. Use `has_more_pages()` for that. """ return self._current_rows or [] From d5834c65d472846608bdcfb8b4edc581f7b7a1b3 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sat, 31 May 2025 17:26:25 -0400 Subject: [PATCH 002/298] Remove python 3.8 support python 3.8 is EOF since 2024-10-07 1. Stop testing it 2. Stop building wheels 3. Remove from `pyproject.toml` --- .github/workflows/integration-tests.yml | 2 +- .github/workflows/lib-build-and-push.yml | 2 +- README.rst | 2 +- docs/index.rst | 2 +- docs/installation.rst | 2 +- pyproject.toml | 10 ++++------ 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 6576154aac..65c3773648 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -27,7 +27,7 @@ jobs: fail-fast: false matrix: java-version: [8] - python-version: ["3.8", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.11", "3.12", "3.13"] event_loop_manager: ["libev", "asyncio", "asyncore"] exclude: - python-version: "3.12" diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index 42b42d892c..c409ec94b8 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -98,7 +98,7 @@ jobs: echo "CIBW_TEST_COMMAND=true" >> $GITHUB_ENV; echo "CIBW_TEST_COMMAND_WINDOWS=(exit 0)" >> $GITHUB_ENV; echo "CIBW_TEST_SKIP=*" >> $GITHUB_ENV; - echo "CIBW_SKIP=cp2* cp36* pp36* cp37* pp37* *i686 *musllinux*" >> $GITHUB_ENV; + echo "CIBW_SKIP=cp2* cp36* pp36* cp37* pp37* cp38* pp38* *i686 *musllinux*" >> $GITHUB_ENV; echo "CIBW_BUILD=cp3* pp3*" >> $GITHUB_ENV; echo "CIBW_BEFORE_TEST=true" >> $GITHUB_ENV; echo "CIBW_BEFORE_TEST_WINDOWS=(exit 0)" >> $GITHUB_ENV; diff --git a/README.rst b/README.rst index 1f520ef420..f6a983a5b2 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ Scylla Enterprise (2018.1.x+) using exclusively Cassandra's binary protocol and .. image:: https://github.com/scylladb/python-driver/actions/workflows/integration-tests.yml/badge.svg?branch=master :target: https://github.com/scylladb/python-driver/actions/workflows/integration-tests.yml?query=event%3Apush+branch%3Amaster -The driver supports Python versions 3.7-3.13. +The driver supports Python versions 3.9-3.13. .. **Note:** This driver does not support big-endian systems. diff --git a/docs/index.rst b/docs/index.rst index 16712f0bf1..a928dce0f8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,7 +4,7 @@ A Python client driver for `Scylla `_. This driver works exclusively with the Cassandra Query Language v3 (CQL3) and Cassandra's native protocol. -The driver supports Python 3.6-3.12. +The driver supports Python 3.9-3.13. This driver is open source under the `Apache v2 License `_. diff --git a/docs/installation.rst b/docs/installation.rst index 27329f533c..c8bf66dfdc 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -3,7 +3,7 @@ Installation Supported Platforms ------------------- -Python versions 3.6-3.12 are supported. Both CPython (the standard Python +Python versions 3.9-3.13 are supported. Both CPython (the standard Python implementation) and `PyPy `_ are supported and tested. Linux, OSX, and Windows are supported. diff --git a/pyproject.toml b/pyproject.toml index 717c18bc90..f3069869b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ classifiers = [ 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', @@ -44,15 +43,14 @@ test = [ "sure", "scales", "pure-sasl", - "twisted[tls]; python_version >= '3.5'", - "twisted[tls]==19.2.1; python_version < '3.5'", + "twisted[tls]", "gevent>=1.0; python_version < '3.13' and platform_machine != 'i686' and platform_machine != 'win32'", "gevent==23.9.0; python_version < '3.13' and (platform_machine == 'i686' or platform_machine == 'win32')", "eventlet>=0.33.3; python_version < '3.13'", "cython", "packaging", - "futurist; python_version >= '3.7'", - "asynctest; python_version >= '3.5'", + "futurist", + "asynctest", "pyyaml", ] @@ -86,7 +84,7 @@ tag_regex = '(?P\d*?\.\d*?\.\d*?)-scylla' [tool.cibuildwheel] build-frontend = "build[uv]" environment = { CASS_DRIVER_BUILD_CONCURRENCY = "2", CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST = "yes", CFLAGS = "-g0 -O3" } -skip = ["cp2*", "cp36*", "pp36*", "cp37*", "pp37*", "*i686", "*musllinux*"] +skip = ["cp2*", "cp36*", "pp36*", "cp37*", "pp37*", "cp38*", "pp38*", "*i686", "*musllinux*"] build = ["cp3*", "pp3*"] before-test = "pip install -r {project}/test-requirements.txt" From 4ad0566ed87d7451a6f35b61fccb339813718bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 10 Jun 2025 14:28:12 +0200 Subject: [PATCH 003/298] Docs: Remove serverless For now Scylla Cloud Serverless does not exist, so there is no point in keeping its docs. If it is ever brought back, we can restore the docs from git. --- docs/index.rst | 4 --- docs/scylla-cloud-serverless.rst | 49 -------------------------------- 2 files changed, 53 deletions(-) delete mode 100644 docs/scylla-cloud-serverless.rst diff --git a/docs/index.rst b/docs/index.rst index a928dce0f8..f4abf44320 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -53,9 +53,6 @@ Contents :doc:`scylla-cloud` Connect to ScyllaDB Cloud -:doc:`scylla-cloud-serverless` - Connect to ScyllaDB Cloud Serverless - :doc:`faq` A collection of Frequently Asked Questions @@ -79,7 +76,6 @@ Contents object-mapper dates-and-times scylla-cloud - scylla-cloud-serverless faq Getting Help diff --git a/docs/scylla-cloud-serverless.rst b/docs/scylla-cloud-serverless.rst deleted file mode 100644 index 4e0bafd1b8..0000000000 --- a/docs/scylla-cloud-serverless.rst +++ /dev/null @@ -1,49 +0,0 @@ -ScyllaDB Cloud Serverless -------------------------- - -With ScyllaDB Cloud, you can deploy `serverless databases `_. -The Python driver allows you to connect to a serverless database by utilizing the connection bundle you can download via the **Connect>Python** tab in the Cloud application. -The connection bundle is a YAML file with connection and credential information for your cluster. - -Connecting to a ScyllaDB Cloud serverless database is very similar to a standard connection to a ScyllaDB database. - -Here’s a short program that connects to a ScyllaDB Cloud serverless database and prints metadata about the cluster: - -.. code-block:: python - - from cassandra.cluster import Cluster, ExecutionProfile, EXEC_PROFILE_DEFAULT - from cassandra.policies import DCAwareRoundRobinPolicy, TokenAwarePolicy - - PATH_TO_BUNDLE_YAML = '/file/downloaded/from/cloud/connect-bundle.yaml' - - - def get_cluster(): - profile = ExecutionProfile( - load_balancing_policy=TokenAwarePolicy( - DCAwareRoundRobinPolicy(local_dc='us-east-1') - ) - ) - - return Cluster( - execution_profiles={EXEC_PROFILE_DEFAULT: profile}, - scylla_cloud=PATH_TO_BUNDLE_YAML, - ) - - - print('Connecting to cluster') - cluster = get_cluster() - session = cluster.connect() - - print('Connected to cluster', cluster.metadata.cluster_name) - - print('Getting metadata') - for host in cluster.metadata.all_hosts(): - print('Datacenter: {}; Host: {}; Rack: {}'.format( - host.datacenter, host.address, host.rack) - ) - - cluster.shutdown() - -By providing the ``scylla_cloud`` parameter to the :class:`~.Cluster` constructor, -the driver can set up the connection based on the endpoint and credential information -stored in your downloaded ScyllaDB Cloud Serverless connection bundle. \ No newline at end of file From 3f7bcbb47f2ec551d47dae64dd2da0910c02e35a Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Wed, 4 Jun 2025 22:16:32 -0400 Subject: [PATCH 004/298] cluster: add application_info Implement clustr.application_info to make driver send following startup options to server: 1. `APPLICATION_NAME` - ID what application is using driver, example: repo of the application 2. `APPLICATION_VERSION` - Version of the application, example: release version or commit id of the application 3. `CLIENT_ID` - unique id of the client instance, example: pod name All strings. --- cassandra/application_info.py | 53 +++++++++++ cassandra/cluster.py | 23 +++++ cassandra/connection.py | 9 +- .../standard/test_application_info.py | 93 +++++++++++++++++++ 4 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 cassandra/application_info.py create mode 100644 tests/integration/standard/test_application_info.py diff --git a/cassandra/application_info.py b/cassandra/application_info.py new file mode 100644 index 0000000000..bdb084201a --- /dev/null +++ b/cassandra/application_info.py @@ -0,0 +1,53 @@ +# Copyright 2025 ScyllaDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from typing import Optional + + +class ApplicationInfoBase: + """ + A class that holds application information and adds it to startup message options + """ + def add_startup_options(self, options: dict[str, str]): + raise NotImplementedError() + + +class ApplicationInfo(ApplicationInfoBase): + application_name: Optional[str] + application_version: Optional[str] + client_id: Optional[str] + + def __init__( + self, + application_name: Optional[str] = None, + application_version: Optional[str] = None, + client_id: Optional[str] = None + ): + if application_name and not isinstance(application_name, str): + raise TypeError('application_name must be a string') + if application_version and not isinstance(application_version, str): + raise TypeError('application_version must be a string') + if client_id and not isinstance(client_id, str): + raise TypeError('client_id must be a string') + + self.application_name = application_name + self.application_version = application_version + self.client_id = client_id + + def add_startup_options(self, options: dict[str, str]): + if self.application_name: + options['APPLICATION_NAME'] = self.application_name + if self.application_version: + options['APPLICATION_VERSION'] = self.application_version + if self.client_id: + options['CLIENT_ID'] = self.client_id diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 56e4377478..679293a52d 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -29,6 +29,7 @@ from itertools import groupby, count, chain import json import logging +from typing import Optional from warnings import warn from random import random import re @@ -95,6 +96,7 @@ from cassandra.datastax.graph.query import _request_timeout_key, _GraphSONContextRowFactory from cassandra.datastax import cloud as dscloud from cassandra.scylla.cloud import CloudConfiguration +from cassandra.application_info import ApplicationInfoBase try: from cassandra.io.twistedreactor import TwistedConnection @@ -706,6 +708,19 @@ class Cluster(object): Setting this to :const:`False` disables compression. """ + _application_info: Optional[ApplicationInfoBase] = None + + @property + def application_info(self) -> Optional[ApplicationInfoBase]: + """ + An instance of any subclass of :class:`.application_info.ApplicationInfoBase`. + + Defaults to None + + When set makes driver sends information about application that uses driver in startup frame + """ + return self._application_info + _auth_provider = None _auth_provider_callable = None @@ -1204,6 +1219,7 @@ def __init__(self, shard_aware_options=None, metadata_request_timeout=None, column_encryption_policy=None, + application_info:Optional[ApplicationInfoBase]=None ): """ ``executor_threads`` defines the number of threads in a pool for handling asynchronous tasks such as @@ -1329,6 +1345,12 @@ def __init__(self, raise TypeError("address_translator should not be a class, it should be an instance of that class") self.address_translator = address_translator + if application_info is not None: + if not isinstance(application_info, ApplicationInfoBase): + raise TypeError( + "application_info should be an instance of any ApplicationInfoBase class") + self._application_info = application_info + if timestamp_generator is not None: if not callable(timestamp_generator): raise ValueError("timestamp_generator must be callable") @@ -1779,6 +1801,7 @@ def _make_connection_kwargs(self, endpoint, kwargs_dict): kwargs_dict.setdefault('user_type_map', self._user_types) kwargs_dict.setdefault('allow_beta_protocol_version', self.allow_beta_protocol_version) kwargs_dict.setdefault('no_compact', self.no_compact) + kwargs_dict.setdefault('application_info', self.application_info) return kwargs_dict diff --git a/cassandra/connection.py b/cassandra/connection.py index a2540a967b..e1646cafc1 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -28,7 +28,9 @@ import weakref import random import itertools +from typing import Optional +from cassandra.application_info import ApplicationInfoBase from cassandra.protocol_features import ProtocolFeatures if 'gevent.monkey' in sys.modules: @@ -761,8 +763,8 @@ class Connection(object): _is_checksumming_enabled = False _on_orphaned_stream_released = None - features = None + _application_info: Optional[ApplicationInfoBase] = None @property def _iobuf(self): @@ -774,7 +776,7 @@ def __init__(self, host='127.0.0.1', port=9042, authenticator=None, cql_version=None, protocol_version=ProtocolVersion.MAX_SUPPORTED, is_control_connection=False, user_type_map=None, connect_timeout=None, allow_beta_protocol_version=False, no_compact=False, ssl_context=None, owning_pool=None, shard_id=None, total_shards=None, - on_orphaned_stream_released=None): + on_orphaned_stream_released=None, application_info: Optional[ApplicationInfoBase] = None): # TODO next major rename host to endpoint and remove port kwarg. self.endpoint = host if isinstance(host, EndPoint) else DefaultEndPoint(host, port) @@ -797,6 +799,7 @@ def __init__(self, host='127.0.0.1', port=9042, authenticator=None, self._socket_writable = True self.orphaned_request_ids = set() self._on_orphaned_stream_released = on_orphaned_stream_released + self._application_info = application_info if ssl_options: self.ssl_options.update(self.endpoint.ssl_options or {}) @@ -1379,6 +1382,8 @@ def _handle_options_response(self, options_response): self._product_type = options_response.options.get('PRODUCT_TYPE', [None])[0] options = {} + if self._application_info: + self._application_info.add_startup_options(options) self.features.add_startup_options(options) if self.cql_version: diff --git a/tests/integration/standard/test_application_info.py b/tests/integration/standard/test_application_info.py new file mode 100644 index 0000000000..e1e0cb2ee4 --- /dev/null +++ b/tests/integration/standard/test_application_info.py @@ -0,0 +1,93 @@ +# Copyright 2025 ScyllaDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +from cassandra.application_info import ApplicationInfo +from tests.integration import TestCluster, use_single_node, remove_cluster, xfail_scylla + + +def setup_module(): + use_single_node() + + +def teardown_module(): + remove_cluster() + + +@xfail_scylla("#scylladb/scylla-enterprise#5467 - not released yet") +class ApplicationInfoTest(unittest.TestCase): + attribute_to_startup_key = { + 'application_name': 'APPLICATION_NAME', + 'application_version': 'APPLICATION_VERSION', + 'client_id': 'CLIENT_ID', + } + + def test_create_session_and_check_system_views_clients(self): + """ + Test to ensure that ApplicationInfo user provides endup in `client_options` of `system_views.clients` table + """ + + for application_info_args in [ + { + 'application_name': None, + 'application_version': None, + 'client_id': None, + }, + { + 'application_name': 'some-application-name', + 'application_version': 'some-application-version', + 'client_id': 'some-client-id', + }, + { + 'application_name': 'some-application-name', + 'application_version': None, + 'client_id': None, + }, + { + 'application_name': None, + 'application_version': 'some-application-version', + 'client_id': None, + }, + { + 'application_name': None, + 'application_version': None, + 'client_id': 'some-client-id', + }, + ]: + with self.subTest(**application_info_args): + try: + cluster = TestCluster( + application_info=ApplicationInfo( + **application_info_args + )) + + found = False + for row in cluster.connect().execute("select client_options from system_views.clients"): + if not row[0]: + continue + for attribute_key, startup_key in self.attribute_to_startup_key.items(): + expected_value = application_info_args.get(attribute_key) + if expected_value: + if row[0].get(startup_key) != expected_value: + break + else: + # Check that it is absent + if row[0].get(startup_key, None) is not None: + break + else: + found = True + assert found + finally: + cluster.shutdown() From 5c944b5865fe83ec61865c62d4ae217b3e7ceefb Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 3 Jul 2025 02:09:11 -0400 Subject: [PATCH 005/298] Update windows openssl to 3.5.1 3.4.1 was revoved from https://slproweb.com. And CICD became broken. --- .github/workflows/lib-build-and-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index c409ec94b8..c911a8ffd5 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -121,7 +121,7 @@ jobs: - name: Install OpenSSL for Windows if: runner.os == 'Windows' run: | - choco install openssl --version=3.4.1 -f -y --no-progress + choco install openssl --version=3.5.1 -f -y --no-progress - name: Install Conan if: runner.os == 'Windows' From f487a6122361d89450a1e6e6b6cdca2723fd7b0c Mon Sep 17 00:00:00 2001 From: David Garcia Date: Fri, 4 Jul 2025 17:16:39 +0100 Subject: [PATCH 006/298] docs: update theme 1.8.7 --- docs/poetry.lock | 621 +++++++++++++++++++++++++---------------------- 1 file changed, 337 insertions(+), 284 deletions(-) diff --git a/docs/poetry.lock b/docs/poetry.lock index bd62e7b50a..d918f96e57 100644 --- a/docs/poetry.lock +++ b/docs/poetry.lock @@ -65,21 +65,21 @@ dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)" [[package]] name = "beartype" -version = "0.20.2" +version = "0.21.0" description = "Unbearably fast near-real-time hybrid runtime-static type-checking in pure Python." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "beartype-0.20.2-py3-none-any.whl", hash = "sha256:5171a91ecf01438a59884f0cde37d2d5da2c992198b53d6ba31db3940f47ff04"}, - {file = "beartype-0.20.2.tar.gz", hash = "sha256:38c60c065ad99364a8c767e8a0e71ba8263d467b91414ed5dcffb7758a2e8079"}, + {file = "beartype-0.21.0-py3-none-any.whl", hash = "sha256:b6a1bd56c72f31b0a496a36cc55df6e2f475db166ad07fa4acc7e74f4c7f34c0"}, + {file = "beartype-0.21.0.tar.gz", hash = "sha256:f9a5078f5ce87261c2d22851d19b050b64f6a805439e8793aecf01ce660d3244"}, ] [package.extras] -dev = ["autoapi (>=0.9.0)", "click", "coverage (>=5.5)", "equinox ; sys_platform == \"linux\"", "jax[cpu] ; sys_platform == \"linux\"", "jaxtyping ; sys_platform == \"linux\"", "langchain", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\"", "numba ; python_version < \"3.13.0\"", "numpy ; sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera", "pydata-sphinx-theme (<=0.7.2)", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "rich-click", "sphinx", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)", "xarray"] +dev = ["autoapi (>=0.9.0)", "click", "coverage (>=5.5)", "equinox ; sys_platform == \"linux\"", "jax[cpu] ; sys_platform == \"linux\"", "jaxtyping ; sys_platform == \"linux\"", "langchain", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\"", "numba ; python_version < \"3.13.0\"", "numpy ; sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera", "pydata-sphinx-theme (<=0.7.2)", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "rich-click", "sphinx", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)", "sqlalchemy", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)", "xarray"] doc-rtd = ["autoapi (>=0.9.0)", "pydata-sphinx-theme (<=0.7.2)", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)"] -test = ["click", "coverage (>=5.5)", "equinox ; sys_platform == \"linux\"", "jax[cpu] ; sys_platform == \"linux\"", "jaxtyping ; sys_platform == \"linux\"", "langchain", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\"", "numba ; python_version < \"3.13.0\"", "numpy ; sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "rich-click", "sphinx", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)", "xarray"] -test-tox = ["click", "equinox ; sys_platform == \"linux\"", "jax[cpu] ; sys_platform == \"linux\"", "jaxtyping ; sys_platform == \"linux\"", "langchain", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\"", "numba ; python_version < \"3.13.0\"", "numpy ; sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "rich-click", "sphinx", "typing-extensions (>=3.10.0.0)", "xarray"] +test = ["click", "coverage (>=5.5)", "equinox ; sys_platform == \"linux\"", "jax[cpu] ; sys_platform == \"linux\"", "jaxtyping ; sys_platform == \"linux\"", "langchain", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\"", "numba ; python_version < \"3.13.0\"", "numpy ; sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "rich-click", "sphinx", "sqlalchemy", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)", "xarray"] +test-tox = ["click", "equinox ; sys_platform == \"linux\"", "jax[cpu] ; sys_platform == \"linux\"", "jaxtyping ; sys_platform == \"linux\"", "langchain", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\"", "numba ; python_version < \"3.13.0\"", "numpy ; sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "rich-click", "sphinx", "sqlalchemy", "typing-extensions (>=3.10.0.0)", "xarray"] test-tox-coverage = ["coverage (>=5.5)"] [[package]] @@ -107,14 +107,14 @@ lxml = ["lxml"] [[package]] name = "certifi" -version = "2025.1.31" +version = "2025.6.15" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, - {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, + {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"}, + {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"}, ] [[package]] @@ -200,116 +200,116 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.4.1" +version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, - {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, - {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, + {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, + {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, ] [[package]] name = "click" -version = "8.1.8" +version = "8.2.1" description = "Composable command line interface toolkit" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, ] [package.dependencies] @@ -394,17 +394,20 @@ six = ">=1.10.0" [[package]] name = "exceptiongroup" -version = "1.2.2" +version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main"] markers = "python_version == \"3.10\"" files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + [package.extras] test = ["pytest (>=6)"] @@ -504,67 +507,66 @@ test = ["cffi (>=1.12.2) ; platform_python_implementation == \"CPython\"", "cove [[package]] name = "greenlet" -version = "3.2.1" +version = "3.2.3" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "greenlet-3.2.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:777c1281aa7c786738683e302db0f55eb4b0077c20f1dc53db8852ffaea0a6b0"}, - {file = "greenlet-3.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3059c6f286b53ea4711745146ffe5a5c5ff801f62f6c56949446e0f6461f8157"}, - {file = "greenlet-3.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e1a40a17e2c7348f5eee5d8e1b4fa6a937f0587eba89411885a36a8e1fc29bd2"}, - {file = "greenlet-3.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5193135b3a8d0017cb438de0d49e92bf2f6c1c770331d24aa7500866f4db4017"}, - {file = "greenlet-3.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:639a94d001fe874675b553f28a9d44faed90f9864dc57ba0afef3f8d76a18b04"}, - {file = "greenlet-3.2.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fe303381e7e909e42fb23e191fc69659910909fdcd056b92f6473f80ef18543"}, - {file = "greenlet-3.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:72c9b668454e816b5ece25daac1a42c94d1c116d5401399a11b77ce8d883110c"}, - {file = "greenlet-3.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6079ae990bbf944cf66bea64a09dcb56085815630955109ffa98984810d71565"}, - {file = "greenlet-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:e63cd2035f49376a23611fbb1643f78f8246e9d4dfd607534ec81b175ce582c2"}, - {file = "greenlet-3.2.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:aa30066fd6862e1153eaae9b51b449a6356dcdb505169647f69e6ce315b9468b"}, - {file = "greenlet-3.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b0f3a0a67786facf3b907a25db80efe74310f9d63cc30869e49c79ee3fcef7e"}, - {file = "greenlet-3.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64a4d0052de53ab3ad83ba86de5ada6aeea8f099b4e6c9ccce70fb29bc02c6a2"}, - {file = "greenlet-3.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:852ef432919830022f71a040ff7ba3f25ceb9fe8f3ab784befd747856ee58530"}, - {file = "greenlet-3.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4818116e75a0dd52cdcf40ca4b419e8ce5cb6669630cb4f13a6c384307c9543f"}, - {file = "greenlet-3.2.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9afa05fe6557bce1642d8131f87ae9462e2a8e8c46f7ed7929360616088a3975"}, - {file = "greenlet-3.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5c12f0d17a88664757e81a6e3fc7c2452568cf460a2f8fb44f90536b2614000b"}, - {file = "greenlet-3.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dbb4e1aa2000852937dd8f4357fb73e3911da426df8ca9b8df5db231922da474"}, - {file = "greenlet-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:cb5ee928ce5fedf9a4b0ccdc547f7887136c4af6109d8f2fe8e00f90c0db47f5"}, - {file = "greenlet-3.2.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:0ba2811509a30e5f943be048895a983a8daf0b9aa0ac0ead526dfb5d987d80ea"}, - {file = "greenlet-3.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4245246e72352b150a1588d43ddc8ab5e306bef924c26571aafafa5d1aaae4e8"}, - {file = "greenlet-3.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7abc0545d8e880779f0c7ce665a1afc3f72f0ca0d5815e2b006cafc4c1cc5840"}, - {file = "greenlet-3.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6dcc6d604a6575c6225ac0da39df9335cc0c6ac50725063fa90f104f3dbdb2c9"}, - {file = "greenlet-3.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2273586879affca2d1f414709bb1f61f0770adcabf9eda8ef48fd90b36f15d12"}, - {file = "greenlet-3.2.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ff38c869ed30fff07f1452d9a204ece1ec6d3c0870e0ba6e478ce7c1515acf22"}, - {file = "greenlet-3.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e934591a7a4084fa10ee5ef50eb9d2ac8c4075d5c9cf91128116b5dca49d43b1"}, - {file = "greenlet-3.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:063bcf7f8ee28eb91e7f7a8148c65a43b73fbdc0064ab693e024b5a940070145"}, - {file = "greenlet-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7132e024ebeeeabbe661cf8878aac5d2e643975c4feae833142592ec2f03263d"}, - {file = "greenlet-3.2.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:e1967882f0c42eaf42282a87579685c8673c51153b845fde1ee81be720ae27ac"}, - {file = "greenlet-3.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e77ae69032a95640a5fe8c857ec7bee569a0997e809570f4c92048691ce4b437"}, - {file = "greenlet-3.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3227c6ec1149d4520bc99edac3b9bc8358d0034825f3ca7572165cb502d8f29a"}, - {file = "greenlet-3.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ddda0197c5b46eedb5628d33dad034c455ae77708c7bf192686e760e26d6a0c"}, - {file = "greenlet-3.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de62b542e5dcf0b6116c310dec17b82bb06ef2ceb696156ff7bf74a7a498d982"}, - {file = "greenlet-3.2.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c07a0c01010df42f1f058b3973decc69c4d82e036a951c3deaf89ab114054c07"}, - {file = "greenlet-3.2.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2530bfb0abcd451ea81068e6d0a1aac6dabf3f4c23c8bd8e2a8f579c2dd60d95"}, - {file = "greenlet-3.2.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1c472adfca310f849903295c351d297559462067f618944ce2650a1878b84123"}, - {file = "greenlet-3.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:24a496479bc8bd01c39aa6516a43c717b4cee7196573c47b1f8e1011f7c12495"}, - {file = "greenlet-3.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:175d583f7d5ee57845591fc30d852b75b144eb44b05f38b67966ed6df05c8526"}, - {file = "greenlet-3.2.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ecc9d33ca9428e4536ea53e79d781792cee114d2fa2695b173092bdbd8cd6d5"}, - {file = "greenlet-3.2.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f56382ac4df3860ebed8ed838f268f03ddf4e459b954415534130062b16bc32"}, - {file = "greenlet-3.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc45a7189c91c0f89aaf9d69da428ce8301b0fd66c914a499199cfb0c28420fc"}, - {file = "greenlet-3.2.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51a2f49da08cff79ee42eb22f1658a2aed60c72792f0a0a95f5f0ca6d101b1fb"}, - {file = "greenlet-3.2.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:0c68bbc639359493420282d2f34fa114e992a8724481d700da0b10d10a7611b8"}, - {file = "greenlet-3.2.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:e775176b5c203a1fa4be19f91da00fd3bff536868b77b237da3f4daa5971ae5d"}, - {file = "greenlet-3.2.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d6668caf15f181c1b82fb6406f3911696975cc4c37d782e19cb7ba499e556189"}, - {file = "greenlet-3.2.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:17964c246d4f6e1327edd95e2008988a8995ae3a7732be2f9fc1efed1f1cdf8c"}, - {file = "greenlet-3.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04b4ec7f65f0e4a1500ac475c9343f6cc022b2363ebfb6e94f416085e40dea15"}, - {file = "greenlet-3.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b38d53cf268da963869aa25a6e4cc84c1c69afc1ae3391738b2603d110749d01"}, - {file = "greenlet-3.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05a7490f74e8aabc5f29256765a99577ffde979920a2db1f3676d265a3adba41"}, - {file = "greenlet-3.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4339b202ac20a89ccd5bde0663b4d00dc62dd25cb3fb14f7f3034dec1b0d9ece"}, - {file = "greenlet-3.2.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a750f1046994b9e038b45ae237d68153c29a3a783075211fb1414a180c8324b"}, - {file = "greenlet-3.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:374ffebaa5fbd10919cd599e5cf8ee18bae70c11f9d61e73db79826c8c93d6f9"}, - {file = "greenlet-3.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b89e5d44f55372efc6072f59ced5ed1efb7b44213dab5ad7e0caba0232c6545"}, - {file = "greenlet-3.2.1-cp39-cp39-win32.whl", hash = "sha256:b7503d6b8bbdac6bbacf5a8c094f18eab7553481a1830975799042f26c9e101b"}, - {file = "greenlet-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:e98328b8b8f160925d6b1c5b1879d8e64f6bd8cf11472b7127d579da575b77d9"}, - {file = "greenlet-3.2.1.tar.gz", hash = "sha256:9f4dd4b4946b14bb3bf038f81e1d2e535b7d94f1b2a59fdba1293cd9c1a0a4d7"}, + {file = "greenlet-3.2.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b"}, + {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712"}, + {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00"}, + {file = "greenlet-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302"}, + {file = "greenlet-3.2.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5"}, + {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc"}, + {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba"}, + {file = "greenlet-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34"}, + {file = "greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb"}, + {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c"}, + {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163"}, + {file = "greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849"}, + {file = "greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b"}, + {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0"}, + {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36"}, + {file = "greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3"}, + {file = "greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141"}, + {file = "greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a"}, + {file = "greenlet-3.2.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:42efc522c0bd75ffa11a71e09cd8a399d83fafe36db250a87cf1dacfaa15dc64"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d760f9bdfe79bff803bad32b4d8ffb2c1d2ce906313fc10a83976ffb73d64ca7"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8324319cbd7b35b97990090808fdc99c27fe5338f87db50514959f8059999805"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8c37ef5b3787567d322331d5250e44e42b58c8c713859b8a04c6065f27efbf72"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ce539fb52fb774d0802175d37fcff5c723e2c7d249c65916257f0a940cee8904"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:003c930e0e074db83559edc8705f3a2d066d4aa8c2f198aff1e454946efd0f26"}, + {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7e70ea4384b81ef9e84192e8a77fb87573138aa5d4feee541d8014e452b434da"}, + {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:22eb5ba839c4b2156f18f76768233fe44b23a31decd9cc0d4cc8141c211fd1b4"}, + {file = "greenlet-3.2.3-cp39-cp39-win32.whl", hash = "sha256:4532f0d25df67f896d137431b13f4cdce89f7e3d4a96387a41290910df4d3a57"}, + {file = "greenlet-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:aaa7aae1e7f75eaa3ae400ad98f8644bb81e1dc6ba47ce8a93d3f17274e08322"}, + {file = "greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365"}, ] [package.extras] @@ -842,14 +844,14 @@ files = [ [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, - {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] [package.extras] @@ -956,19 +958,19 @@ test = ["pre-commit", "pytest"] [[package]] name = "requests" -version = "2.32.3" +version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, + {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, + {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" +charset_normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" @@ -1012,14 +1014,14 @@ six = "*" [[package]] name = "setuptools" -version = "78.1.1" +version = "79.0.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, - {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, + {file = "setuptools-79.0.1-py3-none-any.whl", hash = "sha256:e147c0549f27767ba362f9da434eab9c5dc0045d5304feb602a0af001089fc51"}, + {file = "setuptools-79.0.1.tar.gz", hash = "sha256:128ce7b8f33c3079fd1b067ecbb4051a66e8526e7b65f6cec075dfc650ddfa88"}, ] [package.extras] @@ -1069,14 +1071,14 @@ files = [ [[package]] name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +version = "3.0.1" +description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." optional = false -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" groups = ["main"] files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, + {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, + {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, ] [[package]] @@ -1188,6 +1190,21 @@ sphinx = ">=1.8" code-style = ["pre-commit (==2.12.1)"] rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] +[[package]] +name = "sphinx-last-updated-by-git" +version = "0.3.8" +description = "Get the \"last updated\" time for each Sphinx page from Git" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sphinx_last_updated_by_git-0.3.8-py3-none-any.whl", hash = "sha256:6382c8285ac1f222483a58569b78c0371af5e55f7fbf9c01e5e8a72d6fdfa499"}, + {file = "sphinx_last_updated_by_git-0.3.8.tar.gz", hash = "sha256:c145011f4609d841805b69a9300099fc02fed8f5bb9e5bcef77d97aea97b7761"}, +] + +[package.dependencies] +sphinx = ">=1.8" + [[package]] name = "sphinx-multiversion-scylla" version = "0.3.2" @@ -1223,20 +1240,20 @@ test = ["tox"] [[package]] name = "sphinx-scylladb-theme" -version = "1.8.6" +version = "1.8.7" description = "A Sphinx Theme for ScyllaDB documentation projects" optional = false python-versions = "<4.0,>=3.10" groups = ["main"] files = [ - {file = "sphinx_scylladb_theme-1.8.6-py3-none-any.whl", hash = "sha256:002de443f4c240131366d7a7d96d362d2753e479fe0d935f4752d93041c8f074"}, - {file = "sphinx_scylladb_theme-1.8.6.tar.gz", hash = "sha256:3d3b3e731fd008d9b4240b43227272d90165a159db20c1a5eba8dd0d0778fef0"}, + {file = "sphinx_scylladb_theme-1.8.7-py3-none-any.whl", hash = "sha256:64c86e86737e16d8bbdbec492622865ec1e9c0c3a5915d747a9c109fd69145f1"}, + {file = "sphinx_scylladb_theme-1.8.7.tar.gz", hash = "sha256:7b84fc99e1156ebf14149f5c1f88b61b5ea852e367fb3940eb99f514db0a6c41"}, ] [package.dependencies] beautifulsoup4 = ">=4.12.3,<5.0.0" pyyaml = ">=6.0.1,<7.0.0" -setuptools = ">=70.1.1,<79.0.0" +setuptools = ">=70.1.1,<80.0.0" sphinx-collapse = ">=0.1.1,<0.2.0" sphinx-copybutton = ">=0.5.2,<0.6.0" sphinx-notfound-page = ">=1.0.4,<2.0.0" @@ -1246,21 +1263,21 @@ sphinxcontrib-mermaid = ">=1.0.0,<2.0.0" [[package]] name = "sphinx-sitemap" -version = "2.6.0" +version = "2.7.2" description = "Sitemap generator for Sphinx" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "sphinx_sitemap-2.6.0-py3-none-any.whl", hash = "sha256:7478e417d141f99c9af27ccd635f44c03a471a08b20e778a0f9daef7ace1d30b"}, - {file = "sphinx_sitemap-2.6.0.tar.gz", hash = "sha256:5e0c66b9f2e371ede80c659866a9eaad337d46ab02802f9c7e5f7bc5893c28d2"}, + {file = "sphinx_sitemap-2.7.2-py3-none-any.whl", hash = "sha256:1a6a8dcecb0ffb85fd37678f785cfcc40adfe3eebafb05e678971e5260b117e4"}, + {file = "sphinx_sitemap-2.7.2.tar.gz", hash = "sha256:819e028e27579b47efa0e2f863b87136b711c45f13e84730610e80316f6883da"}, ] [package.dependencies] -sphinx = ">=1.2" +sphinx-last-updated-by-git = "*" [package.extras] -dev = ["build", "flake8", "pre-commit", "pytest", "sphinx", "tox"] +dev = ["build", "flake8", "pre-commit", "pytest", "sphinx", "sphinx-last-updated-by-git", "tox"] [[package]] name = "sphinx-substitution-extensions" @@ -1449,18 +1466,19 @@ test = ["pytest"] [[package]] name = "starlette" -version = "0.46.2" +version = "0.47.1" description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35"}, - {file = "starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5"}, + {file = "starlette-0.47.1-py3-none-any.whl", hash = "sha256:5e11c9f5c7c3f24959edbf2dffdc01bba860228acf657129467d8a7468591527"}, + {file = "starlette-0.47.1.tar.gz", hash = "sha256:aef012dd2b6be325ffa16698f9dc533614fb1cebd593a906b90dc1025529a79b"}, ] [package.dependencies] anyio = ">=3.6.2,<5" +typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} [package.extras] full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] @@ -1525,14 +1543,14 @@ files = [ [[package]] name = "typer" -version = "0.15.2" +version = "0.16.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc"}, - {file = "typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5"}, + {file = "typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855"}, + {file = "typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b"}, ] [package.dependencies] @@ -1543,26 +1561,26 @@ typing-extensions = ">=3.7.4.3" [[package]] name = "typing-extensions" -version = "4.13.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, - {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] [[package]] name = "urllib3" -version = "2.4.0" +version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, - {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] @@ -1573,14 +1591,14 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uvicorn" -version = "0.34.2" +version = "0.35.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403"}, - {file = "uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328"}, + {file = "uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a"}, + {file = "uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01"}, ] [package.dependencies] @@ -1589,87 +1607,122 @@ h11 = ">=0.8" typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] -standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "watchfiles" -version = "1.0.5" +version = "1.1.0" description = "Simple, modern and high performance file watching and code reload in python." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "watchfiles-1.0.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5c40fe7dd9e5f81e0847b1ea64e1f5dd79dd61afbedb57759df06767ac719b40"}, - {file = "watchfiles-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c0db396e6003d99bb2d7232c957b5f0b5634bbd1b24e381a5afcc880f7373fb"}, - {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b551d4fb482fc57d852b4541f911ba28957d051c8776e79c3b4a51eb5e2a1b11"}, - {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:830aa432ba5c491d52a15b51526c29e4a4b92bf4f92253787f9726fe01519487"}, - {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a16512051a822a416b0d477d5f8c0e67b67c1a20d9acecb0aafa3aa4d6e7d256"}, - {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe0cbc787770e52a96c6fda6726ace75be7f840cb327e1b08d7d54eadc3bc85"}, - {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d363152c5e16b29d66cbde8fa614f9e313e6f94a8204eaab268db52231fe5358"}, - {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee32c9a9bee4d0b7bd7cbeb53cb185cf0b622ac761efaa2eba84006c3b3a614"}, - {file = "watchfiles-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29c7fd632ccaf5517c16a5188e36f6612d6472ccf55382db6c7fe3fcccb7f59f"}, - {file = "watchfiles-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e637810586e6fe380c8bc1b3910accd7f1d3a9a7262c8a78d4c8fb3ba6a2b3d"}, - {file = "watchfiles-1.0.5-cp310-cp310-win32.whl", hash = "sha256:cd47d063fbeabd4c6cae1d4bcaa38f0902f8dc5ed168072874ea11d0c7afc1ff"}, - {file = "watchfiles-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:86c0df05b47a79d80351cd179893f2f9c1b1cae49d96e8b3290c7f4bd0ca0a92"}, - {file = "watchfiles-1.0.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:237f9be419e977a0f8f6b2e7b0475ababe78ff1ab06822df95d914a945eac827"}, - {file = "watchfiles-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0da39ff917af8b27a4bdc5a97ac577552a38aac0d260a859c1517ea3dc1a7c4"}, - {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cfcb3952350e95603f232a7a15f6c5f86c5375e46f0bd4ae70d43e3e063c13d"}, - {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b2dddba7a4e6151384e252a5632efcaa9bc5d1c4b567f3cb621306b2ca9f63"}, - {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cf944fcfc394c5f9de794ce581914900f82ff1f855326f25ebcf24d5397418"}, - {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf6cd9f83d7c023b1aba15d13f705ca7b7d38675c121f3cc4a6e25bd0857ee9"}, - {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:852de68acd6212cd6d33edf21e6f9e56e5d98c6add46f48244bd479d97c967c6"}, - {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5730f3aa35e646103b53389d5bc77edfbf578ab6dab2e005142b5b80a35ef25"}, - {file = "watchfiles-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:18b3bd29954bc4abeeb4e9d9cf0b30227f0f206c86657674f544cb032296acd5"}, - {file = "watchfiles-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba5552a1b07c8edbf197055bc9d518b8f0d98a1c6a73a293bc0726dce068ed01"}, - {file = "watchfiles-1.0.5-cp311-cp311-win32.whl", hash = "sha256:2f1fefb2e90e89959447bc0420fddd1e76f625784340d64a2f7d5983ef9ad246"}, - {file = "watchfiles-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b6e76ceb1dd18c8e29c73f47d41866972e891fc4cc7ba014f487def72c1cf096"}, - {file = "watchfiles-1.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:266710eb6fddc1f5e51843c70e3bebfb0f5e77cf4f27129278c70554104d19ed"}, - {file = "watchfiles-1.0.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5eb568c2aa6018e26da9e6c86f3ec3fd958cee7f0311b35c2630fa4217d17f2"}, - {file = "watchfiles-1.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a04059f4923ce4e856b4b4e5e783a70f49d9663d22a4c3b3298165996d1377f"}, - {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e380c89983ce6e6fe2dd1e1921b9952fb4e6da882931abd1824c092ed495dec"}, - {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe43139b2c0fdc4a14d4f8d5b5d967f7a2777fd3d38ecf5b1ec669b0d7e43c21"}, - {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee0822ce1b8a14fe5a066f93edd20aada932acfe348bede8aa2149f1a4489512"}, - {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0dbcb1c2d8f2ab6e0a81c6699b236932bd264d4cef1ac475858d16c403de74d"}, - {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2014a2b18ad3ca53b1f6c23f8cd94a18ce930c1837bd891262c182640eb40a6"}, - {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6ae86d5cb647bf58f9f655fcf577f713915a5d69057a0371bc257e2553234"}, - {file = "watchfiles-1.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1a7bac2bde1d661fb31f4d4e8e539e178774b76db3c2c17c4bb3e960a5de07a2"}, - {file = "watchfiles-1.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ab626da2fc1ac277bbf752446470b367f84b50295264d2d313e28dc4405d663"}, - {file = "watchfiles-1.0.5-cp312-cp312-win32.whl", hash = "sha256:9f4571a783914feda92018ef3901dab8caf5b029325b5fe4558c074582815249"}, - {file = "watchfiles-1.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:360a398c3a19672cf93527f7e8d8b60d8275119c5d900f2e184d32483117a705"}, - {file = "watchfiles-1.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:1a2902ede862969077b97523987c38db28abbe09fb19866e711485d9fbf0d417"}, - {file = "watchfiles-1.0.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0b289572c33a0deae62daa57e44a25b99b783e5f7aed81b314232b3d3c81a11d"}, - {file = "watchfiles-1.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a056c2f692d65bf1e99c41045e3bdcaea3cb9e6b5a53dcaf60a5f3bd95fc9763"}, - {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9dca99744991fc9850d18015c4f0438865414e50069670f5f7eee08340d8b40"}, - {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:894342d61d355446d02cd3988a7326af344143eb33a2fd5d38482a92072d9563"}, - {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab44e1580924d1ffd7b3938e02716d5ad190441965138b4aa1d1f31ea0877f04"}, - {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6f9367b132078b2ceb8d066ff6c93a970a18c3029cea37bfd7b2d3dd2e5db8f"}, - {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2e55a9b162e06e3f862fb61e399fe9f05d908d019d87bf5b496a04ef18a970a"}, - {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0125f91f70e0732a9f8ee01e49515c35d38ba48db507a50c5bdcad9503af5827"}, - {file = "watchfiles-1.0.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:13bb21f8ba3248386337c9fa51c528868e6c34a707f729ab041c846d52a0c69a"}, - {file = "watchfiles-1.0.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:839ebd0df4a18c5b3c1b890145b5a3f5f64063c2a0d02b13c76d78fe5de34936"}, - {file = "watchfiles-1.0.5-cp313-cp313-win32.whl", hash = "sha256:4a8ec1e4e16e2d5bafc9ba82f7aaecfeec990ca7cd27e84fb6f191804ed2fcfc"}, - {file = "watchfiles-1.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:f436601594f15bf406518af922a89dcaab416568edb6f65c4e5bbbad1ea45c11"}, - {file = "watchfiles-1.0.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2cfb371be97d4db374cba381b9f911dd35bb5f4c58faa7b8b7106c8853e5d225"}, - {file = "watchfiles-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a3904d88955fda461ea2531fcf6ef73584ca921415d5cfa44457a225f4a42bc1"}, - {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b7a21715fb12274a71d335cff6c71fe7f676b293d322722fe708a9ec81d91f5"}, - {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dfd6ae1c385ab481766b3c61c44aca2b3cd775f6f7c0fa93d979ddec853d29d5"}, - {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b659576b950865fdad31fa491d31d37cf78b27113a7671d39f919828587b429b"}, - {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1909e0a9cd95251b15bff4261de5dd7550885bd172e3536824bf1cf6b121e200"}, - {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:832ccc221927c860e7286c55c9b6ebcc0265d5e072f49c7f6456c7798d2b39aa"}, - {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85fbb6102b3296926d0c62cfc9347f6237fb9400aecd0ba6bbda94cae15f2b3b"}, - {file = "watchfiles-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:15ac96dd567ad6c71c71f7b2c658cb22b7734901546cd50a475128ab557593ca"}, - {file = "watchfiles-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b6227351e11c57ae997d222e13f5b6f1f0700d84b8c52304e8675d33a808382"}, - {file = "watchfiles-1.0.5-cp39-cp39-win32.whl", hash = "sha256:974866e0db748ebf1eccab17862bc0f0303807ed9cda465d1324625b81293a18"}, - {file = "watchfiles-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:9848b21ae152fe79c10dd0197304ada8f7b586d3ebc3f27f43c506e5a52a863c"}, - {file = "watchfiles-1.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f59b870db1f1ae5a9ac28245707d955c8721dd6565e7f411024fa374b5362d1d"}, - {file = "watchfiles-1.0.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9475b0093767e1475095f2aeb1d219fb9664081d403d1dff81342df8cd707034"}, - {file = "watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc533aa50664ebd6c628b2f30591956519462f5d27f951ed03d6c82b2dfd9965"}, - {file = "watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed1cd825158dcaae36acce7b2db33dcbfd12b30c34317a88b8ed80f0541cc57"}, - {file = "watchfiles-1.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:554389562c29c2c182e3908b149095051f81d28c2fec79ad6c8997d7d63e0009"}, - {file = "watchfiles-1.0.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a74add8d7727e6404d5dc4dcd7fac65d4d82f95928bbee0cf5414c900e86773e"}, - {file = "watchfiles-1.0.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb1489f25b051a89fae574505cc26360c8e95e227a9500182a7fe0afcc500ce0"}, - {file = "watchfiles-1.0.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0901429650652d3f0da90bad42bdafc1f9143ff3605633c455c999a2d786cac"}, - {file = "watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9"}, + {file = "watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc"}, + {file = "watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9"}, + {file = "watchfiles-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72"}, + {file = "watchfiles-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc"}, + {file = "watchfiles-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587"}, + {file = "watchfiles-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82"}, + {file = "watchfiles-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2"}, + {file = "watchfiles-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f"}, + {file = "watchfiles-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4"}, + {file = "watchfiles-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d"}, + {file = "watchfiles-1.1.0-cp311-cp311-win32.whl", hash = "sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2"}, + {file = "watchfiles-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12"}, + {file = "watchfiles-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a"}, + {file = "watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179"}, + {file = "watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f"}, + {file = "watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4"}, + {file = "watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f"}, + {file = "watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd"}, + {file = "watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47"}, + {file = "watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6"}, + {file = "watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30"}, + {file = "watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c"}, + {file = "watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b"}, + {file = "watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb"}, + {file = "watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9"}, + {file = "watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7"}, + {file = "watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5"}, + {file = "watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1"}, + {file = "watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20"}, + {file = "watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef"}, + {file = "watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb"}, + {file = "watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297"}, + {file = "watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e"}, + {file = "watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b"}, + {file = "watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259"}, + {file = "watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f"}, + {file = "watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147"}, + {file = "watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8"}, + {file = "watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db"}, + {file = "watchfiles-1.1.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:865c8e95713744cf5ae261f3067861e9da5f1370ba91fc536431e29b418676fa"}, + {file = "watchfiles-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42f92befc848bb7a19658f21f3e7bae80d7d005d13891c62c2cd4d4d0abb3433"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa0cc8365ab29487eb4f9979fd41b22549853389e22d5de3f134a6796e1b05a4"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90ebb429e933645f3da534c89b29b665e285048973b4d2b6946526888c3eb2c7"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c588c45da9b08ab3da81d08d7987dae6d2a3badd63acdb3e206a42dbfa7cb76f"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c55b0f9f68590115c25272b06e63f0824f03d4fc7d6deed43d8ad5660cabdbf"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd17a1e489f02ce9117b0de3c0b1fab1c3e2eedc82311b299ee6b6faf6c23a29"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da71945c9ace018d8634822f16cbc2a78323ef6c876b1d34bbf5d5222fd6a72e"}, + {file = "watchfiles-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:51556d5004887045dba3acdd1fdf61dddea2be0a7e18048b5e853dcd37149b86"}, + {file = "watchfiles-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04e4ed5d1cd3eae68c89bcc1a485a109f39f2fd8de05f705e98af6b5f1861f1f"}, + {file = "watchfiles-1.1.0-cp39-cp39-win32.whl", hash = "sha256:c600e85f2ffd9f1035222b1a312aff85fd11ea39baff1d705b9b047aad2ce267"}, + {file = "watchfiles-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3aba215958d88182e8d2acba0fdaf687745180974946609119953c0e112397dc"}, + {file = "watchfiles-1.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5"}, + {file = "watchfiles-1.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d"}, + {file = "watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea"}, + {file = "watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6"}, + {file = "watchfiles-1.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3"}, + {file = "watchfiles-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c"}, + {file = "watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432"}, + {file = "watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792"}, + {file = "watchfiles-1.1.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7b3443f4ec3ba5aa00b0e9fa90cf31d98321cbff8b925a7c7b84161619870bc9"}, + {file = "watchfiles-1.1.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7049e52167fc75fc3cc418fc13d39a8e520cbb60ca08b47f6cedb85e181d2f2a"}, + {file = "watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54062ef956807ba806559b3c3d52105ae1827a0d4ab47b621b31132b6b7e2866"}, + {file = "watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a7bd57a1bb02f9d5c398c0c1675384e7ab1dd39da0ca50b7f09af45fa435277"}, + {file = "watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575"}, ] [package.dependencies] @@ -1756,14 +1809,14 @@ files = [ [[package]] name = "zope-event" -version = "5.0" +version = "5.1" description = "Very basic event publishing system" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26"}, - {file = "zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"}, + {file = "zope_event-5.1-py3-none-any.whl", hash = "sha256:53de8f0e9f61dc0598141ac591f49b042b6d74784dab49971b9cc91d0f73a7df"}, + {file = "zope_event-5.1.tar.gz", hash = "sha256:a153660e0c228124655748e990396b9d8295d6e4f546fa1b34f3319e1c666e7f"}, ] [package.dependencies] From b9be0679756f11440585ab316c8557797a49b76e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 4 Jul 2025 14:25:43 +0200 Subject: [PATCH 007/298] Don't call setup.py for building docs Calling setup.py from command line is deprecated by Python for a very long time now. --- .github/workflows/docs-pages.yaml | 2 -- .github/workflows/docs-pr.yaml | 2 -- docs/poetry.lock | 37 ++++++++++++++++++++++++------- docs/pyproject.toml | 5 +++-- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/.github/workflows/docs-pages.yaml b/.github/workflows/docs-pages.yaml index 4ff1f18d54..31f8dc74c5 100644 --- a/.github/workflows/docs-pages.yaml +++ b/.github/workflows/docs-pages.yaml @@ -27,8 +27,6 @@ jobs: python-version: '3.10' - name: Set up env run: make -C docs setupenv - - name: Build driver - run: python setup.py develop - name: Build docs run: make -C docs multiversion - name: Build redirects diff --git a/.github/workflows/docs-pr.yaml b/.github/workflows/docs-pr.yaml index dd9f9de66e..28a74f2e58 100644 --- a/.github/workflows/docs-pr.yaml +++ b/.github/workflows/docs-pr.yaml @@ -26,7 +26,5 @@ jobs: python-version: '3.10' - name: Set up env run: make -C docs setupenv - - name: Build driver - run: python setup.py develop - name: Build docs run: make -C docs test diff --git a/docs/poetry.lock b/docs/poetry.lock index d918f96e57..b1d2ed96e3 100644 --- a/docs/poetry.lock +++ b/docs/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "aenum" @@ -425,19 +425,18 @@ files = [ [[package]] name = "geomet" -version = "0.2.1.post1" -description = "GeoJSON <-> WKT/WKB conversion utilities" +version = "1.1.0" +description = "Pure Python conversion library for common geospatial data formats" optional = false -python-versions = ">2.6, !=3.3.*, <4" +python-versions = ">=3.7" groups = ["main"] files = [ - {file = "geomet-0.2.1.post1-py3-none-any.whl", hash = "sha256:a41a1e336b381416d6cbed7f1745c848e91defaa4d4c1bdc1312732e46ffad2b"}, - {file = "geomet-0.2.1.post1.tar.gz", hash = "sha256:91d754f7c298cbfcabd3befdb69c641c27fe75e808b27aa55028605761d17e95"}, + {file = "geomet-1.1.0-py3-none-any.whl", hash = "sha256:4372fe4e286a34acc6f2e9308284850bd8c4aa5bc12065e2abbd4995900db12f"}, + {file = "geomet-1.1.0.tar.gz", hash = "sha256:51e92231a0ef6aaa63ac20c443377ba78a303fd2ecd179dc3567de79f3c11605"}, ] [package.dependencies] click = "*" -six = "*" [[package]] name = "gevent" @@ -1012,6 +1011,28 @@ files = [ [package.dependencies] six = "*" +[[package]] +name = "scylla-driver" +version = "3.29.3" +description = "Scylla Driver for Apache Cassandra" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [] +develop = true + +[package.dependencies] +geomet = ">=1.1" +pyyaml = ">5.0" + +[package.extras] +cle = ["cryptography (>=35.0)"] +graph = ["gremlinpython (==3.4.6)"] + +[package.source] +type = "directory" +url = ".." + [[package]] name = "setuptools" version = "79.0.1" @@ -1884,4 +1905,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "302d62881c3c0d5ae60560928810117c52594d173faf903ba5d3cfeb49554dd3" +content-hash = "432ab6b3744422cd7526b9d863d4f2de57fec071049ecde4b690e15b6dd36551" diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 205b142c76..3d178f1936 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -8,13 +8,12 @@ package-mode = false [tool.poetry.dependencies] eventlet = "^0.33.3" futures = "2.2.0" -geomet = ">=0.1,<0.3" gevent = "^23.9.1" gremlinpython = "3.4.7" python = "^3.10" pygments = "^2.18.0" recommonmark = "0.7.1" -redirects_cli ="~0.1.2" +redirects_cli = "~0.1.2" sphinx-autobuild = "^2024.4.19" sphinx-sitemap = "^2.6.0" sphinx-scylladb-theme = "^1.8.1" @@ -23,6 +22,8 @@ Sphinx = "^7.3.7" scales = "^1.0.9" six = ">=1.9" tornado = ">=4.0,<5.0" +scylla-driver = { path = "../", develop = true } + [build-system] requires = ["poetry>=1.8.0"] From 03b795b5dc152cfc3ce77fa13e4066c306f692a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 22 May 2025 15:17:06 +0200 Subject: [PATCH 008/298] Format pyproject.toml --- pyproject.toml | 61 +++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f3069869b4..c39452a4eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,7 @@ [project] name = "scylla-driver" description = "Scylla Driver for Apache Cassandra" -authors = [ - {name = "ScyllaDB"}, -] +authors = [{ name = "ScyllaDB" }] keywords = ["cassandra", "cql", "orm", "dse", "graph"] classifiers = [ 'Development Status :: 5 - Production/Stable', @@ -19,12 +17,9 @@ classifiers = [ 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', - 'Topic :: Software Development :: Libraries :: Python Modules' -] -dependencies = [ - 'geomet>=1.1', - 'pyyaml > 5.0' + 'Topic :: Software Development :: Libraries :: Python Modules', ] +dependencies = ['geomet>=1.1', 'pyyaml > 5.0'] dynamic = ["version", "readme"] [project.urls] @@ -34,7 +29,7 @@ dynamic = ["version", "readme"] "Issues" = "https://github.com/scylladb/python-driver/issues" [project.optional-dependencies] -graph = ['gremlinpython==3.4.6'] +graph = ['gremlinpython==3.4.6'] cle = ['cryptography>=35.0'] test = [ "pytest", @@ -56,22 +51,26 @@ test = [ [tool.setuptools] include-package-data = true -packages=[ - 'cassandra', 'cassandra.io', 'cassandra.cqlengine', 'cassandra.graph', - 'cassandra.datastax', 'cassandra.datastax.insights', 'cassandra.datastax.graph', - 'cassandra.datastax.graph.fluent', 'cassandra.datastax.cloud', 'cassandra.scylla', - 'cassandra.column_encryption' +packages = [ + 'cassandra', + 'cassandra.io', + 'cassandra.cqlengine', + 'cassandra.graph', + 'cassandra.datastax', + 'cassandra.datastax.insights', + 'cassandra.datastax.graph', + 'cassandra.datastax.graph.fluent', + 'cassandra.datastax.cloud', + 'cassandra.scylla', + 'cassandra.column_encryption', ] [tool.setuptools.dynamic] -version = {attr = "cassandra.__version__"} # any module attribute compatible with ast.literal_eval -readme = {file = "README.rst", content-type = "text/x-rst"} +version = { attr = "cassandra.__version__" } # any module attribute compatible with ast.literal_eval +readme = { file = "README.rst", content-type = "text/x-rst" } [build-system] -requires = [ - "setuptools>=42", - "Cython", -] +requires = ["setuptools>=42", "Cython"] build-backend = "setuptools.build_meta" @@ -84,7 +83,17 @@ tag_regex = '(?P\d*?\.\d*?\.\d*?)-scylla' [tool.cibuildwheel] build-frontend = "build[uv]" environment = { CASS_DRIVER_BUILD_CONCURRENCY = "2", CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST = "yes", CFLAGS = "-g0 -O3" } -skip = ["cp2*", "cp36*", "pp36*", "cp37*", "pp37*", "cp38*", "pp38*", "*i686", "*musllinux*"] +skip = [ + "cp2*", + "cp36*", + "pp36*", + "cp37*", + "pp37*", + "cp38*", + "pp38*", + "*i686", + "*musllinux*", +] build = ["cp3*", "pp3*"] before-test = "pip install -r {project}/test-requirements.txt" @@ -98,24 +107,24 @@ manylinux-pypy_aarch64-image = "manylinux_2_28" before-build = "rm -rf ~/.pyxbld && rpm --import https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux && yum install -y libffi-devel libev libev-devel openssl openssl-devel" test-command = [ - "pytest {package}/tests/unit", - "EVENT_LOOP_MANAGER=gevent pytest {package}/tests/unit/io/test_geventreactor.py", + "pytest {package}/tests/unit", + "EVENT_LOOP_MANAGER=gevent pytest {package}/tests/unit/io/test_geventreactor.py", ] [tool.cibuildwheel.macos] build-frontend = "build" test-command = [ - "pytest {project}/tests/unit -k 'not (test_multi_timer_validation or test_empty_connections or test_timer_cancellation)'" + "pytest {project}/tests/unit -k 'not (test_multi_timer_validation or test_empty_connections or test_timer_cancellation)'", ] [tool.cibuildwheel.windows] build-frontend = "build" test-command = [ - "pytest {project}/tests/unit -k \"not (test_deserialize_date_range_year or test_datetype or test_libevreactor)\"" + "pytest {project}/tests/unit -k \"not (test_deserialize_date_range_year or test_datetype or test_libevreactor)\"", ] # TODO: set CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST to yes when https://github.com/scylladb/python-driver/issues/429 is fixed -environment = { CASS_DRIVER_BUILD_CONCURRENCY = "2", CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST = "no"} +environment = { CASS_DRIVER_BUILD_CONCURRENCY = "2", CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST = "no" } [[tool.cibuildwheel.overrides]] select = "pp*" From 97e44573b933c89087c59fec813789b453fedca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 22 May 2025 15:17:29 +0200 Subject: [PATCH 009/298] pyproject.toml: Use license field --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c39452a4eb..01a3e20817 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,6 @@ keywords = ["cassandra", "cql", "orm", "dse", "graph"] classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', @@ -21,6 +20,7 @@ classifiers = [ ] dependencies = ['geomet>=1.1', 'pyyaml > 5.0'] dynamic = ["version", "readme"] +license = "Apache-2.0" [project.urls] "Homepage" = "https://github.com/scylladb/python-driver" From 672e6d6c8b0bb78acd2a5c2d7915414571853a97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 22 May 2025 15:18:05 +0200 Subject: [PATCH 010/298] pyproject.toml: Specify required Python version --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 01a3e20817..502935f13c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ classifiers = [ dependencies = ['geomet>=1.1', 'pyyaml > 5.0'] dynamic = ["version", "readme"] license = "Apache-2.0" +requires-python = ">=3.9" [project.urls] "Homepage" = "https://github.com/scylladb/python-driver" From f494e1a7c2cbcfe987d10e4a690a34ded0741aa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 22 May 2025 15:18:38 +0200 Subject: [PATCH 011/298] pyproject.toml: Move to dependency-groups This is a new standardized way to have dev dependencies. --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 502935f13c..75f8add818 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,9 @@ requires-python = ">=3.9" [project.optional-dependencies] graph = ['gremlinpython==3.4.6'] cle = ['cryptography>=35.0'] -test = [ + +[dependency-groups] +dev = [ "pytest", "PyYAML", "pytz", From 8367ec6eb8ab103923221a86efe9498167f58be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 3 Jul 2025 17:46:07 +0200 Subject: [PATCH 012/298] pytest.ini: Add -rf flag We use this in CI, and it is generally useful, so lets not require manually adding it. --- pytest.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/pytest.ini b/pytest.ini index 0846273427..d01008d3a1 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,3 +3,4 @@ log_format = %(asctime)s.%(msecs)03d %(levelname)s [%(module)s:%(lineno)s]: %(me log_level = DEBUG log_date_format = %Y-%m-%d %H:%M:%S xfail_strict=true +addopts = -rf From 4b72dc04244a6971167388d36a361ad2a11d9e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 3 Jul 2025 17:55:20 +0200 Subject: [PATCH 013/298] pytest.ini: Move to pyproject.toml This reduces amount of files in root and move towards centralizing configuration. --- pyproject.toml | 7 +++++++ pytest.ini | 6 ------ 2 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 pytest.ini diff --git a/pyproject.toml b/pyproject.toml index 75f8add818..ccfc07a98c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,13 @@ requires = ["setuptools>=42", "Cython"] build-backend = "setuptools.build_meta" +[tool.pytest.ini_options] +log_format = "%(asctime)s.%(msecs)03d %(levelname)s [%(module)s:%(lineno)s]: %(message)s" +log_level = "DEBUG" +log_date_format = "%Y-%m-%d %H:%M:%S" +xfail_strict = true +addopts = "-rf" + [tool.setuptools_scm] version_file = "cassandra/_version.py" tag_regex = '(?P\d*?\.\d*?\.\d*?)-scylla' diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index d01008d3a1..0000000000 --- a/pytest.ini +++ /dev/null @@ -1,6 +0,0 @@ -[pytest] -log_format = %(asctime)s.%(msecs)03d %(levelname)s [%(module)s:%(lineno)s]: %(message)s -log_level = DEBUG -log_date_format = %Y-%m-%d %H:%M:%S -xfail_strict=true -addopts = -rf From 37108e20181e3a0a95723af15b5307e69237612a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 17 Jun 2025 19:48:37 +0200 Subject: [PATCH 014/298] Add ccm to dev deps --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ccfc07a98c..9b2c8de9ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ dev = [ "futurist", "asynctest", "pyyaml", + "ccm @ git+https://git@github.com/scylladb/scylla-ccm.git@master", ] [tool.setuptools] From 9fee70e23771818fac3136de6f99b010b558a533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 4 Jul 2025 11:23:04 +0200 Subject: [PATCH 015/298] dev dependencies: Remove Python version requirements Those were removed some time ago in test-requirements.txt, but not here. --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9b2c8de9ae..c4535936c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,9 +42,9 @@ dev = [ "scales", "pure-sasl", "twisted[tls]", - "gevent>=1.0; python_version < '3.13' and platform_machine != 'i686' and platform_machine != 'win32'", - "gevent==23.9.0; python_version < '3.13' and (platform_machine == 'i686' or platform_machine == 'win32')", - "eventlet>=0.33.3; python_version < '3.13'", + "gevent>=1.0; platform_machine != 'i686' and platform_machine != 'win32'", + "gevent==23.9.0; platform_machine == 'i686' or platform_machine == 'win32'", + "eventlet>=0.33.3", "cython", "packaging", "futurist", From aea7e378d6d7fa7987da84c9153e95d87fdb164e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 4 Jul 2025 11:30:51 +0200 Subject: [PATCH 016/298] Remove platform gevent requirements They make it impossible to resolve dependencies on Python 3.13 because gevent 23.9.0 doesn't have wheels for it. This special case was, as far as I can tell, somehow ignored in our CI. By this I mean that even on windows build it used newer gevent. Because of that, removing those cases shouldn't break anything. --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c4535936c9..6d34b6566c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,8 +42,7 @@ dev = [ "scales", "pure-sasl", "twisted[tls]", - "gevent>=1.0; platform_machine != 'i686' and platform_machine != 'win32'", - "gevent==23.9.0; platform_machine == 'i686' or platform_machine == 'win32'", + "gevent", "eventlet>=0.33.3", "cython", "packaging", From ca1da76bc579d42fb5688123e7f439a0ebd3fd58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 3 Jul 2025 17:30:38 +0200 Subject: [PATCH 017/298] Move tablets test to standard tests Tablets are now available on Scylla version that we run CI with, so I see no reason to keep it separate. --- .github/workflows/integration-tests.yml | 6 ------ tests/integration/{experiments => standard}/test_tablets.py | 0 2 files changed, 6 deletions(-) rename tests/integration/{experiments => standard}/test_tablets.py (100%) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 65c3773648..706bc0f101 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -55,9 +55,3 @@ jobs: export EVENT_LOOP_MANAGER=${{ matrix.event_loop_manager }} export SCYLLA_VERSION='release:6.2' ./scripts/run_integration_test.sh tests/integration/standard/ tests/integration/cqlengine/ - - - name: Test tablets - run: | - export EVENT_LOOP_MANAGER=${{ matrix.event_loop_manager }} - export SCYLLA_VERSION='release:6.2' - ./scripts/run_integration_test.sh tests/integration/experiments/ diff --git a/tests/integration/experiments/test_tablets.py b/tests/integration/standard/test_tablets.py similarity index 100% rename from tests/integration/experiments/test_tablets.py rename to tests/integration/standard/test_tablets.py From 120ab50a06d1ffddf85f15506665ce59d36ec01a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 3 Jul 2025 17:50:26 +0200 Subject: [PATCH 018/298] integration-tests.yml: Move to uv --- .github/workflows/integration-tests.yml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 706bc0f101..bccbdc63cc 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -44,14 +44,30 @@ jobs: java-version: ${{ matrix.java-version }} distribution: 'adopt' - - uses: actions/setup-python@v5 - name: Install Python ${{ matrix.python-version }} + - name: Install libev + run: sudo apt-get install libev4 libev-dev + + - name: Install uv + uses: astral-sh/setup-uv@v6 with: python-version: ${{ matrix.python-version }} - allow-prereleases: true + + # This is to get honest accounting of test time vs download time vs build time. + # Not strictly necessary for running tests. + - name: Build driver + run: uv sync + + # This is to get honest accounting of test time vs download time vs build time. + # Not strictly necessary for running tests. + - name: Download Scylla + run: | + export SCYLLA_VERSION='release:6.2' + uv run ccm create scylla-driver-temp -n 1 --scylla --version ${SCYLLA_VERSION} + uv run ccm remove - name: Test with pytest run: | export EVENT_LOOP_MANAGER=${{ matrix.event_loop_manager }} export SCYLLA_VERSION='release:6.2' - ./scripts/run_integration_test.sh tests/integration/standard/ tests/integration/cqlengine/ + export PROTOCOL_VERSION=4 + uv run pytest tests/integration/standard/ tests/integration/cqlengine/ From 45354cc6f5f880ab79ca38f90b479e3c608e3d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 3 Jul 2025 17:52:01 +0200 Subject: [PATCH 019/298] Remove run_integration_test.sh No longer necessary for anything. --- scripts/run_integration_test.sh | 41 --------------------------------- 1 file changed, 41 deletions(-) delete mode 100755 scripts/run_integration_test.sh diff --git a/scripts/run_integration_test.sh b/scripts/run_integration_test.sh deleted file mode 100755 index 2944e045db..0000000000 --- a/scripts/run_integration_test.sh +++ /dev/null @@ -1,41 +0,0 @@ -#! /bin/bash -e - -sudo apt-get install gcc python3-dev libev4 libev-dev - -aio_max_nr_recommended_value=1048576 -aio_max_nr=$(cat /proc/sys/fs/aio-max-nr) -echo "The current aio-max-nr value is $aio_max_nr" -if (( aio_max_nr != aio_max_nr_recommended_value )); then - sudo sh -c "echo 'fs.aio-max-nr = $aio_max_nr_recommended_value' >> /etc/sysctl.conf" - sudo sysctl -p /etc/sysctl.conf - echo "The aio-max-nr was changed from $aio_max_nr to $(cat /proc/sys/fs/aio-max-nr)" - if (( $(cat /proc/sys/fs/aio-max-nr) != aio_max_nr_recommended_value )); then - echo "The aio-max-nr value was not changed to $aio_max_nr_recommended_value" - exit 1 - fi -fi - -python3 -m venv .test-venv -source .test-venv/bin/activate -pip install --upgrade pip -pip install -U wheel setuptools - -# install driver wheel -pip install --ignore-installed -r test-requirements.txt pytest -pip install -e . - -# download awscli -pip install awscli - -# install scylla-ccm -pip install https://github.com/scylladb/scylla-ccm/archive/master.zip - -# download version - -ccm create scylla-driver-temp -n 1 --scylla --version ${SCYLLA_VERSION} -ccm remove - -# run test - -PROTOCOL_VERSION=4 pytest -rf $* - From eb5a40f97d72582037bb4c271bf40a31f5248b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 3 Jul 2025 18:09:26 +0200 Subject: [PATCH 020/298] lib-build-and-push.yml: Move to uv --- .github/workflows/lib-build-and-push.yml | 26 ++++++++---------------- pyproject.toml | 3 +-- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index c911a8ffd5..3a1c9fa480 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -103,20 +103,14 @@ jobs: echo "CIBW_BEFORE_TEST=true" >> $GITHUB_ENV; echo "CIBW_BEFORE_TEST_WINDOWS=(exit 0)" >> $GITHUB_ENV; - - uses: actions/setup-python@v5 - name: Install Python + - name: Install uv + uses: astral-sh/setup-uv@v6 with: python-version: ${{ inputs.python-version }} - allow-prereleases: 'true' - - - name: Enable pip installing globally - if: runner.os == 'MacOs' || runner.os == 'Windows' - run: | - echo "PIP_BREAK_SYSTEM_PACKAGES=1" >> $GITHUB_ENV - name: Install cibuildwheel run: | - python3 -m pip install cibuildwheel==2.22.0 + uv tool install 'cibuildwheel==2.22.0' - name: Install OpenSSL for Windows if: runner.os == 'Windows' @@ -154,12 +148,12 @@ jobs: if: matrix.target != 'linux-aarch64' shell: bash run: | - GITHUB_WORKFLOW_REF="scylladb/python-driver/.github/workflows/lib-build-and-push.yml@refs/heads/master" python3 -m cibuildwheel --output-dir wheelhouse + GITHUB_WORKFLOW_REF="scylladb/python-driver/.github/workflows/lib-build-and-push.yml@refs/heads/master" cibuildwheel --output-dir wheelhouse - name: Build wheels for linux aarch64 if: matrix.target == 'linux-aarch64' run: | - GITHUB_WORKFLOW_REF="scylladb/python-driver/.github/workflows/lib-build-and-push.yml@refs/heads/master" CIBW_BUILD="cp3*" python -m cibuildwheel --archs aarch64 --output-dir wheelhouse + GITHUB_WORKFLOW_REF="scylladb/python-driver/.github/workflows/lib-build-and-push.yml@refs/heads/master" CIBW_BUILD="cp3*" cibuildwheel --archs aarch64 --output-dir wheelhouse - uses: actions/upload-artifact@v4 with: @@ -172,14 +166,12 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - name: Install Python + - name: Install uv + uses: astral-sh/setup-uv@v6 - name: Build sdist - run: | - pip install build - python -m build --sdist - + run: uv build --sdist + - uses: actions/upload-artifact@v4 with: name: source-dist diff --git a/pyproject.toml b/pyproject.toml index 6d34b6566c..aa163452f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,8 +105,7 @@ skip = [ "*musllinux*", ] build = ["cp3*", "pp3*"] - -before-test = "pip install -r {project}/test-requirements.txt" +test-groups = ["dev"] manylinux-x86_64-image = "manylinux_2_28" manylinux-aarch64-image = "manylinux_2_28" From 738ec2918425475e53ffef2d2cd81f5a4df80347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 3 Jul 2025 18:11:02 +0200 Subject: [PATCH 021/298] Remove legacy dependencies files No longer needed for anything, and we should not have 2 places to keep dependencies in. It will, and already has, resulted in those places being out of sync. --- requirements.txt | 1 - test-requirements.txt | 15 --------------- 2 files changed, 16 deletions(-) delete mode 100644 requirements.txt delete mode 100644 test-requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 1d5f0bcfc4..0000000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -geomet>=1.1 diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index 331094fab6..0000000000 --- a/test-requirements.txt +++ /dev/null @@ -1,15 +0,0 @@ --r requirements.txt -scales -pytest -pytz -sure -pure-sasl -twisted[tls] -gevent>=1.0; platform_machine != 'i686' and platform_machine != 'win32' -gevent==23.9.0; platform_machine == 'i686' or platform_machine == 'win32' -eventlet>=0.33.3; -cython>=0.20,<0.30 -packaging -futurist -asynctest -pyyaml From f160402739a185c5a0131b5df8979779f475e0d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 3 Jul 2025 18:00:25 +0200 Subject: [PATCH 022/298] README-dev.rst: Recommend uv usage --- README-dev.rst | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/README-dev.rst b/README-dev.rst index f0c4815729..f158226de0 100644 --- a/README-dev.rst +++ b/README-dev.rst @@ -15,7 +15,7 @@ Releasing Building the Docs ================= -To build and preview the documentation for the ScyllaDB Python driver locally, you must first manually install `python-driver`. +To build and preview the documentation for the ScyllaDB Python driver locally, you must first manually install `python-driver`. This is necessary for autogenerating the reference documentation of the driver. You can find detailed instructions on how to install the driver in the `Installation guide `_. @@ -24,6 +24,14 @@ After installing the driver, you can build the documentation: - Install poetry: ``pip install poetry`` - To preview docs in your browser: ``make -C docs preview`` +Tooling +======= + +We recommend using `uv` tool for running tests, linters and basically everything else, +since it makes Python tooling ecosystem mostly usable. +To install it, see instructions at https://docs.astral.sh/uv/getting-started/installation/ +The rest of this document assumes you have `uv` installed. + Tests ===== @@ -31,13 +39,13 @@ Running Unit Tests ------------------ Unit tests can be run like so:: - python -m pytest tests/unit - EVENT_LOOP_MANAGER=gevent python -m pytest tests/unit/io/test_geventreactor.py - EVENT_LOOP_MANAGER=eventlet python -m pytest tests/unit/io/test_eventletreactor.py + uv run pytest tests/unit + EVENT_LOOP_MANAGER=gevent uv run pytest tests/unit/io/test_geventreactor.py + EVENT_LOOP_MANAGER=eventlet uv run pytest tests/unit/io/test_eventletreactor.py You can run a specific test method like so:: - python -m pytest tests/unit/test_connection.py::ConnectionTest::test_bad_protocol_version + uv run pytest tests/unit/test_connection.py::ConnectionTest::test_bad_protocol_version Running Integration Tests ------------------------- @@ -46,17 +54,17 @@ In order to run integration tests, you must specify a version to run using eithe * ``CASSANDRA_VERSION`` environment variable:: - SCYLLA_VERSION="release:5.1" python -m pytest tests/integration/standard tests/integration/cqlengine/ + SCYLLA_VERSION="release:5.1" uv run pytest tests/integration/standard tests/integration/cqlengine/ Or you can specify a scylla/cassandra directory (to test unreleased versions):: - SCYLLA_VERSION=/path/to/scylla pytest tests/integration/standard/ + SCYLLA_VERSION=/path/to/scylla uv run pytest tests/integration/standard/ Specifying the usage of an already running Scylla cluster ------------------------------------------------------------ The test will start the appropriate Scylla clusters when necessary but if you don't want this to happen because a Scylla cluster is already running the flag ``USE_CASS_EXTERNAL`` can be used, for example:: - USE_CASS_EXTERNAL=1 SCYLLA_VERSION='release:5.1' pytest tests/integration/standard + USE_CASS_EXTERNAL=1 SCYLLA_VERSION='release:5.1' uv run pytest tests/integration/standard Specify a Protocol Version for Tests ------------------------------------ @@ -66,30 +74,29 @@ The protocol version defaults to: - 5 for Cassandra >= 4.0, 4 for Cassandra >= 2.2, 3 for Cassandra >= 2.1, 2 for Cassandra >= 2.0 You can overwrite it with the ``PROTOCOL_VERSION`` environment variable:: - PROTOCOL_VERSION=3 SCYLLA_VERSION="release:5.1" python -m pytest tests/integration/standard tests/integration/cqlengine/ + PROTOCOL_VERSION=3 SCYLLA_VERSION="release:5.1" uv run pytest tests/integration/standard tests/integration/cqlengine/ Seeing Test Logs in Real Time ----------------------------- Sometimes it's useful to output logs for the tests as they run:: - python -m pytest -s tests/unit/ + uv run pytest -s tests/unit/ Use tee to capture logs and see them on your terminal:: - python -m pytest -s tests/unit/ 2>&1 | tee test.log + uv run pytest -s tests/unit/ 2>&1 | tee test.log Running the Benchmarks ====================== There needs to be a version of cassandra running locally so before running the benchmarks, if ccm is installed: - - ccm create benchmark_cluster -v 3.0.1 -n 1 -s + + uv run ccm create benchmark_cluster -v 3.0.1 -n 1 -s To run the benchmarks, pick one of the files under the ``benchmarks/`` dir and run it:: - python benchmarks/future_batches.py + uv run benchmarks/future_batches.py There are a few options. Use ``--help`` to see them all:: - python benchmarks/future_batches.py --help - + uv run benchmarks/future_batches.py --help From aa2a4b656a1a0eb6d9c8dfa5c1da52838d5e4012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 4 Jul 2025 12:25:52 +0200 Subject: [PATCH 023/298] setup.py: Build concurrently by default Because why not? It will be faster - on my machine ~4x faster. --- docs/installation.rst | 3 +++ setup.py | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index c8bf66dfdc..b57ad37f96 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -76,6 +76,9 @@ threads used to build the driver and any C extensions: $ # installing from pip $ CASS_DRIVER_BUILD_CONCURRENCY=8 pip install scylla-driver +Note that by default (when CASS_DRIVER_BUILD_CONCURRENCY is not specified), concurrency will be equal to the number of +logical cores on your machine. + OSX Installation Error ^^^^^^^^^^^^^^^^^^^^^^ If you're installing on OSX and have XCode 5.1 installed, you may see an error like this:: diff --git a/setup.py b/setup.py index be2c5ca816..340ded87ed 100644 --- a/setup.py +++ b/setup.py @@ -206,6 +206,9 @@ def eval_env_var_as_array(varname): sys.argv = [a for a in sys.argv if a not in ("--no-murmur3", "--no-libev", "--no-cython", "--no-extensions")] build_concurrency = int(os.environ.get('CASS_DRIVER_BUILD_CONCURRENCY', '0')) +if build_concurrency == 0: + build_concurrency = None + CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST = bool(os.environ.get('CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST', 'no') == 'yes') class NoPatchExtension(Extension): @@ -292,7 +295,7 @@ def run(self): def build_extensions(self): - if build_concurrency > 1: + if build_concurrency is None or build_concurrency > 1: self.check_extensions_list(self.extensions) import multiprocessing.pool @@ -431,7 +434,6 @@ def run_setup(extensions): setup(**kw) - run_setup(None) if has_cqlengine: From 32ce4414600794988ddcf2b53a3e58dbf2104a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 4 Jul 2025 15:42:56 +0200 Subject: [PATCH 024/298] uv: Rebuild lib when cython stuff changes When a file or variable used by setup.py changes, we should rebuild the driver. If we don't we risk very unpleasant errors caused by source files and binary files differing. --- pyproject.toml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index aa163452f0..196a25b45c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,40 @@ requires = ["setuptools>=42", "Cython"] build-backend = "setuptools.build_meta" +[tool.uv] +cache-keys = [ + { file = "pyproject.toml" }, + { file = "setup.py" }, + # Cythonized modules + { file = "cassandra/io/libevwrapper.c" }, + { file = "cassandra/cmurmur3.c" }, + { file = "cassandra/cluster.py" }, + { file = "cassandra/concurrent.py" }, + { file = "cassandra/connection.py" }, + { file = "cassandra/cqltypes.py" }, + { file = "cassandra/metadata.py" }, + { file = "cassandra/pool.py" }, + { file = "cassandra/protocol.py" }, + { file = "cassandra/query.py" }, + { file = "cassandra/util.py" }, + { file = "cassandra/shard_info.py" }, + { file = "cassandra/*.pyx" }, + { file = "cassandra/*.pxd" }, + + # Env variables used in setup.py + { env = "CASS_DRIVER_LIBEV_INCLUDES" }, + { env = "CASS_DRIVER_LIBEV_LIBS" }, + { env = "CASS_DRIVER_NO_EXTENSIONS" }, + { env = "CASS_DRIVER_NO_LIBEV" }, + { env = "CASS_DRIVER_NO_CYTHON" }, + { env = "CASS_DRIVER_BUILD_CONCURRENCY" }, + { env = "CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST" }, + { env = "CASS_DRIVER_ALLOWED_CYTHON_VERSION" }, + + # used by setuptools_scm + { git = { commit = true, tags = true } }, +] + [tool.pytest.ini_options] log_format = "%(asctime)s.%(msecs)03d %(levelname)s [%(module)s:%(lineno)s]: %(message)s" log_level = "DEBUG" From d540e14a942d367d03ae3ddc257ee2d8916f30e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Sun, 6 Jul 2025 16:02:11 +0200 Subject: [PATCH 025/298] build-sdist: Use input Python version --- .github/workflows/lib-build-and-push.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index 3a1c9fa480..b68ef4eba5 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -168,6 +168,8 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v6 + with: + python-version: ${{ inputs.python-version }} - name: Build sdist run: uv build --sdist From eb219752eae524fd43b6c9a810657c50598e6b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 4 Jul 2025 16:40:13 +0200 Subject: [PATCH 026/298] Split README-dec into CONTRIBUTING and MAINTENANCE It was quite weird to have both CONTRIBUTING and README-dev. Most of README-dev fits well into CONTRIBUTING. The sections about releasing deserves its own MAINTENANCE document, similarly to how it is done in Rust Driver. --- CONTRIBUTING.rst | 91 +++++++++++++++++++++++++++++++++++++++++- MAINTENANCE.md | 13 ++++++ README-dev.rst | 102 ----------------------------------------------- 3 files changed, 103 insertions(+), 103 deletions(-) create mode 100644 MAINTENANCE.md delete mode 100644 README-dev.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index c635fd8c1b..776ebaa20b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -18,7 +18,7 @@ good bug reports. They will not be repeated in detail here, but in general, the Pull Requests ------------- If you're able to fix a bug yourself, you can `fork the repository `_ and submit a `Pull Request `_ with the fix. -Please include tests demonstrating the issue and fix. For examples of how to run the tests, consult the `dev README `_. +Please include tests demonstrating the issue and fix. For examples of how to run the tests, consult the further parts of this document. Design and Implementation Guidelines ------------------------------------ @@ -26,3 +26,92 @@ Design and Implementation Guidelines - This project follows `semantic versioning `_, so breaking API changes will only be introduced in major versions. - Legacy ``cqlengine`` has varying degrees of overreaching client-side validation. Going forward, we will avoid client validation where server feedback is adequate and not overly expensive. - When writing tests, try to achieve maximal coverage in unit tests (where it is faster to run across many runtimes). Integration tests are good for things where we need to test server interaction, or where it is important to test across different server versions (emulating in unit tests would not be effective). + +Dev setup +========= + +We recommend using `uv` tool for running tests, linters and basically everything else, +since it makes Python tooling ecosystem mostly usable. +To install it, see instructions at https://docs.astral.sh/uv/getting-started/installation/ +The rest of this document assumes you have `uv` installed. + +Building the Docs +================= + +To build and preview the documentation for the ScyllaDB Python driver locally, you must first manually install `python-driver`. +This is necessary for autogenerating the reference documentation of the driver. +You can find detailed instructions on how to install the driver in the `Installation guide `_. + +After installing the driver, you can build the documentation: +- Make sure you have Python version compatible with docs. You can see supported version in ``docs/pyproject.toml`` - look for ``python`` in ``tool.poetry.dependencies`` section. +- Install poetry: ``pip install poetry`` +- To preview docs in your browser: ``make -C docs preview`` + +Tests +===== + +Running Unit Tests +------------------ +Unit tests can be run like so:: + + uv run pytest tests/unit + EVENT_LOOP_MANAGER=gevent uv run pytest tests/unit/io/test_geventreactor.py + EVENT_LOOP_MANAGER=eventlet uv run pytest tests/unit/io/test_eventletreactor.py + +You can run a specific test method like so:: + + uv run pytest tests/unit/test_connection.py::ConnectionTest::test_bad_protocol_version + +Running Integration Tests +------------------------- +In order to run integration tests, you must specify a version to run using either of: +* ``SCYLLA_VERSION`` e.g. ``release:5.1`` +* ``CASSANDRA_VERSION`` +environment variable:: + + SCYLLA_VERSION="release:5.1" uv run pytest tests/integration/standard tests/integration/cqlengine/ + +Or you can specify a scylla/cassandra directory (to test unreleased versions):: + + SCYLLA_VERSION=/path/to/scylla uv run pytest tests/integration/standard/ + +Specifying the usage of an already running Scylla cluster +------------------------------------------------------------ +The test will start the appropriate Scylla clusters when necessary but if you don't want this to happen because a Scylla cluster is already running the flag ``USE_CASS_EXTERNAL`` can be used, for example:: + + USE_CASS_EXTERNAL=1 SCYLLA_VERSION='release:5.1' uv run pytest tests/integration/standard + +Specify a Protocol Version for Tests +------------------------------------ +The protocol version defaults to: +- 4 for Scylla >= 3.0 and Scylla Enterprise > 2019. +- 3 for older versions of Scylla +- 5 for Cassandra >= 4.0, 4 for Cassandra >= 2.2, 3 for Cassandra >= 2.1, 2 for Cassandra >= 2.0 +You can overwrite it with the ``PROTOCOL_VERSION`` environment variable:: + + PROTOCOL_VERSION=3 SCYLLA_VERSION="release:5.1" uv run pytest tests/integration/standard tests/integration/cqlengine/ + +Seeing Test Logs in Real Time +----------------------------- +Sometimes it's useful to output logs for the tests as they run:: + + uv run pytest -s tests/unit/ + +Use tee to capture logs and see them on your terminal:: + + uv run pytest -s tests/unit/ 2>&1 | tee test.log + + +Running the Benchmarks +====================== +There needs to be a version of cassandra running locally so before running the benchmarks, if ccm is installed: + + uv run ccm create benchmark_cluster -v 3.0.1 -n 1 -s + +To run the benchmarks, pick one of the files under the ``benchmarks/`` dir and run it:: + + uv run benchmarks/future_batches.py + +There are a few options. Use ``--help`` to see them all:: + + uv run benchmarks/future_batches.py --help diff --git a/MAINTENANCE.md b/MAINTENANCE.md new file mode 100644 index 0000000000..8fc860ac4b --- /dev/null +++ b/MAINTENANCE.md @@ -0,0 +1,13 @@ +Releasing +========= +* Run the tests and ensure they all pass +* Update the version in ``cassandra/__init__.py`` +* Add the new version in ``docs/conf.py`` (variables: ``TAGS``, ``LATEST_VERSION``, ``DEPRECATED_VERSIONS``). + * For patch version releases (like ``3.26.8-scylla -> 3.26.9-scylla``) replace the old version with new one in ``TAGS`` and update ``LATEST_VERSION``. + * For minor version releases (like ``3.26.9-scylla -> 3.27.0-scylla``) add new version to ``TAGS``, update ``LATEST_VERSION`` and add previous minor version to ``DEPRECATED_VERSIONS``. +* Commit the version changes, e.g. ``git commit -m 'Release 3.26.9'`` +* Tag the release. For example: ``git tag -a 3.26.9-scylla -m 'Release 3.26.9'`` +* Push the tag and new ``master`` SIMULTANEOUSLY: ``git push --atomic origin master v6.0.21-scylla`` +* Now new version and its docs should be automatically published. Check `PyPI `_ and `docs `_ to make sure its there. +* If you didn't push branch and tag simultaneously (or doc publishing failed for other reason) then restart the relevant job from GitHub Actions UI. +* Publish a GitHub Release and a post on community forum. diff --git a/README-dev.rst b/README-dev.rst deleted file mode 100644 index f158226de0..0000000000 --- a/README-dev.rst +++ /dev/null @@ -1,102 +0,0 @@ -Releasing -========= -* Run the tests and ensure they all pass -* Update the version in ``cassandra/__init__.py`` -* Add the new version in ``docs/conf.py`` (variables: ``TAGS``, ``LATEST_VERSION``, ``DEPRECATED_VERSIONS``). - * For patch version releases (like ``3.26.8-scylla -> 3.26.9-scylla``) replace the old version with new one in ``TAGS`` and update ``LATEST_VERSION``. - * For minor version releases (like ``3.26.9-scylla -> 3.27.0-scylla``) add new version to ``TAGS``, update ``LATEST_VERSION`` and add previous minor version to ``DEPRECATED_VERSIONS``. -* Commit the version changes, e.g. ``git commit -m 'Release 3.26.9'`` -* Tag the release. For example: ``git tag -a 3.26.9-scylla -m 'Release 3.26.9'`` -* Push the tag and new ``master`` SIMULTANEOUSLY: ``git push --atomic origin master v6.0.21-scylla`` -* Now new version and its docs should be automatically published. Check `PyPI `_ and `docs `_ to make sure its there. -* If you didn't push branch and tag simultaneously (or doc publishing failed for other reason) then restart the relevant job from GitHub Actions UI. -* Publish a GitHub Release and a post on community forum. - -Building the Docs -================= - -To build and preview the documentation for the ScyllaDB Python driver locally, you must first manually install `python-driver`. -This is necessary for autogenerating the reference documentation of the driver. -You can find detailed instructions on how to install the driver in the `Installation guide `_. - -After installing the driver, you can build the documentation: -- Make sure you have Python version compatible with docs. You can see supported version in ``docs/pyproject.toml`` - look for ``python`` in ``tool.poetry.dependencies`` section. -- Install poetry: ``pip install poetry`` -- To preview docs in your browser: ``make -C docs preview`` - -Tooling -======= - -We recommend using `uv` tool for running tests, linters and basically everything else, -since it makes Python tooling ecosystem mostly usable. -To install it, see instructions at https://docs.astral.sh/uv/getting-started/installation/ -The rest of this document assumes you have `uv` installed. - -Tests -===== - -Running Unit Tests ------------------- -Unit tests can be run like so:: - - uv run pytest tests/unit - EVENT_LOOP_MANAGER=gevent uv run pytest tests/unit/io/test_geventreactor.py - EVENT_LOOP_MANAGER=eventlet uv run pytest tests/unit/io/test_eventletreactor.py - -You can run a specific test method like so:: - - uv run pytest tests/unit/test_connection.py::ConnectionTest::test_bad_protocol_version - -Running Integration Tests -------------------------- -In order to run integration tests, you must specify a version to run using either of: -* ``SCYLLA_VERSION`` e.g. ``release:5.1`` -* ``CASSANDRA_VERSION`` -environment variable:: - - SCYLLA_VERSION="release:5.1" uv run pytest tests/integration/standard tests/integration/cqlengine/ - -Or you can specify a scylla/cassandra directory (to test unreleased versions):: - - SCYLLA_VERSION=/path/to/scylla uv run pytest tests/integration/standard/ - -Specifying the usage of an already running Scylla cluster ------------------------------------------------------------- -The test will start the appropriate Scylla clusters when necessary but if you don't want this to happen because a Scylla cluster is already running the flag ``USE_CASS_EXTERNAL`` can be used, for example:: - - USE_CASS_EXTERNAL=1 SCYLLA_VERSION='release:5.1' uv run pytest tests/integration/standard - -Specify a Protocol Version for Tests ------------------------------------- -The protocol version defaults to: -- 4 for Scylla >= 3.0 and Scylla Enterprise > 2019. -- 3 for older versions of Scylla -- 5 for Cassandra >= 4.0, 4 for Cassandra >= 2.2, 3 for Cassandra >= 2.1, 2 for Cassandra >= 2.0 -You can overwrite it with the ``PROTOCOL_VERSION`` environment variable:: - - PROTOCOL_VERSION=3 SCYLLA_VERSION="release:5.1" uv run pytest tests/integration/standard tests/integration/cqlengine/ - -Seeing Test Logs in Real Time ------------------------------ -Sometimes it's useful to output logs for the tests as they run:: - - uv run pytest -s tests/unit/ - -Use tee to capture logs and see them on your terminal:: - - uv run pytest -s tests/unit/ 2>&1 | tee test.log - - -Running the Benchmarks -====================== -There needs to be a version of cassandra running locally so before running the benchmarks, if ccm is installed: - - uv run ccm create benchmark_cluster -v 3.0.1 -n 1 -s - -To run the benchmarks, pick one of the files under the ``benchmarks/`` dir and run it:: - - uv run benchmarks/future_batches.py - -There are a few options. Use ``--help`` to see them all:: - - uv run benchmarks/future_batches.py --help From d8ed0ff0472b8437b80deb5b1b3a51a16f9f4ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 4 Jul 2025 16:44:39 +0200 Subject: [PATCH 027/298] CONTRIBUTING: Adjust commands to Scylla Gets rid of some Cassandra leftovers, and updates some mentioned Scylla versions. --- CONTRIBUTING.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 776ebaa20b..c78e0a8fa5 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -65,11 +65,11 @@ You can run a specific test method like so:: Running Integration Tests ------------------------- In order to run integration tests, you must specify a version to run using either of: -* ``SCYLLA_VERSION`` e.g. ``release:5.1`` +* ``SCYLLA_VERSION`` e.g. ``release:2025.2`` * ``CASSANDRA_VERSION`` environment variable:: - SCYLLA_VERSION="release:5.1" uv run pytest tests/integration/standard tests/integration/cqlengine/ + SCYLLA_VERSION="release:2025.2" uv run pytest tests/integration/standard tests/integration/cqlengine/ Or you can specify a scylla/cassandra directory (to test unreleased versions):: @@ -104,9 +104,9 @@ Use tee to capture logs and see them on your terminal:: Running the Benchmarks ====================== -There needs to be a version of cassandra running locally so before running the benchmarks, if ccm is installed: +There needs to be a version of Scyll running locally so before running the benchmarks, if ccm is installed: - uv run ccm create benchmark_cluster -v 3.0.1 -n 1 -s + uv run ccm create benchmark_cluster --scylla -v release:2025.2 -n 1 -s To run the benchmarks, pick one of the files under the ``benchmarks/`` dir and run it:: From 3ead907ced902be956f16df14bbf8ec61c56d5c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 4 Jul 2025 16:48:32 +0200 Subject: [PATCH 028/298] CONTRIBUTING: Recommend ccache/sccache Those tools dramatically speed up building of the driver, making contributor life much easier. --- CONTRIBUTING.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index c78e0a8fa5..8b8fc0e791 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -35,6 +35,11 @@ since it makes Python tooling ecosystem mostly usable. To install it, see instructions at https://docs.astral.sh/uv/getting-started/installation/ The rest of this document assumes you have `uv` installed. +It is also strongly recommended to use C/C++-caching tool like ccache or sccache. +When modifying driver files, rebuilding Cython modules is often necessary. +Without caching, each such rebuild may take over a minute. Caching usually brings it +down to about 2-3 seconds. + Building the Docs ================= From dee06939e15a10013b43688ebcfb10bdda5a27d7 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 7 Jul 2025 23:17:42 -0400 Subject: [PATCH 029/298] fix: make HostConnection.return_connection not to lock connection and and pool Locking connection and pool at the same time can lead to deadlock. Consider that connection.close also locks connection. --- cassandra/pool.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/cassandra/pool.py b/cassandra/pool.py index d1f6604abf..d2770ad88c 100644 --- a/cassandra/pool.py +++ b/cassandra/pool.py @@ -580,16 +580,18 @@ def return_connection(self, connection, stream_was_orphaned=False): return self._is_replacing = True self._session.submit(self._replace, connection) - else: - if connection in self._trash: - with connection.lock: - if connection.in_flight == len(connection.orphaned_request_ids): - with self._lock: - if connection in self._trash: - self._trash.remove(connection) - log.debug("Closing trashed connection (%s) to %s", id(connection), self.host) - connection.close() - return + elif connection in self._trash: + with connection.lock: + no_pending_requests = connection.in_flight <= len(connection.orphaned_request_ids) + if no_pending_requests: + with self._lock: + close_connection = False + if connection in self._trash: + self._trash.remove(connection) + close_connection = True + if close_connection: + log.debug("Closing trashed connection (%s) to %s", id(connection), self.host) + connection.close() def on_orphaned_stream_released(self): """ From 7cc1a6c31cfe693073fd463fa0f1f5de608d49b4 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 7 Jul 2025 23:21:07 -0400 Subject: [PATCH 030/298] fix: HostConnectionPool.return_connection not to lock connection and pool Locking connection and pool can lead to deadlock. Consider that connection.close locks connection. --- cassandra/pool.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/cassandra/pool.py b/cassandra/pool.py index d2770ad88c..dc63d0f35f 100644 --- a/cassandra/pool.py +++ b/cassandra/pool.py @@ -1134,15 +1134,18 @@ def return_connection(self, connection, stream_was_orphaned=False): self.shutdown() else: self._replace(connection) - else: - if connection in self._trash: - with connection.lock: - if connection.in_flight == 0: - with self._lock: - if connection in self._trash: - self._trash.remove(connection) - log.debug("Closing trashed connection (%s) to %s", id(connection), self.host) - connection.close() + elif connection in self._trash: + with connection.lock: + no_pending_requests = connection.in_flight <= len(connection.orphaned_request_ids) + if no_pending_requests: + with self._lock: + close_connection = False + if connection in self._trash: + self._trash.remove(connection) + close_connection = True + if close_connection: + log.debug("Closing trashed connection (%s) to %s", id(connection), self.host) + connection.close() return core_conns = self._session.cluster.get_core_connections_per_host(self.host_distance) From 84435ddbc46099e12f9a589bb1c39bc1c44080b9 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 7 Jul 2025 23:22:32 -0400 Subject: [PATCH 031/298] fix: HostConnectionPool._maybe_trash_connection not to lock connection and pool Locking connection a pool can lead to a deadlock. Consider that connection.close also locks connection. This commit also fixes condition for closing connection right away. Instead of checking there is no in-flight requests it now checks if in-flight requests less or equal than orphaned requests, since orphaned requests still counts as in-flight and can be safely disregarded when connection is dropped. --- cassandra/pool.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cassandra/pool.py b/cassandra/pool.py index dc63d0f35f..890e91aba2 100644 --- a/cassandra/pool.py +++ b/cassandra/pool.py @@ -1180,14 +1180,14 @@ def _maybe_trash_connection(self, connection): new_connections.remove(connection) self._connections = new_connections - with connection.lock: - if connection.in_flight == 0: - log.debug("Skipping trash and closing unused connection (%s) to %s", id(connection), self.host) - connection.close() - - # skip adding it to the trash if we're already closing it - return - + if did_trash: + with connection.lock: + no_pending_requests = connection.in_flight <= len(connection.orphaned_request_ids) + if no_pending_requests: + log.debug("Skipping trash and closing unused connection (%s) to %s", id(connection), self.host) + connection.close() + return + with self._lock: self._trash.add(connection) if did_trash: From 4f2312f77a0df317149f024b68e4abaadcc1ec4a Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 7 Jul 2025 23:44:49 -0400 Subject: [PATCH 032/298] fix: HostConnection._open_connection_to_missing_shard not to lock pool and connection Locking connection and pool at the same time lead to dead lock. Consider connection.close locks connection. --- cassandra/pool.py | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/cassandra/pool.py b/cassandra/pool.py index 890e91aba2..8bb318f582 100644 --- a/cassandra/pool.py +++ b/cassandra/pool.py @@ -758,23 +758,26 @@ def _open_connection_to_missing_shard(self, shard_id): ) old_conn = None with self._lock: - if self.is_shutdown: - conn.close() - return - if conn.features.shard_id in self._connections.keys(): - # Move the current connection to the trash and use the new one from now on - old_conn = self._connections[conn.features.shard_id] - log.debug( - "Replacing overloaded connection (%s) with (%s) for shard %i for host %s", - id(old_conn), - id(conn), - conn.features.shard_id, - self.host - ) - if self._keyspace: - conn.set_keyspace_blocking(self._keyspace) + is_shutdown = self.is_shutdown + if not is_shutdown: + if conn.features.shard_id in self._connections.keys(): + # Move the current connection to the trash and use the new one from now on + old_conn = self._connections[conn.features.shard_id] + log.debug( + "Replacing overloaded connection (%s) with (%s) for shard %i for host %s", + id(old_conn), + id(conn), + conn.features.shard_id, + self.host + ) + if self._keyspace: + conn.set_keyspace_blocking(self._keyspace) + self._connections[conn.features.shard_id] = conn + + if is_shutdown: + conn.close() + return - self._connections[conn.features.shard_id] = conn if old_conn is not None: remaining = old_conn.in_flight - len(old_conn.orphaned_request_ids) if remaining == 0: @@ -794,10 +797,11 @@ def _open_connection_to_missing_shard(self, shard_id): remaining, ) with self._lock: - if self.is_shutdown: - old_conn.close() - else: + is_shutdown = self.is_shutdown + if not is_shutdown: self._trash.add(old_conn) + if is_shutdown: + conn.close() num_missing_or_needing_replacement = self.num_missing_or_needing_replacement log.debug( "Connected to %s/%i shards on host %s (%i missing or needs replacement)", From c49ac3ab2eb5179abc156c1214fb10987f680495 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Thu, 19 Jun 2025 16:22:15 +0300 Subject: [PATCH 033/298] Remove Protocols V1, V2 Remove most of the code related to V1, V2 protocol versions. Fixes: https://github.com/scylladb/python-driver/issues/490 Signed-off-by: Yaniv Kaul --- benchmarks/callback_full_pipeline.py | 5 +- cassandra/__init__.py | 12 +- cassandra/cluster.py | 71 +--- cassandra/connection.py | 23 +- cassandra/cqltypes.py | 42 +- cassandra/marshal.py | 5 - cassandra/metadata.py | 7 +- cassandra/pool.py | 365 +----------------- cassandra/protocol.py | 121 ++---- docs/api/cassandra/cluster.rst | 8 - docs/security.rst | 17 - docs/upgrading.rst | 43 +-- tests/integration/__init__.py | 18 +- tests/integration/standard/test_cluster.py | 30 +- tests/integration/standard/test_connection.py | 9 +- tests/unit/io/utils.py | 3 +- tests/unit/test_cluster.py | 18 - tests/unit/test_connection.py | 22 +- tests/unit/test_host_connection_pool.py | 52 +-- tests/unit/test_marshalling.py | 22 +- tests/unit/test_orderedmap.py | 2 +- tests/unit/test_parameter_binding.py | 15 +- 22 files changed, 110 insertions(+), 800 deletions(-) diff --git a/benchmarks/callback_full_pipeline.py b/benchmarks/callback_full_pipeline.py index a4a4c33315..87eb999cfe 100644 --- a/benchmarks/callback_full_pipeline.py +++ b/benchmarks/callback_full_pipeline.py @@ -49,10 +49,7 @@ def insert_next(self, previous_result=sentinel): def run(self): self.start_profile() - if self.protocol_version >= 3: - concurrency = 1000 - else: - concurrency = 100 + concurrency = 1000 for _ in range(min(concurrency, self.num_queries)): self.insert_next() diff --git a/cassandra/__init__.py b/cassandra/__init__.py index dfded7d1a6..c1ee195aa2 100644 --- a/cassandra/__init__.py +++ b/cassandra/__init__.py @@ -135,16 +135,6 @@ class ProtocolVersion(object): """ Defines native protocol versions supported by this driver. """ - V1 = 1 - """ - v1, supported in Cassandra 1.2-->2.2 - """ - - V2 = 2 - """ - v2, supported in Cassandra 2.0-->2.2; - added support for lightweight transactions, batch operations, and automatic query paging. - """ V3 = 3 """ @@ -180,7 +170,7 @@ class ProtocolVersion(object): DSE private protocol v2, supported in DSE 6.0+ """ - SUPPORTED_VERSIONS = (DSE_V2, DSE_V1, V6, V5, V4, V3, V2, V1) + SUPPORTED_VERSIONS = (DSE_V2, DSE_V1, V6, V5, V4, V3) """ A tuple of all supported protocol versions """ diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 679293a52d..68ab370f93 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -75,7 +75,7 @@ NoSpeculativeExecutionPolicy, DefaultLoadBalancingPolicy, NeverRetryPolicy) from cassandra.pool import (Host, _ReconnectionHandler, _HostReconnectionHandler, - HostConnectionPool, HostConnection, + HostConnection, NoConnectionsAvailable) from cassandra.query import (SimpleStatement, PreparedStatement, BoundStatement, BatchStatement, bind_params, QueryTrace, TraceUnavailable, @@ -731,9 +731,6 @@ def auth_provider(self): be an instance of a subclass of :class:`~cassandra.auth.AuthProvider`, such as :class:`~.PlainTextAuthProvider`. - When :attr:`~.Cluster.protocol_version` is 1, this should be - a function that accepts one argument, the IP address of a node, - and returns a dict of credentials for that node. When not using authentication, this should be left as :const:`None`. """ @@ -1452,18 +1449,6 @@ def __init__(self, self._user_types = defaultdict(dict) - self._min_requests_per_connection = { - HostDistance.LOCAL_RACK: DEFAULT_MIN_REQUESTS, - HostDistance.LOCAL: DEFAULT_MIN_REQUESTS, - HostDistance.REMOTE: DEFAULT_MIN_REQUESTS - } - - self._max_requests_per_connection = { - HostDistance.LOCAL_RACK: DEFAULT_MAX_REQUESTS, - HostDistance.LOCAL: DEFAULT_MAX_REQUESTS, - HostDistance.REMOTE: DEFAULT_MAX_REQUESTS - } - self._core_connections_per_host = { HostDistance.LOCAL_RACK: DEFAULT_MIN_CONNECTIONS_PER_LOCAL_HOST, HostDistance.LOCAL: DEFAULT_MIN_CONNECTIONS_PER_LOCAL_HOST, @@ -1666,48 +1651,6 @@ def add_execution_profile(self, name, profile, pool_wait_timeout=5): if not_done: raise OperationTimedOut("Failed to create all new connection pools in the %ss timeout.") - def get_min_requests_per_connection(self, host_distance): - return self._min_requests_per_connection[host_distance] - - def set_min_requests_per_connection(self, host_distance, min_requests): - """ - Sets a threshold for concurrent requests per connection, below which - connections will be considered for disposal (down to core connections; - see :meth:`~Cluster.set_core_connections_per_host`). - - Pertains to connection pool management in protocol versions {1,2}. - """ - if self.protocol_version >= 3: - raise UnsupportedOperation( - "Cluster.set_min_requests_per_connection() only has an effect " - "when using protocol_version 1 or 2.") - if min_requests < 0 or min_requests > 126 or \ - min_requests >= self._max_requests_per_connection[host_distance]: - raise ValueError("min_requests must be 0-126 and less than the max_requests for this host_distance (%d)" % - (self._min_requests_per_connection[host_distance],)) - self._min_requests_per_connection[host_distance] = min_requests - - def get_max_requests_per_connection(self, host_distance): - return self._max_requests_per_connection[host_distance] - - def set_max_requests_per_connection(self, host_distance, max_requests): - """ - Sets a threshold for concurrent requests per connection, above which new - connections will be created to a host (up to max connections; - see :meth:`~Cluster.set_max_connections_per_host`). - - Pertains to connection pool management in protocol versions {1,2}. - """ - if self.protocol_version >= 3: - raise UnsupportedOperation( - "Cluster.set_max_requests_per_connection() only has an effect " - "when using protocol_version 1 or 2.") - if max_requests < 1 or max_requests > 127 or \ - max_requests <= self._min_requests_per_connection[host_distance]: - raise ValueError("max_requests must be 1-127 and greater than the min_requests for this host_distance (%d)" % - (self._min_requests_per_connection[host_distance],)) - self._max_requests_per_connection[host_distance] = max_requests - def get_core_connections_per_host(self, host_distance): """ Gets the minimum number of connections per Session that will be opened @@ -3101,13 +3044,11 @@ def _create_response_future(self, query, parameters, trace, custom_payload, spec_exec_policy = execution_profile.speculative_execution_policy fetch_size = query.fetch_size - if fetch_size is FETCH_SIZE_UNSET and self._protocol_version >= 2: + if fetch_size is FETCH_SIZE_UNSET: fetch_size = self.default_fetch_size - elif self._protocol_version == 1: - fetch_size = None start_time = time.time() - if self._protocol_version >= 3 and self.use_client_timestamp: + if self.use_client_timestamp: timestamp = self.cluster.timestamp_generator() else: timestamp = None @@ -3378,11 +3319,7 @@ def add_or_renew_pool(self, host, is_host_addition): def run_add_or_renew_pool(): try: - if self._protocol_version >= 3: - new_pool = HostConnection(host, distance, self) - else: - # TODO remove host pool again ??? - new_pool = HostConnectionPool(host, distance, self) + new_pool = HostConnection(host, distance, self) except AuthenticationFailed as auth_exc: conn_exc = ConnectionException(str(auth_exc), endpoint=host) self.cluster.signal_connection_failure(host, conn_exc, is_host_addition) diff --git a/cassandra/connection.py b/cassandra/connection.py index e1646cafc1..2d998fb805 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -123,7 +123,6 @@ def decompress(byts): DEFAULT_LOCAL_PORT_LOW = 49152 DEFAULT_LOCAL_PORT_HIGH = 65535 -frame_header_v1_v2 = struct.Struct('>BbBi') frame_header_v3 = struct.Struct('>BhBi') @@ -817,17 +816,12 @@ def __init__(self, host='127.0.0.1', port=9042, authenticator=None, if not self.ssl_context and self.ssl_options: self.ssl_context = self._build_ssl_context_from_options() - if protocol_version >= 3: - self.max_request_id = min(self.max_in_flight - 1, (2 ** 15) - 1) - # Don't fill the deque with 2**15 items right away. Start with some and add - # more if needed. - initial_size = min(300, self.max_in_flight) - self.request_ids = deque(range(initial_size)) - self.highest_request_id = initial_size - 1 - else: - self.max_request_id = min(self.max_in_flight, (2 ** 7) - 1) - self.request_ids = deque(range(self.max_request_id + 1)) - self.highest_request_id = self.max_request_id + self.max_request_id = min(self.max_in_flight - 1, (2 ** 15) - 1) + # Don't fill the deque with 2**15 items right away. Start with some and add + # more if needed. + initial_size = min(300, self.max_in_flight) + self.request_ids = deque(range(initial_size)) + self.highest_request_id = initial_size - 1 self.lock = RLock() self.connected_event = Event() @@ -1205,11 +1199,10 @@ def _read_frame_header(self): version = buf[0] & PROTOCOL_VERSION_MASK if version not in ProtocolVersion.SUPPORTED_VERSIONS: raise ProtocolError("This version of the driver does not support protocol version %d" % version) - frame_header = frame_header_v3 if version >= 3 else frame_header_v1_v2 # this frame header struct is everything after the version byte - header_size = frame_header.size + 1 + header_size = frame_header_v3.size + 1 if pos >= header_size: - flags, stream, op, body_len = frame_header.unpack_from(buf, 1) + flags, stream, op, body_len = frame_header_v3.unpack_from(buf, 1) if body_len < 0: raise ProtocolError("Received negative body length: %r" % body_len) self._current_frame = _Frame(version, flags, stream, op, header_size, body_len + header_size) diff --git a/cassandra/cqltypes.py b/cassandra/cqltypes.py index d1580f00ff..e36c48563c 100644 --- a/cassandra/cqltypes.py +++ b/cassandra/cqltypes.py @@ -812,18 +812,13 @@ class _SimpleParameterizedType(_ParameterizedType): @classmethod def deserialize_safe(cls, byts, protocol_version): subtype, = cls.subtypes - if protocol_version >= 3: - unpack = int32_unpack - length = 4 - else: - unpack = uint16_unpack - length = 2 - numelements = unpack(byts[:length]) + length = 4 + numelements = int32_unpack(byts[:length]) p = length result = [] inner_proto = max(3, protocol_version) for _ in range(numelements): - itemlen = unpack(byts[p:p + length]) + itemlen = int32_unpack(byts[p:p + length]) p += length if itemlen < 0: result.append(None) @@ -839,16 +834,15 @@ def serialize_safe(cls, items, protocol_version): raise TypeError("Received a string for a type that expects a sequence") subtype, = cls.subtypes - pack = int32_pack if protocol_version >= 3 else uint16_pack buf = io.BytesIO() - buf.write(pack(len(items))) + buf.write(int32_pack(len(items))) inner_proto = max(3, protocol_version) for item in items: if item is None: - buf.write(pack(-1)) + buf.write(int32_pack(-1)) else: itembytes = subtype.to_binary(item, inner_proto) - buf.write(pack(len(itembytes))) + buf.write(int32_pack(len(itembytes))) buf.write(itembytes) return buf.getvalue() @@ -872,18 +866,13 @@ class MapType(_ParameterizedType): @classmethod def deserialize_safe(cls, byts, protocol_version): key_type, value_type = cls.subtypes - if protocol_version >= 3: - unpack = int32_unpack - length = 4 - else: - unpack = uint16_unpack - length = 2 - numelements = unpack(byts[:length]) + length = 4 + numelements = int32_unpack(byts[:length]) p = length themap = util.OrderedMapSerializedKey(key_type, protocol_version) inner_proto = max(3, protocol_version) for _ in range(numelements): - key_len = unpack(byts[p:p + length]) + key_len = int32_unpack(byts[p:p + length]) p += length if key_len < 0: keybytes = None @@ -893,7 +882,7 @@ def deserialize_safe(cls, byts, protocol_version): p += key_len key = key_type.from_binary(keybytes, inner_proto) - val_len = unpack(byts[p:p + length]) + val_len = int32_unpack(byts[p:p + length]) p += length if val_len < 0: val = None @@ -908,9 +897,8 @@ def deserialize_safe(cls, byts, protocol_version): @classmethod def serialize_safe(cls, themap, protocol_version): key_type, value_type = cls.subtypes - pack = int32_pack if protocol_version >= 3 else uint16_pack buf = io.BytesIO() - buf.write(pack(len(themap))) + buf.write(int32_pack(len(themap))) try: items = themap.items() except AttributeError: @@ -919,16 +907,16 @@ def serialize_safe(cls, themap, protocol_version): for key, val in items: if key is not None: keybytes = key_type.to_binary(key, inner_proto) - buf.write(pack(len(keybytes))) + buf.write(int32_pack(len(keybytes))) buf.write(keybytes) else: - buf.write(pack(-1)) + buf.write(int32_pack(-1)) if val is not None: valbytes = value_type.to_binary(val, inner_proto) - buf.write(pack(len(valbytes))) + buf.write(int32_pack(len(valbytes))) buf.write(valbytes) else: - buf.write(pack(-1)) + buf.write(int32_pack(-1)) return buf.getvalue() diff --git a/cassandra/marshal.py b/cassandra/marshal.py index a527a9e1d7..413e1831d4 100644 --- a/cassandra/marshal.py +++ b/cassandra/marshal.py @@ -33,11 +33,6 @@ def _make_packer(format_string): float_pack, float_unpack = _make_packer('>f') double_pack, double_unpack = _make_packer('>d') -# Special case for cassandra header -header_struct = struct.Struct('>BBbB') -header_pack = header_struct.pack -header_unpack = header_struct.unpack - # in protocol version 3 and higher, the stream ID is two bytes v3_header_struct = struct.Struct('>BBhB') v3_header_pack = v3_header_struct.pack diff --git a/cassandra/metadata.py b/cassandra/metadata.py index 30bcf81654..25d7561989 100644 --- a/cassandra/metadata.py +++ b/cassandra/metadata.py @@ -153,12 +153,7 @@ def refresh(self, connection, timeout, target_type=None, change_type=None, fetch meta = parse_method(self.keyspaces, **kwargs) if meta: update_method = getattr(self, '_update_' + tt_lower) - if tt_lower == 'keyspace' and connection.protocol_version < 3: - # we didn't have 'type' target in legacy protocol versions, so we need to query those too - user_types = parser.get_types_map(self.keyspaces, **kwargs) - self._update_keyspace(meta, user_types) - else: - update_method(meta) + update_method(meta) else: drop_method = getattr(self, '_drop_' + tt_lower) drop_method(**kwargs) diff --git a/cassandra/pool.py b/cassandra/pool.py index 8bb318f582..70abb6eccc 100644 --- a/cassandra/pool.py +++ b/cassandra/pool.py @@ -374,8 +374,7 @@ def on_exception(self, exc, next_delay): class HostConnection(object): """ - When using v3 of the native protocol, this is used instead of a connection - pool per host (HostConnectionPool) due to the increased in-flight capacity + When using v3 of the native protocol, this is useddue to the increased in-flight capacity of individual connections. """ @@ -929,365 +928,3 @@ def _excess_connection_limit(self): return self.host.sharding_info.shards_count * self.max_excess_connections_per_shard_multiplier -_MAX_SIMULTANEOUS_CREATION = 1 -_MIN_TRASH_INTERVAL = 10 - - -class HostConnectionPool(object): - """ - Used to pool connections to a host for v1 and v2 native protocol. - """ - - host = None - host_distance = None - - is_shutdown = False - open_count = 0 - _scheduled_for_creation = 0 - _next_trash_allowed_at = 0 - _keyspace = None - - def __init__(self, host, host_distance, session): - self.host = host - self.host_distance = host_distance - - self._session = weakref.proxy(session) - self._lock = RLock() - self._conn_available_condition = Condition() - - log.debug("Initializing new connection pool for host %s", self.host) - core_conns = session.cluster.get_core_connections_per_host(host_distance) - self._connections = [session.cluster.connection_factory(host.endpoint, on_orphaned_stream_released=self.on_orphaned_stream_released) - for i in range(core_conns)] - - self._keyspace = session.keyspace - if self._keyspace: - for conn in self._connections: - conn.set_keyspace_blocking(self._keyspace) - - self._trash = set() - self._next_trash_allowed_at = time.time() - self.open_count = core_conns - log.debug("Finished initializing new connection pool for host %s", self.host) - - def borrow_connection(self, timeout, routing_key=None): - if self.is_shutdown: - raise ConnectionException( - "Pool for %s is shutdown" % (self.host,), self.host) - - conns = self._connections - if not conns: - # handled specially just for simpler code - log.debug("Detected empty pool, opening core conns to %s", self.host) - core_conns = self._session.cluster.get_core_connections_per_host(self.host_distance) - with self._lock: - # we check the length of self._connections again - # along with self._scheduled_for_creation while holding the lock - # in case multiple threads hit this condition at the same time - to_create = core_conns - (len(self._connections) + self._scheduled_for_creation) - for i in range(to_create): - self._scheduled_for_creation += 1 - self._session.submit(self._create_new_connection) - - # in_flight is incremented by wait_for_conn - conn = self._wait_for_conn(timeout) - return conn - else: - # note: it would be nice to push changes to these config settings - # to pools instead of doing a new lookup on every - # borrow_connection() call - max_reqs = self._session.cluster.get_max_requests_per_connection(self.host_distance) - max_conns = self._session.cluster.get_max_connections_per_host(self.host_distance) - - least_busy = min(conns, key=lambda c: c.in_flight) - request_id = None - # to avoid another thread closing this connection while - # trashing it (through the return_connection process), hold - # the connection lock from this point until we've incremented - # its in_flight count - need_to_wait = False - with least_busy.lock: - if least_busy.in_flight < least_busy.max_request_id: - least_busy.in_flight += 1 - request_id = least_busy.get_request_id() - else: - # once we release the lock, wait for another connection - need_to_wait = True - - if need_to_wait: - # wait_for_conn will increment in_flight on the conn - least_busy, request_id = self._wait_for_conn(timeout) - - # if we have too many requests on this connection but we still - # have space to open a new connection against this host, go ahead - # and schedule the creation of a new connection - if least_busy.in_flight >= max_reqs and len(self._connections) < max_conns: - self._maybe_spawn_new_connection() - - return least_busy, request_id - - def _maybe_spawn_new_connection(self): - with self._lock: - if self._scheduled_for_creation >= _MAX_SIMULTANEOUS_CREATION: - return - if self.open_count >= self._session.cluster.get_max_connections_per_host(self.host_distance): - return - self._scheduled_for_creation += 1 - - log.debug("Submitting task for creation of new Connection to %s", self.host) - self._session.submit(self._create_new_connection) - - def _create_new_connection(self): - try: - self._add_conn_if_under_max() - except (ConnectionException, socket.error) as exc: - log.warning("Failed to create new connection to %s: %s", self.host, exc) - except Exception: - log.exception("Unexpectedly failed to create new connection") - finally: - with self._lock: - self._scheduled_for_creation -= 1 - - def _add_conn_if_under_max(self): - max_conns = self._session.cluster.get_max_connections_per_host(self.host_distance) - with self._lock: - if self.is_shutdown: - return True - - if self.open_count >= max_conns: - return True - - self.open_count += 1 - - log.debug("Going to open new connection to host %s", self.host) - try: - conn = self._session.cluster.connection_factory(self.host.endpoint, on_orphaned_stream_released=self.on_orphaned_stream_released) - if self._keyspace: - conn.set_keyspace_blocking(self._session.keyspace) - self._next_trash_allowed_at = time.time() + _MIN_TRASH_INTERVAL - with self._lock: - new_connections = self._connections[:] + [conn] - self._connections = new_connections - log.debug("Added new connection (%s) to pool for host %s, signaling availablility", - id(conn), self.host) - self._signal_available_conn() - return True - except (ConnectionException, socket.error) as exc: - log.warning("Failed to add new connection to pool for host %s: %s", self.host, exc) - with self._lock: - self.open_count -= 1 - if self._session.cluster.signal_connection_failure(self.host, exc, is_host_addition=False): - self.shutdown() - return False - except AuthenticationFailed: - with self._lock: - self.open_count -= 1 - return False - - def _await_available_conn(self, timeout): - with self._conn_available_condition: - self._conn_available_condition.wait(timeout) - - def _signal_available_conn(self): - with self._conn_available_condition: - self._conn_available_condition.notify() - - def _signal_all_available_conn(self): - with self._conn_available_condition: - self._conn_available_condition.notify_all() - - def _wait_for_conn(self, timeout): - start = time.time() - remaining = timeout - - while remaining > 0: - # wait on our condition for the possibility that a connection - # is useable - self._await_available_conn(remaining) - - # self.shutdown() may trigger the above Condition - if self.is_shutdown: - raise ConnectionException("Pool is shutdown") - - conns = self._connections - if conns: - least_busy = min(conns, key=lambda c: c.in_flight) - with least_busy.lock: - if least_busy.in_flight < least_busy.max_request_id: - least_busy.in_flight += 1 - return least_busy, least_busy.get_request_id() - - remaining = timeout - (time.time() - start) - - raise NoConnectionsAvailable() - - def return_connection(self, connection, stream_was_orphaned=False): - with connection.lock: - if not stream_was_orphaned: - connection.in_flight -= 1 - in_flight = connection.in_flight - - if connection.is_defunct or connection.is_closed: - if not connection.signaled_error: - log.debug("Defunct or closed connection (%s) returned to pool, potentially " - "marking host %s as down", id(connection), self.host) - is_down = self._session.cluster.signal_connection_failure( - self.host, connection.last_error, is_host_addition=False) - connection.signaled_error = True - if is_down: - self.shutdown() - else: - self._replace(connection) - elif connection in self._trash: - with connection.lock: - no_pending_requests = connection.in_flight <= len(connection.orphaned_request_ids) - if no_pending_requests: - with self._lock: - close_connection = False - if connection in self._trash: - self._trash.remove(connection) - close_connection = True - if close_connection: - log.debug("Closing trashed connection (%s) to %s", id(connection), self.host) - connection.close() - return - - core_conns = self._session.cluster.get_core_connections_per_host(self.host_distance) - min_reqs = self._session.cluster.get_min_requests_per_connection(self.host_distance) - # we can use in_flight here without holding the connection lock - # because the fact that in_flight dipped below the min at some - # point is enough to start the trashing procedure - if len(self._connections) > core_conns and in_flight <= min_reqs and \ - time.time() >= self._next_trash_allowed_at: - self._maybe_trash_connection(connection) - else: - self._signal_available_conn() - - def on_orphaned_stream_released(self): - """ - Called when a response for an orphaned stream (timed out on the client - side) was received. - """ - self._signal_available_conn() - - def _maybe_trash_connection(self, connection): - core_conns = self._session.cluster.get_core_connections_per_host(self.host_distance) - did_trash = False - with self._lock: - if connection not in self._connections: - return - - if self.open_count > core_conns: - did_trash = True - self.open_count -= 1 - new_connections = self._connections[:] - new_connections.remove(connection) - self._connections = new_connections - - if did_trash: - with connection.lock: - no_pending_requests = connection.in_flight <= len(connection.orphaned_request_ids) - if no_pending_requests: - log.debug("Skipping trash and closing unused connection (%s) to %s", id(connection), self.host) - connection.close() - return - with self._lock: - self._trash.add(connection) - - if did_trash: - self._next_trash_allowed_at = time.time() + _MIN_TRASH_INTERVAL - log.debug("Trashed connection (%s) to %s", id(connection), self.host) - - def _replace(self, connection): - should_replace = False - with self._lock: - if connection in self._connections: - new_connections = self._connections[:] - new_connections.remove(connection) - self._connections = new_connections - self.open_count -= 1 - should_replace = True - - if should_replace: - log.debug("Replacing connection (%s) to %s", id(connection), self.host) - connection.close() - self._session.submit(self._retrying_replace) - else: - log.debug("Closing connection (%s) to %s", id(connection), self.host) - connection.close() - - def _retrying_replace(self): - replaced = False - try: - replaced = self._add_conn_if_under_max() - except Exception: - log.exception("Failed replacing connection to %s", self.host) - if not replaced: - log.debug("Failed replacing connection to %s. Retrying.", self.host) - self._session.submit(self._retrying_replace) - - def shutdown(self): - with self._lock: - if self.is_shutdown: - return - else: - self.is_shutdown = True - - self._signal_all_available_conn() - - connections_to_close = [] - with self._lock: - connections_to_close.extend(self._connections) - self.open_count -= len(self._connections) - self._connections.clear() - connections_to_close.extend(self._trash) - self._trash.clear() - - for conn in connections_to_close: - conn.close() - - def ensure_core_connections(self): - if self.is_shutdown: - return - - core_conns = self._session.cluster.get_core_connections_per_host(self.host_distance) - with self._lock: - to_create = core_conns - (len(self._connections) + self._scheduled_for_creation) - for i in range(to_create): - self._scheduled_for_creation += 1 - self._session.submit(self._create_new_connection) - - def _set_keyspace_for_all_conns(self, keyspace, callback): - """ - Asynchronously sets the keyspace for all connections. When all - connections have been set, `callback` will be called with two - arguments: this pool, and a list of any errors that occurred. - """ - remaining_callbacks = set(self._connections) - errors = [] - - if not remaining_callbacks: - callback(self, errors) - return - - def connection_finished_setting_keyspace(conn, error): - self.return_connection(conn) - remaining_callbacks.remove(conn) - if error: - errors.append(error) - - if not remaining_callbacks: - callback(self, errors) - - self._keyspace = keyspace - for conn in self._connections: - conn.set_keyspace_async(keyspace, connection_finished_setting_keyspace) - - def get_connections(self): - return self._connections - - def get_state(self): - in_flights = [c.in_flight for c in self._connections] - orphan_requests = [c.orphaned_request_ids for c in self._connections] - return {'shutdown': self.is_shutdown, 'open_count': self.open_count, \ - 'in_flights': in_flights, 'orphan_requests': orphan_requests} diff --git a/cassandra/protocol.py b/cassandra/protocol.py index 29ae404048..5fe4ed2be4 100644 --- a/cassandra/protocol.py +++ b/cassandra/protocol.py @@ -36,7 +36,7 @@ TupleType, lookup_casstype, SimpleDateType, TimeType, ByteType, ShortType, DurationType) from cassandra.marshal import (int32_pack, int32_unpack, uint16_pack, uint16_unpack, - uint8_pack, int8_unpack, uint64_pack, header_pack, + uint8_pack, int8_unpack, uint64_pack, v3_header_pack, uint32_pack, uint32_le_unpack, uint32_le_pack) from cassandra.policies import ColDesc from cassandra import WriteType @@ -563,29 +563,13 @@ def _write_query_params(self, f, protocol_version): flags |= _VALUES_FLAG # also v2+, but we're only setting params internally right now if self.serial_consistency_level: - if protocol_version >= 2: - flags |= _WITH_SERIAL_CONSISTENCY_FLAG - else: - raise UnsupportedOperation( - "Serial consistency levels require the use of protocol version " - "2 or higher. Consider setting Cluster.protocol_version to 2 " - "to support serial consistency levels.") + flags |= _WITH_SERIAL_CONSISTENCY_FLAG if self.fetch_size: - if protocol_version >= 2: - flags |= _PAGE_SIZE_FLAG - else: - raise UnsupportedOperation( - "Automatic query paging may only be used with protocol version " - "2 or higher. Consider setting Cluster.protocol_version to 2.") + flags |= _PAGE_SIZE_FLAG if self.paging_state: - if protocol_version >= 2: - flags |= _WITH_PAGING_STATE_FLAG - else: - raise UnsupportedOperation( - "Automatic query paging may only be used with protocol version " - "2 or higher. Consider setting Cluster.protocol_version to 2.") + flags |= _WITH_PAGING_STATE_FLAG if self.timestamp is not None: flags |= _PROTOCOL_TIMESTAMP_FLAG @@ -664,22 +648,7 @@ def __init__(self, query_id, query_params, consistency_level, paging_state, timestamp, skip_meta, continuous_paging_options) def _write_query_params(self, f, protocol_version): - if protocol_version == 1: - if self.serial_consistency_level: - raise UnsupportedOperation( - "Serial consistency levels require the use of protocol version " - "2 or higher. Consider setting Cluster.protocol_version to 2 " - "to support serial consistency levels.") - if self.fetch_size or self.paging_state: - raise UnsupportedOperation( - "Automatic query paging may only be used with protocol version " - "2 or higher. Consider setting Cluster.protocol_version to 2.") - write_short(f, len(self.query_params)) - for param in self.query_params: - write_value(f, param) - write_consistency_level(f, self.consistency_level) - else: - super(ExecuteMessage, self)._write_query_params(f, protocol_version) + super(ExecuteMessage, self)._write_query_params(f, protocol_version) def send_body(self, f, protocol_version): write_string(f, self.query_id) @@ -853,8 +822,7 @@ def recv_prepared_metadata(self, f, protocol_version, user_type_map): coltype = self.read_type(f, user_type_map) bind_metadata.append(ColumnMetadata(colksname, colcfname, colname, coltype)) - if protocol_version >= 2: - self.recv_results_metadata(f, user_type_map) + self.recv_results_metadata(f, user_type_map) self.bind_metadata = bind_metadata self.pk_indexes = pk_indexes @@ -969,33 +937,31 @@ def send_body(self, f, protocol_version): write_value(f, param) write_consistency_level(f, self.consistency_level) - if protocol_version >= 3: - flags = 0 - if self.serial_consistency_level: - flags |= _WITH_SERIAL_CONSISTENCY_FLAG - if self.timestamp is not None: - flags |= _PROTOCOL_TIMESTAMP_FLAG - if self.keyspace: - if ProtocolVersion.uses_keyspace_flag(protocol_version): - flags |= _WITH_KEYSPACE_FLAG - else: - raise UnsupportedOperation( - "Keyspaces may only be set on queries with protocol version " - "5 or higher. Consider setting Cluster.protocol_version to 5.") - - if ProtocolVersion.uses_int_query_flags(protocol_version): - write_int(f, flags) + flags = 0 + if self.serial_consistency_level: + flags |= _WITH_SERIAL_CONSISTENCY_FLAG + if self.timestamp is not None: + flags |= _PROTOCOL_TIMESTAMP_FLAG + if self.keyspace: + if ProtocolVersion.uses_keyspace_flag(protocol_version): + flags |= _WITH_KEYSPACE_FLAG else: - write_byte(f, flags) + raise UnsupportedOperation( + "Keyspaces may only be set on queries with protocol version " + "5 or higher. Consider setting Cluster.protocol_version to 5.") + if ProtocolVersion.uses_int_query_flags(protocol_version): + write_int(f, flags) + else: + write_byte(f, flags) - if self.serial_consistency_level: - write_consistency_level(f, self.serial_consistency_level) - if self.timestamp is not None: - write_long(f, self.timestamp) + if self.serial_consistency_level: + write_consistency_level(f, self.serial_consistency_level) + if self.timestamp is not None: + write_long(f, self.timestamp) - if ProtocolVersion.uses_keyspace_flag(protocol_version): - if self.keyspace is not None: - write_string(f, self.keyspace) + if ProtocolVersion.uses_keyspace_flag(protocol_version): + if self.keyspace is not None: + write_string(f, self.keyspace) known_event_types = frozenset(( @@ -1050,25 +1016,17 @@ def recv_status_change(cls, f, protocol_version): def recv_schema_change(cls, f, protocol_version): # "CREATED", "DROPPED", or "UPDATED" change_type = read_string(f) - if protocol_version >= 3: - target = read_string(f) - keyspace = read_string(f) - event = {'target_type': target, 'change_type': change_type, 'keyspace': keyspace} - if target != SchemaTargetType.KEYSPACE: - target_name = read_string(f) - if target == SchemaTargetType.FUNCTION: - event['function'] = UserFunctionDescriptor(target_name, [read_string(f) for _ in range(read_short(f))]) - elif target == SchemaTargetType.AGGREGATE: - event['aggregate'] = UserAggregateDescriptor(target_name, [read_string(f) for _ in range(read_short(f))]) - else: - event[target.lower()] = target_name - else: - keyspace = read_string(f) - table = read_string(f) - if table: - event = {'target_type': SchemaTargetType.TABLE, 'change_type': change_type, 'keyspace': keyspace, 'table': table} + target = read_string(f) + keyspace = read_string(f) + event = {'target_type': target, 'change_type': change_type, 'keyspace': keyspace} + if target != SchemaTargetType.KEYSPACE: + target_name = read_string(f) + if target == SchemaTargetType.FUNCTION: + event['function'] = UserFunctionDescriptor(target_name, [read_string(f) for _ in range(read_short(f))]) + elif target == SchemaTargetType.AGGREGATE: + event['aggregate'] = UserAggregateDescriptor(target_name, [read_string(f) for _ in range(read_short(f))]) else: - event = {'target_type': SchemaTargetType.KEYSPACE, 'change_type': change_type, 'keyspace': keyspace} + event[target.lower()] = target_name return event @@ -1164,8 +1122,7 @@ def _write_header(f, version, flags, stream_id, opcode, length): """ Write a CQL protocol frame header. """ - pack = v3_header_pack if version >= 3 else header_pack - f.write(pack(version, flags, stream_id, opcode)) + f.write(v3_header_pack(version, flags, stream_id, opcode)) write_int(f, length) @classmethod diff --git a/docs/api/cassandra/cluster.rst b/docs/api/cassandra/cluster.rst index a9a9d378a4..81da28c052 100644 --- a/docs/api/cassandra/cluster.rst +++ b/docs/api/cassandra/cluster.rst @@ -86,14 +86,6 @@ .. automethod:: add_execution_profile - .. automethod:: set_max_requests_per_connection - - .. automethod:: get_max_requests_per_connection - - .. automethod:: set_min_requests_per_connection - - .. automethod:: get_min_requests_per_connection - .. automethod:: get_core_connections_per_host .. automethod:: set_core_connections_per_host diff --git a/docs/security.rst b/docs/security.rst index c30189562f..57e2be71da 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -37,23 +37,6 @@ If these do not suit your needs, you may need to create your own subclasses of :class:`~.AuthProvider` and :class:`~.Authenticator`. You can use the Sasl classes as example implementations. -Protocol v1 Authentication -^^^^^^^^^^^^^^^^^^^^^^^^^^ -When working with Cassandra 1.2 (or a higher version with -:attr:`~.Cluster.protocol_version` set to ``1``), you will not pass in -an :class:`~.AuthProvider` instance. Instead, you should pass in a -function that takes one argument, the IP address of a host, and returns -a dict of credentials with a ``username`` and ``password`` key: - -.. code-block:: python - - from cassandra.cluster import Cluster - - def get_credentials(host_address): - return {'username': 'joe', 'password': '1234'} - - cluster = Cluster(auth_provider=get_credentials, protocol_version=1) - SSL --- SSL should be used when client encryption is enabled in Cassandra. diff --git a/docs/upgrading.rst b/docs/upgrading.rst index d0d40e46b5..3efcf3db61 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -183,14 +183,17 @@ Upgrading to 2.1 from 2.0 Version 2.1 of the DataStax Python driver for Apache Cassandra adds support for Cassandra 2.1 and version 3 of the native protocol. -Cassandra 1.2, 2.0, and 2.1 are all supported. However, 1.2 only +Cassandra 1.2, 2.0 are not supported: 1.2 only supports protocol version 1, and 2.0 only supports versions 1 and -2, so some features may not be available. +2, both of which were removed from the driver. Using the v3 Native Protocol ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -By default, the driver will attempt to use version 2 of the -native protocol. To use version 3, you must explicitly +By default, the driver will attempt to use highest version of the +native protocol supported by the server. Protocol v3 is the lowest +version supported. + +To use version 3, you must explicitly set the :attr:`~.Cluster.protocol_version`: .. code-block:: python @@ -201,9 +204,6 @@ set the :attr:`~.Cluster.protocol_version`: Note that protocol version 3 is only supported by Cassandra 2.1+. -In future releases, the driver may default to using protocol version -3. - Working with User-Defined Types ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cassandra 2.1 introduced the ability to define new types:: @@ -266,35 +266,6 @@ unless :attr:`.Session.use_client_timestamp` is changed to :const:`False`. If a timestamp is specified within the CQL query, it will override the timestamp generated by the driver. -Upgrading to 2.0 from 1.x -------------------------- -Version 2.0 of the DataStax Python driver for Apache Cassandra -includes some notable improvements over version 1.x. This version -of the driver supports Cassandra 1.2, 2.0, and 2.1. However, not -all features may be used with Cassandra 1.2, and some new features -in 2.1 are not yet supported. - -Using the v2 Native Protocol -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -By default, the driver will attempt to use version 2 of Cassandra's -native protocol. You can explicitly set the protocol version to -2, though: - -.. code-block:: python - - from cassandra.cluster import Cluster - - cluster = Cluster(protocol_version=2) - -When working with Cassandra 1.2, you will need to -explicitly set the :attr:`~.Cluster.protocol_version` to 1: - -.. code-block:: python - - from cassandra.cluster import Cluster - - cluster = Cluster(protocol_version=1) - Automatic Query Paging ^^^^^^^^^^^^^^^^^^^^^^ Version 2 of the native protocol adds support for automatic query diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index f00d4c7126..75e1df4261 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -241,8 +241,6 @@ def get_default_protocol(): return 4 elif CASSANDRA_VERSION >= Version('2.1'): return 3 - elif CASSANDRA_VERSION >= Version('2.0'): - return 2 else: raise Exception("Running tests with an unsupported Cassandra version: {0}".format(CASSANDRA_VERSION)) @@ -260,10 +258,8 @@ def get_scylla_default_protocol(): def get_supported_protocol_versions(): """ - 1.2 -> 1 - 2.0 -> 2, 1 - 2.1 -> 3, 2, 1 - 2.2 -> 4, 3, 2, 1 + 2.1 -> 3 + 2.2 -> 4, 3 3.X -> 4, 3 3.10(C*) -> 5(beta),4,3 3.10(DSE) -> DSE_V1,4,3 @@ -286,13 +282,11 @@ def get_supported_protocol_versions(): elif CASSANDRA_VERSION >= Version('3.0'): return (3, 4) elif CASSANDRA_VERSION >= Version('2.2'): - return (1,2, 3, 4) + return (3, 4) elif CASSANDRA_VERSION >= Version('2.1'): - return (1, 2, 3) - elif CASSANDRA_VERSION >= Version('2.0'): - return (1, 2) + return (3) else: - return (1,) + return (3,) def get_unsupported_lower_protocol(): @@ -354,8 +348,6 @@ def _id_and_mark(f): local = local_decorator_creator() notprotocolv1 = unittest.skipUnless(PROTOCOL_VERSION > 1, 'Protocol v1 not supported') -lessthenprotocolv4 = unittest.skipUnless(PROTOCOL_VERSION < 4, 'Protocol versions 4 or greater not supported') -lessthanprotocolv3 = unittest.skipUnless(PROTOCOL_VERSION < 3, 'Protocol versions 3 or greater not supported') greaterthanprotocolv3 = unittest.skipUnless(PROTOCOL_VERSION >= 4, 'Protocol versions less than 4 are not supported') protocolv6 = unittest.skipUnless(6 in get_supported_protocol_versions(), 'Protocol versions less than 6 are not supported') diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index cdfc7c1b82..9c01fc00a9 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -41,7 +41,7 @@ from tests import notwindows, notasyncio from tests.integration import use_cluster, get_server_versions, CASSANDRA_VERSION, \ execute_until_pass, execute_with_long_wait_retry, get_node, MockLoggingHandler, get_unsupported_lower_protocol, \ - get_unsupported_upper_protocol, lessthanprotocolv3, protocolv6, local, CASSANDRA_IP, greaterthanorequalcass30, \ + get_unsupported_upper_protocol, protocolv6, local, CASSANDRA_IP, greaterthanorequalcass30, \ lessthanorequalcass40, DSE_VERSION, TestCluster, PROTOCOL_VERSION, xfail_scylla, incorrect_test from tests.integration.util import assert_quiescent_pool_state import sys @@ -408,34 +408,6 @@ def test_connect_to_bad_hosts(self): protocol_version=PROTOCOL_VERSION) self.assertRaises(NoHostAvailable, cluster.connect) - @lessthanprotocolv3 - def test_cluster_settings(self): - """ - Test connection setting getters and setters - """ - - cluster = TestCluster() - - min_requests_per_connection = cluster.get_min_requests_per_connection(HostDistance.LOCAL) - self.assertEqual(cassandra.cluster.DEFAULT_MIN_REQUESTS, min_requests_per_connection) - cluster.set_min_requests_per_connection(HostDistance.LOCAL, min_requests_per_connection + 1) - self.assertEqual(cluster.get_min_requests_per_connection(HostDistance.LOCAL), min_requests_per_connection + 1) - - max_requests_per_connection = cluster.get_max_requests_per_connection(HostDistance.LOCAL) - self.assertEqual(cassandra.cluster.DEFAULT_MAX_REQUESTS, max_requests_per_connection) - cluster.set_max_requests_per_connection(HostDistance.LOCAL, max_requests_per_connection + 1) - self.assertEqual(cluster.get_max_requests_per_connection(HostDistance.LOCAL), max_requests_per_connection + 1) - - core_connections_per_host = cluster.get_core_connections_per_host(HostDistance.LOCAL) - self.assertEqual(cassandra.cluster.DEFAULT_MIN_CONNECTIONS_PER_LOCAL_HOST, core_connections_per_host) - cluster.set_core_connections_per_host(HostDistance.LOCAL, core_connections_per_host + 1) - self.assertEqual(cluster.get_core_connections_per_host(HostDistance.LOCAL), core_connections_per_host + 1) - - max_connections_per_host = cluster.get_max_connections_per_host(HostDistance.LOCAL) - self.assertEqual(cassandra.cluster.DEFAULT_MAX_CONNECTIONS_PER_LOCAL_HOST, max_connections_per_host) - cluster.set_max_connections_per_host(HostDistance.LOCAL, max_connections_per_host + 1) - self.assertEqual(cluster.get_max_connections_per_host(HostDistance.LOCAL), max_connections_per_host + 1) - def test_refresh_schema(self): cluster = TestCluster() session = cluster.connect() diff --git a/tests/integration/standard/test_connection.py b/tests/integration/standard/test_connection.py index b86a4445af..4a5c23d6bf 100644 --- a/tests/integration/standard/test_connection.py +++ b/tests/integration/standard/test_connection.py @@ -27,7 +27,6 @@ from cassandra.cluster import NoHostAvailable, ConnectionShutdown, ExecutionProfile, EXEC_PROFILE_DEFAULT from cassandra.protocol import QueryMessage 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, \ @@ -168,12 +167,8 @@ def fetch_connections(self, host, cluster): holders = cluster.get_connection_holders() for conn in holders: if host == str(getattr(conn, 'host', '')): - if isinstance(conn, HostConnectionPool): - if conn._connections is not None and (conn._connections): - connections.extend(conn._connections) - else: - if conn._connections and conn._connections: - connections.extend(conn._connections.values()) + if conn._connections and conn._connections: + connections.extend(conn._connections.values()) return connections def wait_for_connections(self, host, cluster): diff --git a/tests/unit/io/utils.py b/tests/unit/io/utils.py index a26d898c5f..fa9017ffa2 100644 --- a/tests/unit/io/utils.py +++ b/tests/unit/io/utils.py @@ -197,10 +197,11 @@ def get_socket(self, connection): def set_socket(self, connection, obj): return setattr(connection, self.socket_attr_name, obj) - def make_header_prefix(self, message_class, version=2, stream_id=0): + def make_header_prefix(self, message_class, version=3, stream_id=0): return bytes().join(map(uint8_pack, [ 0xff & (HEADER_DIRECTION_TO_CLIENT | version), 0, # flags (compression) + 0, # MSB for v3+ stream stream_id, message_class.opcode # opcode ])) diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index ec7d51bc2d..e656dad005 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -105,20 +105,6 @@ def test_invalid_contact_point_types(self): with self.assertRaises(TypeError): Cluster(contact_points="not a sequence", protocol_version=4, connect_timeout=1) - def test_requests_in_flight_threshold(self): - d = HostDistance.LOCAL - mn = 3 - mx = 5 - c = Cluster(protocol_version=2) - c.set_min_requests_per_connection(d, mn) - c.set_max_requests_per_connection(d, mx) - # min underflow, max, overflow - for n in (-1, mx, 127): - self.assertRaises(ValueError, c.set_min_requests_per_connection, d, n) - # max underflow, under min, overflow - for n in (0, mn, 128): - self.assertRaises(ValueError, c.set_max_requests_per_connection, d, n) - def test_port_str(self): """Check port passed as tring is converted and checked properly""" cluster = Cluster(contact_points=['127.0.0.1'], port='1111') @@ -230,10 +216,6 @@ def test_protocol_downgrade_test(self): lower = ProtocolVersion.get_lower_supported(ProtocolVersion.V4) self.assertEqual(ProtocolVersion.V3,lower) lower = ProtocolVersion.get_lower_supported(ProtocolVersion.V3) - self.assertEqual(ProtocolVersion.V2,lower) - lower = ProtocolVersion.get_lower_supported(ProtocolVersion.V2) - self.assertEqual(ProtocolVersion.V1, lower) - lower = ProtocolVersion.get_lower_supported(ProtocolVersion.V1) self.assertEqual(0, lower) self.assertTrue(ProtocolVersion.uses_error_code_map(ProtocolVersion.DSE_V1)) diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 51e6247313..460179e9ff 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -38,21 +38,13 @@ def make_connection(self): return c def make_header_prefix(self, message_class, version=Connection.protocol_version, stream_id=0): - if Connection.protocol_version < 3: - return bytes().join(map(uint8_pack, [ - 0xff & (HEADER_DIRECTION_TO_CLIENT | version), - 0, # flags (compression) - stream_id, - message_class.opcode # opcode - ])) - else: - return bytes().join(map(uint8_pack, [ - 0xff & (HEADER_DIRECTION_TO_CLIENT | version), - 0, # flags (compression) - 0, # MSB for v3+ stream - stream_id, - message_class.opcode # opcode - ])) + return bytes().join(map(uint8_pack, [ + 0xff & (HEADER_DIRECTION_TO_CLIENT | version), + 0, # flags (compression) + 0, # MSB for v3+ stream + stream_id, + message_class.opcode # opcode + ])) def make_options_body(self): options_buf = BytesIO() diff --git a/tests/unit/test_host_connection_pool.py b/tests/unit/test_host_connection_pool.py index b4cb067d2f..252ccb49ca 100644 --- a/tests/unit/test_host_connection_pool.py +++ b/tests/unit/test_host_connection_pool.py @@ -24,7 +24,7 @@ from cassandra.cluster import Session, ShardAwareOptions from cassandra.connection import Connection -from cassandra.pool import HostConnection, HostConnectionPool +from cassandra.pool import HostConnection from cassandra.pool import Host, NoConnectionsAvailable from cassandra.policies import HostDistance, SimpleConvictionPolicy @@ -39,7 +39,6 @@ class _PoolTests(unittest.TestCase): def make_session(self): session = NonCallableMagicMock(spec=Session, keyspace='foobarkeyspace') session.cluster.get_core_connections_per_host.return_value = 1 - session.cluster.get_max_requests_per_connection.return_value = 1 session.cluster.get_max_connections_per_host.return_value = 1 return session @@ -223,55 +222,6 @@ def test_host_equality(self): self.assertNotEqual(b, c, 'Two Host instances should NOT be equal when using two different addresses.') -class HostConnectionPoolTests(_PoolTests): - __test__ = True - PoolImpl = HostConnectionPool - uses_single_connection = False - - def test_all_connections_trashed(self): - host = Mock(spec=Host, address='ip1') - session = self.make_session() - conn = NonCallableMagicMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=False, max_request_id=100, - lock=Lock()) - session.cluster.connection_factory.return_value = conn - session.cluster.get_core_connections_per_host.return_value = 1 - - # manipulate the core connection setting so that we can - # trash the only connection - pool = self.PoolImpl(host, HostDistance.LOCAL, session) - session.cluster.get_core_connections_per_host.return_value = 0 - pool._maybe_trash_connection(conn) - session.cluster.get_core_connections_per_host.return_value = 1 - - submit_called = Event() - - def fire_event(*args, **kwargs): - submit_called.set() - - session.submit.side_effect = fire_event - - def get_conn(): - conn.reset_mock() - c, request_id = pool.borrow_connection(1.0) - self.assertIs(conn, c) - self.assertEqual(1, conn.in_flight) - conn.set_keyspace_blocking.assert_called_once_with('foobarkeyspace') - pool.return_connection(c) - - t = Thread(target=get_conn) - t.start() - - submit_called.wait() - self.assertEqual(1, pool._scheduled_for_creation) - session.submit.assert_called_once_with(pool._create_new_connection) - - # now run the create_new_connection call - pool._create_new_connection() - - t.join() - self.assertEqual(0, conn.in_flight) - - class HostConnectionTests(_PoolTests): __test__ = True PoolImpl = HostConnection diff --git a/tests/unit/test_marshalling.py b/tests/unit/test_marshalling.py index 1fdbfa6a4b..9c368860f3 100644 --- a/tests/unit/test_marshalling.py +++ b/tests/unit/test_marshalling.py @@ -75,10 +75,10 @@ (b'', 'MapType(AsciiType, BooleanType)', None), (b'', 'ListType(FloatType)', None), (b'', 'SetType(LongType)', None), - (b'\x00\x00', 'MapType(DecimalType, BooleanType)', OrderedMapSerializedKey(DecimalType, 0)), - (b'\x00\x00', 'ListType(FloatType)', []), - (b'\x00\x00', 'SetType(IntegerType)', sortedset()), - (b'\x00\x01\x00\x10\xafYC\xa3\xea<\x11\xe1\xabc\xc4,\x03"y\xf0', 'ListType(TimeUUIDType)', [UUID(bytes=b'\xafYC\xa3\xea<\x11\xe1\xabc\xc4,\x03"y\xf0')]), + (b'\x00\x00\x00\x00', 'MapType(DecimalType, BooleanType)', OrderedMapSerializedKey(DecimalType, 3)), + (b'\x00\x00\x00\x00', 'ListType(FloatType)', []), + (b'\x00\x00\x00\x00', 'SetType(IntegerType)', sortedset()), + (b'\x00\x00\x00\x01\x00\x00\x00\x10\xafYC\xa3\xea<\x11\xe1\xabc\xc4,\x03"y\xf0', 'ListType(TimeUUIDType)', [UUID(bytes=b'\xafYC\xa3\xea<\x11\xe1\xabc\xc4,\x03"y\xf0')]), (b'\x80\x00\x00\x01', 'SimpleDateType', Date(1)), (b'\x7f\xff\xff\xff', 'SimpleDateType', Date('1969-12-31')), (b'\x00\x00\x00\x00\x00\x00\x00\x01', 'TimeType', Time(1)), @@ -88,7 +88,7 @@ (b'\x80\x00', 'ShortType', -32768) ) -ordered_map_value = OrderedMapSerializedKey(UTF8Type, 2) +ordered_map_value = OrderedMapSerializedKey(UTF8Type, 3) ordered_map_value._insert(u'\u307fbob', 199) ordered_map_value._insert(u'', -1) ordered_map_value._insert(u'\\', 0) @@ -96,8 +96,8 @@ # these following entries work for me right now, but they're dependent on # vagaries of internal python ordering for unordered types marshalled_value_pairs_unsafe = ( - (b'\x00\x03\x00\x06\xe3\x81\xbfbob\x00\x04\x00\x00\x00\xc7\x00\x00\x00\x04\xff\xff\xff\xff\x00\x01\\\x00\x04\x00\x00\x00\x00', 'MapType(UTF8Type, Int32Type)', ordered_map_value), - (b'\x00\x02\x00\x08@\x01\x99\x99\x99\x99\x99\x9a\x00\x08@\x14\x00\x00\x00\x00\x00\x00', 'SetType(DoubleType)', sortedset([2.2, 5.0])), + (b'\x00\x00\x00\x03\x00\x00\x00\x06\xe3\x81\xbfbob\x00\x00\x00\x04\x00\x00\x00\xc7\x00\x00\x00\x00\x00\x00\x00\x04\xff\xff\xff\xff\x00\x00\x00\x01\\\x00\x00\x00\x04\x00\x00\x00\x00', 'MapType(UTF8Type, Int32Type)', ordered_map_value), + (b'\x00\x00\x00\x02\x00\x00\x00\x08@\x01\x99\x99\x99\x99\x99\x9a\x00\x00\x00\x08@\x14\x00\x00\x00\x00\x00\x00', 'SetType(DoubleType)', sortedset([2.2, 5.0])), (b'\x00', 'IntegerType', 0), ) @@ -111,7 +111,7 @@ class UnmarshalTest(unittest.TestCase): def test_unmarshalling(self): for serializedval, valtype, nativeval in marshalled_value_pairs: unmarshaller = lookup_casstype(valtype) - whatwegot = unmarshaller.from_binary(serializedval, 1) + whatwegot = unmarshaller.from_binary(serializedval, 3) self.assertEqual(whatwegot, nativeval, msg='Unmarshaller for %s (%s) failed: unmarshal(%r) got %r instead of %r' % (valtype, unmarshaller, serializedval, whatwegot, nativeval)) @@ -122,7 +122,7 @@ def test_unmarshalling(self): def test_marshalling(self): for serializedval, valtype, nativeval in marshalled_value_pairs: marshaller = lookup_casstype(valtype) - whatwegot = marshaller.to_binary(nativeval, 1) + whatwegot = marshaller.to_binary(nativeval, 3) self.assertEqual(whatwegot, serializedval, msg='Marshaller for %s (%s) failed: marshal(%r) got %r instead of %r' % (valtype, marshaller, nativeval, whatwegot, serializedval)) @@ -132,14 +132,14 @@ def test_marshalling(self): def test_date(self): # separate test because it will deserialize as datetime - self.assertEqual(DateType.from_binary(DateType.to_binary(date(2015, 11, 2), 1), 1), datetime(2015, 11, 2)) + self.assertEqual(DateType.from_binary(DateType.to_binary(date(2015, 11, 2), 3), 3), datetime(2015, 11, 2)) def test_decimal(self): # testing implicit numeric conversion # int, tuple(sign, digits, exp), float converted_types = (10001, (0, (1, 0, 0, 0, 0, 1), -3), 100.1, -87.629798) - for proto_ver in range(1, ProtocolVersion.MAX_SUPPORTED + 1): + for proto_ver in range(3, ProtocolVersion.MAX_SUPPORTED + 1): for n in converted_types: expected = Decimal(n) self.assertEqual(DecimalType.from_binary(DecimalType.to_binary(n, proto_ver), proto_ver), expected) diff --git a/tests/unit/test_orderedmap.py b/tests/unit/test_orderedmap.py index 5d99fc74a8..318b98a52d 100644 --- a/tests/unit/test_orderedmap.py +++ b/tests/unit/test_orderedmap.py @@ -163,7 +163,7 @@ def test_delitem(self): class OrderedMapSerializedKeyTest(unittest.TestCase): def test_init(self): - om = OrderedMapSerializedKey(UTF8Type, 2) + om = OrderedMapSerializedKey(UTF8Type, 3) self.assertEqual(om, {}) def test_normalized_lookup(self): diff --git a/tests/unit/test_parameter_binding.py b/tests/unit/test_parameter_binding.py index 78f3898e01..9c557c0208 100644 --- a/tests/unit/test_parameter_binding.py +++ b/tests/unit/test_parameter_binding.py @@ -68,10 +68,9 @@ def test_float_precision(self): f = 3.4028234663852886e+38 self.assertEqual(float(bind_params("%s", (f,), Encoder())), f) +class BoundStatementTestV3(unittest.TestCase): -class BoundStatementTestV1(unittest.TestCase): - - protocol_version = 1 + protocol_version = 3 @classmethod def setUpClass(cls): @@ -179,15 +178,7 @@ def test_unset_value(self): self.assertRaises(ValueError, self.bound.bind, (0, 0, 0, UNSET_VALUE)) -class BoundStatementTestV2(BoundStatementTestV1): - protocol_version = 2 - - -class BoundStatementTestV3(BoundStatementTestV1): - protocol_version = 3 - - -class BoundStatementTestV4(BoundStatementTestV1): +class BoundStatementTestV4(BoundStatementTestV3): protocol_version = 4 def test_dict_missing_routing_key(self): From 52b7d25bc8006f26afa9e2a428824c6f081469db Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Tue, 15 Jul 2025 13:39:24 +0300 Subject: [PATCH 034/298] Remove docs/upgrading.rst It's really outdated and irrelevant. --- docs/index.rst | 4 - docs/upgrading.rst | 324 --------------------------------------------- 2 files changed, 328 deletions(-) delete mode 100644 docs/upgrading.rst diff --git a/docs/index.rst b/docs/index.rst index f4abf44320..8c38dc315c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -41,9 +41,6 @@ Contents :doc:`security` An overview of the security features of the driver -:doc:`upgrading` - A guide to upgrading versions of the driver - :doc:`user-defined-types` Working with Scylla's user-defined types (UDT) @@ -66,7 +63,6 @@ Contents installation getting-started scylla-specific - upgrading execution-profiles performance query-paging diff --git a/docs/upgrading.rst b/docs/upgrading.rst deleted file mode 100644 index 3efcf3db61..0000000000 --- a/docs/upgrading.rst +++ /dev/null @@ -1,324 +0,0 @@ -Upgrading -========= - -.. toctree:: - :maxdepth: 1 - -Installation -^^^^^^^^^^^^ - -Only the `scylla-driver` package should be installed. `dse-driver` and `dse-graph` -are not required anymore:: - - pip install scylla-driver - -See :doc:`installation` for more details. - -Import from the cassandra module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -There is no `dse` module, so you should import from the `cassandra` module. You -need to change only the first module of your import statements, not the submodules. - -.. code-block:: python - - from dse.cluster import Cluster, EXEC_PROFILE_GRAPH_DEFAULT - from dse.auth import PlainTextAuthProvider - from dse.policies import WhiteListRoundRobinPolicy - - # becomes - - from cassandra.cluster import Cluster, EXEC_PROFILE_GRAPH_DEFAULT - from cassandra.auth import PlainTextAuthProvider - from cassandra.policies import WhiteListRoundRobinPolicy - -Also note that the cassandra.hosts module doesn't exist in scylla-driver. This -module is named cassandra.pool. - -Session.execute and Session.execute_async API -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Although it is not common to use this API with positional arguments, it is -important to be aware that the `host` and `execute_as` parameters have had -their positional order swapped. This is only because `execute_as` was added -in dse-driver before `host`. - -See :meth:`.Session.execute`. - -Deprecations -^^^^^^^^^^^^ - -These changes are optional, but recommended: - -* Use :class:`~.policies.DefaultLoadBalancingPolicy` instead of DSELoadBalancingPolicy. - -Upgrading to 3.0 ----------------- -Version 3.0 of the DataStax Python driver for Apache Cassandra -adds support for Cassandra 3.0 while maintaining support for -previously supported versions. In addition to substantial internal rework, -there are several updates to the API that integrators will need -to consider: - -Default consistency is now ``LOCAL_ONE`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Previous value was ``ONE``. The new value is introduced to mesh with the default -DC-aware load balancing policy and to match other drivers. - -Execution API Updates -^^^^^^^^^^^^^^^^^^^^^ -Result return normalization ---------------------------- -`PYTHON-368 `_ - -Previously results would be returned as a ``list`` of rows for result rows -up to ``fetch_size``, and ``PagedResult`` afterward. This could break -application code that assumed one type and got another. - -Now, all results are returned as an iterable :class:`~.ResultSet`. - -The preferred way to consume results of unknown size is to iterate through -them, letting automatic paging occur as they are consumed. - -.. code-block:: python - - results = session.execute("SELECT * FROM system.local") - for row in results: - process(row) - -If the expected size of the results is known, it is still possible to -materialize a list using the iterator: - -.. code-block:: python - - results = session.execute("SELECT * FROM system.local") - row_list = list(results) - -For backward compatibility, :class:`~.ResultSet` supports indexing. When -accessed at an index, a `~.ResultSet` object will materialize all its pages: - -.. code-block:: python - - results = session.execute("SELECT * FROM system.local") - first_result = results[0] # materializes results, fetching all pages - -This can send requests and load (possibly large) results into memory, so -`~.ResultSet` will log a warning on implicit materialization. - -Trace information is not attached to executed Statements --------------------------------------------------------- -`PYTHON-318 `_ - -Previously trace data was attached to Statements if tracing was enabled. This -could lead to confusion if the same statement was used for multiple executions. - -Now, trace data is associated with the ``ResponseFuture`` and ``ResultSet`` -returned for each query: - -:meth:`.ResponseFuture.get_query_trace()` - -:meth:`.ResponseFuture.get_all_query_traces()` - -:meth:`.ResultSet.get_query_trace()` - -:meth:`.ResultSet.get_all_query_traces()` - -Binding named parameters now ignores extra names ------------------------------------------------- -`PYTHON-178 `_ - -Previously, :meth:`.BoundStatement.bind()` would raise if a mapping -was passed with extra names not found in the prepared statement. - -Behavior in 3.0+ is to ignore extra names. - -blist removed as soft dependency -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -`PYTHON-385 `_ - -Previously the driver had a soft dependency on ``blist sortedset``, using -that where available and using an internal fallback where possible. - -Now, the driver never chooses the ``blist`` variant, instead returning the -internal :class:`.util.SortedSet` for all ``set`` results. The class implements -all standard set operations, so no integration code should need to change unless -it explicitly checks for ``sortedset`` type. - -Metadata API Updates -^^^^^^^^^^^^^^^^^^^^ -`PYTHON-276 `_, `PYTHON-408 `_, `PYTHON-400 `_, `PYTHON-422 `_ - -Cassandra 3.0 brought a substantial overhaul to the internal schema metadata representation. -This version of the driver supports that metadata in addition to the legacy version. Doing so -also brought some changes to the metadata model. - -The present API is documented: :any:`cassandra.metadata`. Changes highlighted below: - -* All types are now exposed as CQL types instead of types derived from the internal server implementation -* Some metadata attributes have changed names to match current nomenclature (for example, :attr:`.Index.kind` in place of ``Index.type``). -* Some metadata attributes removed - - * ``TableMetadata.keyspace`` reference replaced with :attr:`.TableMetadata.keyspace_name` - * ``ColumnMetadata.index`` is removed table- and keyspace-level mappings are still maintained - -Several deprecated features are removed -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -`PYTHON-292 `_ - -* ``ResponseFuture.result`` timeout parameter is removed, use ``Session.execute`` timeout instead (`031ebb0 `_) -* ``Cluster.refresh_schema`` removed, use ``Cluster.refresh_*_metadata`` instead (`419fcdf `_) -* ``Cluster.submit_schema_refresh`` removed (`574266d `_) -* ``cqltypes`` time/date functions removed, use ``util`` entry points instead (`bb984ee `_) -* ``decoder`` module removed (`e16a073 `_) -* ``TableMetadata.keyspace`` attribute replaced with ``keyspace_name`` (`cc94073 `_) -* ``cqlengine.columns.TimeUUID.from_datetime`` removed, use ``util`` variant instead (`96489cc `_) -* ``cqlengine.columns.Float(double_precision)`` parameter removed, use ``columns.Double`` instead (`a2d3a98 `_) -* ``cqlengine`` keyspace management functions are removed in favor of the strategy-specific entry points (`4bd5909 `_) -* ``cqlengine.Model.__polymorphic_*__`` attributes removed, use ``__discriminator*`` attributes instead (`9d98c8e `_) -* ``cqlengine.statements`` will no longer warn about list list prepend behavior (`79efe97 `_) - - -Upgrading to 2.1 from 2.0 -------------------------- -Version 2.1 of the DataStax Python driver for Apache Cassandra -adds support for Cassandra 2.1 and version 3 of the native protocol. - -Cassandra 1.2, 2.0 are not supported: 1.2 only -supports protocol version 1, and 2.0 only supports versions 1 and -2, both of which were removed from the driver. - -Using the v3 Native Protocol -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -By default, the driver will attempt to use highest version of the -native protocol supported by the server. Protocol v3 is the lowest -version supported. - -To use version 3, you must explicitly -set the :attr:`~.Cluster.protocol_version`: - -.. code-block:: python - - from cassandra.cluster import Cluster - - cluster = Cluster(protocol_version=3) - -Note that protocol version 3 is only supported by Cassandra 2.1+. - -Working with User-Defined Types -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Cassandra 2.1 introduced the ability to define new types:: - - USE KEYSPACE mykeyspace; - - CREATE TYPE address (street text, city text, zip int); - -The driver generally expects you to use instances of a specific -class to represent column values of this type. You can let the -driver know what class to use with :meth:`.Cluster.register_user_type`: - -.. code-block:: python - - cluster = Cluster() - - class Address(object): - - def __init__(self, street, city, zipcode): - self.street = street - self.city = text - self.zipcode = zipcode - - cluster.register_user_type('mykeyspace', 'address', Address) - -When inserting data for ``address`` columns, you should pass in -instances of ``Address``. When querying data, ``address`` column -values will be instances of ``Address``. - -If no class is registered for a user-defined type, query results -will use a ``namedtuple`` class and data may only be inserted -though prepared statements. - -See :ref:`udts` for more details. - -Customizing Encoders for Non-prepared Statements -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Starting with version 2.1 of the driver, it is possible to customize -how Python types are converted to CQL literals when working with -non-prepared statements. This is done on a per-:class:`~.Session` -basis through :attr:`.Session.encoder`: - -.. code-block:: python - - cluster = Cluster() - session = cluster.connect() - session.encoder.mapping[tuple] = session.encoder.cql_encode_tuple - -See :ref:`type-conversions` for the table of default CQL literal conversions. - -Using Client-Side Protocol-Level Timestamps -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -With version 3 of the native protocol, timestamps may be supplied by the -client at the protocol level. (Normally, if they are not specified within -the CQL query itself, a timestamp is generated server-side.) - -When :attr:`~.Cluster.protocol_version` is set to 3 or higher, the driver -will automatically use client-side timestamps with microsecond precision -unless :attr:`.Session.use_client_timestamp` is changed to :const:`False`. -If a timestamp is specified within the CQL query, it will override the -timestamp generated by the driver. - -Automatic Query Paging -^^^^^^^^^^^^^^^^^^^^^^ -Version 2 of the native protocol adds support for automatic query -paging, which can make dealing with large result sets much simpler. - -See :ref:`query-paging` for full details. - -Protocol-Level Batch Statements -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -With version 1 of the native protocol, batching of statements required -using a `BATCH cql query `_. -With version 2 of the native protocol, you can now batch statements at -the protocol level. This allows you to use many different prepared -statements within a single batch. - -See :class:`~.query.BatchStatement` for details and usage examples. - -SASL-based Authentication -^^^^^^^^^^^^^^^^^^^^^^^^^ -Also new in version 2 of the native protocol is SASL-based authentication. -See the section on :ref:`security` for details and examples. - -Lightweight Transactions -^^^^^^^^^^^^^^^^^^^^^^^^ -`Lightweight transactions `_ are another new feature. To use lightweight transactions, add ``IF`` clauses -to your CQL queries and set the :attr:`~.Statement.serial_consistency_level` -on your statements. - -Calling Cluster.shutdown() -^^^^^^^^^^^^^^^^^^^^^^^^^^ -In order to fix some issues around garbage collection and unclean interpreter -shutdowns, version 2.0 of the driver requires you to call :meth:`.Cluster.shutdown()` -on your :class:`~.Cluster` objects when you are through with them. -This helps to guarantee a clean shutdown. - -Deprecations -^^^^^^^^^^^^ -The following functions have moved from ``cassandra.decoder`` to ``cassandra.query``. -The original functions have been left in place with a :exc:`DeprecationWarning` for -now: - -* :attr:`cassandra.decoder.tuple_factory` has moved to - :attr:`cassandra.query.tuple_factory` -* :attr:`cassandra.decoder.named_tuple_factory` has moved to - :attr:`cassandra.query.named_tuple_factory` -* :attr:`cassandra.decoder.dict_factory` has moved to - :attr:`cassandra.query.dict_factory` -* :attr:`cassandra.decoder.ordered_dict_factory` has moved to - :attr:`cassandra.query.ordered_dict_factory` - -Dependency Changes -^^^^^^^^^^^^^^^^^^ -The following dependencies have officially been made optional: - -* ``scales`` -* ``blist`` From e02237872ad901fba165385cb0db62e495a4d417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 4 Jul 2025 17:49:41 +0200 Subject: [PATCH 035/298] Remove DSE tests We don't use them. The only thing they do is making __init__.py of integration tests more bloated. --- tests/integration/__init__.py | 265 +--- tests/integration/advanced/__init__.py | 149 -- tests/integration/advanced/graph/__init__.py | 1195 ----------------- .../advanced/graph/fluent/__init__.py | 718 ---------- .../advanced/graph/fluent/test_graph.py | 241 ---- .../fluent/test_graph_explicit_execution.py | 96 -- .../fluent/test_graph_implicit_execution.py | 108 -- .../advanced/graph/fluent/test_search.py | 539 -------- .../integration/advanced/graph/test_graph.py | 270 ---- .../advanced/graph/test_graph_cont_paging.py | 78 -- .../advanced/graph/test_graph_datatype.py | 266 ---- .../advanced/graph/test_graph_query.py | 594 -------- .../integration/advanced/test_adv_metadata.py | 392 ------ tests/integration/advanced/test_auth.py | 532 -------- .../integration/advanced/test_cont_paging.py | 243 ---- .../test_cqlengine_where_operators.py | 110 -- tests/integration/advanced/test_geometry.py | 249 ---- tests/integration/advanced/test_spark.py | 50 - tests/integration/cloud/__init__.py | 113 -- tests/integration/cloud/conftest.py | 9 - tests/integration/cloud/test_cloud.py | 240 ---- tests/integration/cloud/test_cloud_schema.py | 118 -- .../cqlengine/advanced/__init__.py | 14 - .../cqlengine/advanced/test_cont_paging.py | 169 --- .../cqlengine/management/test_management.py | 2 +- .../long/test_loadbalancingpolicies.py | 3 +- tests/integration/long/utils.py | 4 +- tests/integration/simulacron/__init__.py | 23 +- .../simulacron/advanced/__init__.py | 13 - .../simulacron/advanced/test_insights.py | 108 -- tests/integration/simulacron/test_cluster.py | 6 +- tests/integration/simulacron/utils.py | 8 +- tests/integration/standard/test_cluster.py | 10 +- .../standard/test_control_connection.py | 3 +- .../standard/test_custom_protocol_handler.py | 33 +- tests/integration/standard/test_dse.py | 92 -- tests/integration/standard/test_metadata.py | 53 +- .../standard/test_prepared_statements.py | 35 +- tests/integration/standard/test_query.py | 5 +- .../standard/test_single_interface.py | 7 +- tests/integration/standard/test_types.py | 370 +---- tests/unit/advanced/test_auth.py | 42 - 42 files changed, 86 insertions(+), 7489 deletions(-) delete mode 100644 tests/integration/advanced/graph/__init__.py delete mode 100644 tests/integration/advanced/graph/fluent/__init__.py delete mode 100644 tests/integration/advanced/graph/fluent/test_graph.py delete mode 100644 tests/integration/advanced/graph/fluent/test_graph_explicit_execution.py delete mode 100644 tests/integration/advanced/graph/fluent/test_graph_implicit_execution.py delete mode 100644 tests/integration/advanced/graph/fluent/test_search.py delete mode 100644 tests/integration/advanced/graph/test_graph.py delete mode 100644 tests/integration/advanced/graph/test_graph_cont_paging.py delete mode 100644 tests/integration/advanced/graph/test_graph_datatype.py delete mode 100644 tests/integration/advanced/graph/test_graph_query.py delete mode 100644 tests/integration/advanced/test_adv_metadata.py delete mode 100644 tests/integration/advanced/test_auth.py delete mode 100644 tests/integration/advanced/test_cont_paging.py delete mode 100644 tests/integration/advanced/test_cqlengine_where_operators.py delete mode 100644 tests/integration/advanced/test_geometry.py delete mode 100644 tests/integration/advanced/test_spark.py delete mode 100644 tests/integration/cloud/__init__.py delete mode 100644 tests/integration/cloud/conftest.py delete mode 100644 tests/integration/cloud/test_cloud.py delete mode 100644 tests/integration/cloud/test_cloud_schema.py delete mode 100644 tests/integration/cqlengine/advanced/__init__.py delete mode 100644 tests/integration/cqlengine/advanced/test_cont_paging.py delete mode 100644 tests/integration/simulacron/advanced/__init__.py delete mode 100644 tests/integration/simulacron/advanced/test_insights.py delete mode 100644 tests/integration/standard/test_dse.py delete mode 100644 tests/unit/advanced/test_auth.py diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index 75e1df4261..770b000fca 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -44,7 +44,6 @@ try: import ccmlib - from ccmlib.dse_cluster import DseCluster from ccmlib.cluster import Cluster as CCMCluster from ccmlib.scylla_cluster import ScyllaCluster as CCMScyllaCluster from ccmlib.cluster_factory import ClusterFactory as CCMClusterFactory @@ -122,98 +121,32 @@ def cmd_line_args_to_dict(env_var): args[cmd_arg.lstrip('-')] = cmd_arg_value return args - -def _get_cass_version_from_dse(dse_version): - if dse_version.startswith('4.6') or dse_version.startswith('4.5'): - raise Exception("Cassandra Version 2.0 not supported anymore") - elif dse_version.startswith('4.7') or dse_version.startswith('4.8'): - cass_ver = "2.1" - elif dse_version.startswith('5.0'): - cass_ver = "3.0" - elif dse_version.startswith('5.1'): - # TODO: refactor this method to use packaging.Version everywhere - if Version(dse_version) >= Version('5.1.2'): - cass_ver = "3.11" - else: - cass_ver = "3.10" - elif dse_version.startswith('6.0'): - if dse_version == '6.0.0': - cass_ver = '4.0.0.2284' - elif dse_version == '6.0.1': - cass_ver = '4.0.0.2349' - else: - cass_ver = '4.0.0.' + ''.join(dse_version.split('.')) - elif Version(dse_version) >= Version('6.7'): - if dse_version == '6.7.0': - cass_ver = "4.0.0.67" - else: - cass_ver = '4.0.0.' + ''.join(dse_version.split('.')) - elif dse_version.startswith('6.8'): - if dse_version == '6.8.0': - cass_ver = "4.0.0.68" - else: - cass_ver = '4.0.0.' + ''.join(dse_version.split('.')) - else: - log.error("Unknown dse version found {0}, defaulting to 2.1".format(dse_version)) - cass_ver = "2.1" - return Version(cass_ver) - - -def _get_dse_version_from_cass(cass_version): - if cass_version.startswith('2.1'): - dse_ver = "4.8.15" - elif cass_version.startswith('3.0'): - dse_ver = "5.0.12" - elif cass_version.startswith('3.10') or cass_version.startswith('3.11'): - dse_ver = "5.1.7" - elif cass_version.startswith('4.0'): - dse_ver = "6.0" - else: - log.error("Unknown cassandra version found {0}, defaulting to 2.1".format(cass_version)) - dse_ver = "2.1" - return dse_ver - USE_CASS_EXTERNAL = bool(os.getenv('USE_CASS_EXTERNAL', False)) KEEP_TEST_CLUSTER = bool(os.getenv('KEEP_TEST_CLUSTER', False)) SIMULACRON_JAR = os.getenv('SIMULACRON_JAR', None) -CLOUD_PROXY_PATH = os.getenv('CLOUD_PROXY_PATH', None) -# Supported Clusters: Cassandra, DDAC, DSE, Scylla -DSE_VERSION = None +# Supported Clusters: Cassandra, Scylla SCYLLA_VERSION = os.getenv('SCYLLA_VERSION', None) -if os.getenv('DSE_VERSION', None): # we are testing against DSE - DSE_VERSION = Version(os.getenv('DSE_VERSION', None)) - DSE_CRED = os.getenv('DSE_CREDS', None) - CASSANDRA_VERSION = _get_cass_version_from_dse(DSE_VERSION.base_version) - CCM_VERSION = DSE_VERSION.base_version -else: # we are testing against Cassandra,DDAC or Scylla - if SCYLLA_VERSION: - cv_string = SCYLLA_VERSION - mcv_string = os.getenv('MAPPED_SCYLLA_VERSION', '3.11.4') # Assume that scylla matches cassandra `3.11.4` behavior - else: - cv_string = os.getenv('CASSANDRA_VERSION', None) - mcv_string = os.getenv('MAPPED_CASSANDRA_VERSION', None) - try: - cassandra_version = Version(cv_string) # env var is set to test-dse for DDAC - except: - # fallback to MAPPED_CASSANDRA_VERSION - cassandra_version = Version(mcv_string) +if SCYLLA_VERSION: + cv_string = SCYLLA_VERSION + mcv_string = os.getenv('MAPPED_SCYLLA_VERSION', '3.11.4') # Assume that scylla matches cassandra `3.11.4` behavior +else: + cv_string = os.getenv('CASSANDRA_VERSION', None) + mcv_string = os.getenv('MAPPED_CASSANDRA_VERSION', None) +try: + cassandra_version = Version(cv_string) # env var is set to test-dse for DDAC +except: + # fallback to MAPPED_CASSANDRA_VERSION + cassandra_version = Version(mcv_string) - CASSANDRA_VERSION = Version(mcv_string) if mcv_string else cassandra_version - CCM_VERSION = mcv_string if mcv_string else cv_string +CASSANDRA_VERSION = Version(mcv_string) if mcv_string else cassandra_version +CCM_VERSION = mcv_string if mcv_string else cv_string CASSANDRA_IP = os.getenv('CLUSTER_IP', '127.0.0.1') CASSANDRA_DIR = os.getenv('CASSANDRA_DIR', None) CCM_KWARGS = {} -if DSE_VERSION: - log.info('Using DSE version: %s', DSE_VERSION) - if not CASSANDRA_DIR: - CCM_KWARGS['version'] = DSE_VERSION - if DSE_CRED: - log.info("Using DSE credentials file located at {0}".format(DSE_CRED)) - CCM_KWARGS['dse_credentials_file'] = DSE_CRED -elif CASSANDRA_DIR: +if CASSANDRA_DIR: log.info("Using Cassandra dir: %s", CASSANDRA_DIR) CCM_KWARGS['install_dir'] = CASSANDRA_DIR elif os.getenv('SCYLLA_VERSION'): @@ -228,15 +161,9 @@ def _get_dse_version_from_cass(cass_version): def get_default_protocol(): if CASSANDRA_VERSION >= Version('4.0-a'): - if DSE_VERSION: - return ProtocolVersion.DSE_V2 - else: - return ProtocolVersion.V5 + return ProtocolVersion.V5 if CASSANDRA_VERSION >= Version('3.10'): - if DSE_VERSION: - return ProtocolVersion.DSE_V1 - else: - return 4 + return 4 if CASSANDRA_VERSION >= Version('2.2'): return 4 elif CASSANDRA_VERSION >= Version('2.1'): @@ -262,23 +189,14 @@ def get_supported_protocol_versions(): 2.2 -> 4, 3 3.X -> 4, 3 3.10(C*) -> 5(beta),4,3 - 3.10(DSE) -> DSE_V1,4,3 4.0(C*) -> 6(beta),5,4,3 - 4.0(DSE) -> DSE_v2, DSE_V1,4,3 ` """ if CASSANDRA_VERSION >= Version('4.0-beta5'): - if not DSE_VERSION: - return (3, 4, 5, 6) + return (3, 4, 5, 6) if CASSANDRA_VERSION >= Version('4.0-a'): - if DSE_VERSION: - return (3, 4, ProtocolVersion.DSE_V1, ProtocolVersion.DSE_V2) - else: - return (3, 4, 5) + return (3, 4, 5) elif CASSANDRA_VERSION >= Version('3.10'): - if DSE_VERSION: - return (3, 4, ProtocolVersion.DSE_V1) - else: - return (3, 4) + return (3, 4) elif CASSANDRA_VERSION >= Version('3.0'): return (3, 4) elif CASSANDRA_VERSION >= Version('2.2'): @@ -311,15 +229,9 @@ def get_unsupported_upper_protocol(): return 5 if CASSANDRA_VERSION >= Version('4.0-a'): - if DSE_VERSION: - return None - else: - return ProtocolVersion.DSE_V1 + return ProtocolVersion.DSE_V1 if CASSANDRA_VERSION >= Version('3.10'): - if DSE_VERSION: - return ProtocolVersion.DSE_V2 - else: - return 5 + return 5 if CASSANDRA_VERSION >= Version('2.2'): return 5 elif CASSANDRA_VERSION >= Version('2.1'): @@ -364,14 +276,6 @@ def _id_and_mark(f): lessthancass40 = unittest.skipUnless(CASSANDRA_VERSION < Version('4.0'), 'Cassandra version less than 4.0 required') lessthancass30 = unittest.skipUnless(CASSANDRA_VERSION < Version('3.0'), 'Cassandra version less then 3.0 required') -greaterthanorequaldse68 = unittest.skipUnless(DSE_VERSION and DSE_VERSION >= Version('6.8'), "DSE 6.8 or greater required for this test") -greaterthanorequaldse67 = unittest.skipUnless(DSE_VERSION and DSE_VERSION >= Version('6.7'), "DSE 6.7 or greater required for this test") -greaterthanorequaldse60 = unittest.skipUnless(DSE_VERSION and DSE_VERSION >= Version('6.0'), "DSE 6.0 or greater required for this test") -greaterthanorequaldse51 = unittest.skipUnless(DSE_VERSION and DSE_VERSION >= Version('5.1'), "DSE 5.1 or greater required for this test") -greaterthanorequaldse50 = unittest.skipUnless(DSE_VERSION and DSE_VERSION >= Version('5.0'), "DSE 5.0 or greater required for this test") -lessthandse51 = unittest.skipUnless(DSE_VERSION and DSE_VERSION < Version('5.1'), "DSE version less than 5.1 required") -lessthandse60 = unittest.skipUnless(DSE_VERSION and DSE_VERSION < Version('6.0'), "DSE version less than 6.0 required") - # pytest.mark.xfail instead of unittest.expectedFailure because # 1. unittest doesn't skip setUpClass when used on class and we need it sometimes # 2. unittest doesn't have conditional xfail, and I prefer to use pytest than custom decorator @@ -393,10 +297,6 @@ def _id_and_mark(f): requiresmallclockgranularity = unittest.skipIf("Windows" in platform.system() or "asyncore" in EVENT_LOOP_MANAGER, "This test is not suitible for environments with large clock granularity") requiressimulacron = unittest.skipIf(SIMULACRON_JAR is None or CASSANDRA_VERSION < Version("2.1"), "Simulacron jar hasn't been specified or C* version is 2.0") -requirecassandra = unittest.skipIf(DSE_VERSION, "Cassandra required") -notdse = unittest.skipIf(DSE_VERSION, "DSE not supported") -requiredse = unittest.skipUnless(DSE_VERSION, "DSE required") -requirescloudproxy = unittest.skipIf(CLOUD_PROXY_PATH is None, "Cloud Proxy path hasn't been specified") libevtest = unittest.skipUnless(EVENT_LOOP_MANAGER=="libev", "Test timing designed for libev loop") @@ -508,15 +408,11 @@ def use_cluster(cluster_name, nodes, ipformat=None, start=True, workloads=None, configuration_options = configuration_options or {} dse_options = dse_options or {} workloads = workloads or [] - dse_cluster = True if DSE_VERSION else False - if ccm_options is None and DSE_VERSION: - ccm_options = {"version": CCM_VERSION} - elif ccm_options is None: + if ccm_options is None: ccm_options = CCM_KWARGS.copy() cassandra_version = ccm_options.get('version', CCM_VERSION) - dse_version = ccm_options.get('version', DSE_VERSION) global CCM_CLUSTER if USE_CASS_EXTERNAL: @@ -562,88 +458,41 @@ def use_cluster(cluster_name, nodes, ipformat=None, start=True, workloads=None, if os.path.exists(cluster_path): shutil.rmtree(cluster_path) - if dse_cluster: - CCM_CLUSTER = DseCluster(path, cluster_name, **ccm_options) + if SCYLLA_VERSION: + # `experimental: True` enable all experimental features. + # CDC is causing an issue (can't start cluster with multiple seeds) + # Selecting only features we need for tests, i.e. anything but CDC. + CCM_CLUSTER = CCMScyllaCluster(path, cluster_name, **ccm_options) + CCM_CLUSTER.set_configuration_options({'experimental_features': ['lwt', 'udf'], 'start_native_transport': True}) + + CCM_CLUSTER.set_configuration_options({'skip_wait_for_gossip_to_settle': 0}) + # Permit IS NOT NULL restriction on non-primary key columns of a materialized view + # This allows `test_metadata_with_quoted_identifiers` to run + CCM_CLUSTER.set_configuration_options({'strict_is_not_null_in_views': False}) + else: + ccm_cluster_clz = CCMCluster if Version(cassandra_version) < Version( + '4.1') else Cassandra41CCMCluster + CCM_CLUSTER = ccm_cluster_clz(path, cluster_name, **ccm_options) CCM_CLUSTER.set_configuration_options({'start_native_transport': True}) - CCM_CLUSTER.set_configuration_options({'batch_size_warn_threshold_in_kb': 5}) - if Version(dse_version) >= Version('5.0'): - CCM_CLUSTER.set_configuration_options({'enable_user_defined_functions': True}) - CCM_CLUSTER.set_configuration_options({'enable_scripted_user_defined_functions': True}) - if Version(dse_version) >= Version('5.1'): - # For Inet4Address - CCM_CLUSTER.set_dse_configuration_options({ - 'graph': { - 'gremlin_server': { - 'scriptEngines': { - 'gremlin-groovy': { - 'config': { - 'sandbox_rules': { - 'whitelist_packages': ['java.net'] - } - } - } - } - } - } - }) - if 'spark' in workloads: - if Version(dse_version) >= Version('6.8'): - config_options = { - "resource_manager_options": { - "worker_options": { - "cores_total": 0.1, - "memory_total": "64M" - } - } - } + if Version(cassandra_version) >= Version('2.2'): + CCM_CLUSTER.set_configuration_options({'enable_user_defined_functions': True}) + if Version(cassandra_version) >= Version('3.0'): + # The config.yml option below is deprecated in C* 4.0 per CASSANDRA-17280 + if Version(cassandra_version) < Version('4.0'): + CCM_CLUSTER.set_configuration_options({'enable_scripted_user_defined_functions': True}) else: - config_options = {"initial_spark_worker_resources": 0.1} - - if Version(dse_version) >= Version('6.7'): - log.debug("Disabling AlwaysON SQL for a DSE 6.7 Cluster") - config_options['alwayson_sql_options'] = {'enabled': False} - CCM_CLUSTER.set_dse_configuration_options(config_options) - common.switch_cluster(path, cluster_name) - CCM_CLUSTER.set_configuration_options(configuration_options) - CCM_CLUSTER.populate(nodes, ipformat=ipformat) - - CCM_CLUSTER.set_dse_configuration_options(dse_options) - else: - if SCYLLA_VERSION: - # `experimental: True` enable all experimental features. - # CDC is causing an issue (can't start cluster with multiple seeds) - # Selecting only features we need for tests, i.e. anything but CDC. - CCM_CLUSTER = CCMScyllaCluster(path, cluster_name, **ccm_options) - CCM_CLUSTER.set_configuration_options({'experimental_features': ['lwt', 'udf'], 'start_native_transport': True}) - - CCM_CLUSTER.set_configuration_options({'skip_wait_for_gossip_to_settle': 0}) - # Permit IS NOT NULL restriction on non-primary key columns of a materialized view - # This allows `test_metadata_with_quoted_identifiers` to run - CCM_CLUSTER.set_configuration_options({'strict_is_not_null_in_views': False}) - else: - ccm_cluster_clz = CCMCluster if Version(cassandra_version) < Version( - '4.1') else Cassandra41CCMCluster - CCM_CLUSTER = ccm_cluster_clz(path, cluster_name, **ccm_options) - CCM_CLUSTER.set_configuration_options({'start_native_transport': True}) - if Version(cassandra_version) >= Version('2.2'): - CCM_CLUSTER.set_configuration_options({'enable_user_defined_functions': True}) - if Version(cassandra_version) >= Version('3.0'): - # The config.yml option below is deprecated in C* 4.0 per CASSANDRA-17280 - if Version(cassandra_version) < Version('4.0'): - CCM_CLUSTER.set_configuration_options({'enable_scripted_user_defined_functions': True}) - else: - # Cassandra version >= 4.0 - CCM_CLUSTER.set_configuration_options({ - 'enable_materialized_views': True, - 'enable_sasi_indexes': True, - 'enable_transient_replication': True, - }) - - common.switch_cluster(path, cluster_name) - CCM_CLUSTER.set_configuration_options(configuration_options) - # Since scylla CCM doesn't yet support this options, we skip it - # , use_single_interface=use_single_interface) - CCM_CLUSTER.populate(nodes, ipformat=ipformat) + # Cassandra version >= 4.0 + CCM_CLUSTER.set_configuration_options({ + 'enable_materialized_views': True, + 'enable_sasi_indexes': True, + 'enable_transient_replication': True, + }) + + common.switch_cluster(path, cluster_name) + CCM_CLUSTER.set_configuration_options(configuration_options) + # Since scylla CCM doesn't yet support this options, we skip it + # , use_single_interface=use_single_interface) + CCM_CLUSTER.populate(nodes, ipformat=ipformat) try: jvm_args = [] @@ -1145,4 +994,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) \ No newline at end of file + super(Cassandra41CCMCluster, self).set_configuration_options(values=new_values, *args, **kwargs) diff --git a/tests/integration/advanced/__init__.py b/tests/integration/advanced/__init__.py index dffaccd190..2c9ca172f8 100644 --- a/tests/integration/advanced/__init__.py +++ b/tests/integration/advanced/__init__.py @@ -11,152 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -import unittest - -from urllib.request import build_opener, Request, HTTPHandler -import re -import os -import time -from os.path import expanduser - -from ccmlib import common - -from tests.integration import get_server_versions, BasicKeyspaceUnitTestCase, \ - drop_keyspace_shutdown_cluster, get_node, USE_CASS_EXTERNAL, TestCluster -from tests.integration import use_singledc, use_single_node, wait_for_node_socket, CASSANDRA_IP - -home = expanduser('~') - -# Home directory of the Embedded Apache Directory Server to use -ADS_HOME = os.getenv('ADS_HOME', home) - - -def find_spark_master(session): - - # Iterate over the nodes the one with port 7080 open is the spark master - for host in session.hosts: - ip = host.address - port = 7077 - spark_master = (ip, port) - if common.check_socket_listening(spark_master, timeout=3): - return spark_master[0] - return None - - -def wait_for_spark_workers(num_of_expected_workers, timeout): - """ - This queries the spark master and checks for the expected number of workers - """ - start_time = time.time() - while True: - opener = build_opener(HTTPHandler) - request = Request("http://{0}:7080".format(CASSANDRA_IP)) - request.get_method = lambda: 'GET' - connection = opener.open(request) - match = re.search('Alive Workers:.*(\d+)', connection.read().decode('utf-8')) - num_workers = int(match.group(1)) - if num_workers == num_of_expected_workers: - match = True - break - elif time.time() - start_time > timeout: - match = True - break - time.sleep(1) - return match - - -def use_single_node_with_graph(start=True, options={}, dse_options={}): - use_single_node(start=start, workloads=['graph'], configuration_options=options, dse_options=dse_options) - - -def use_single_node_with_graph_and_spark(start=True, options={}): - use_single_node(start=start, workloads=['graph', 'spark'], configuration_options=options) - - -def use_single_node_with_graph_and_solr(start=True, options={}): - use_single_node(start=start, workloads=['graph', 'solr'], configuration_options=options) - - -def use_singledc_wth_graph(start=True): - use_singledc(start=start, workloads=['graph']) - - -def use_singledc_wth_graph_and_spark(start=True): - use_cluster_with_graph(3) - - -def use_cluster_with_graph(num_nodes): - """ - This is a work around to account for the fact that spark nodes will conflict over master assignment - when started all at once. - """ - if USE_CASS_EXTERNAL: - return - - # Create the cluster but don't start it. - use_singledc(start=False, workloads=['graph', 'spark']) - # Start first node. - get_node(1).start(wait_for_binary_proto=True) - # Wait binary protocol port to open - wait_for_node_socket(get_node(1), 120) - # Wait for spark master to start up - spark_master_http = ("localhost", 7080) - common.check_socket_listening(spark_master_http, timeout=60) - tmp_cluster = TestCluster() - - # Start up remaining nodes. - try: - session = tmp_cluster.connect() - statement = "ALTER KEYSPACE dse_leases WITH REPLICATION = {'class': 'NetworkTopologyStrategy', 'dc1': '%d'}" % (num_nodes) - session.execute(statement) - finally: - tmp_cluster.shutdown() - - for i in range(1, num_nodes+1): - if i is not 1: - node = get_node(i) - node.start(wait_for_binary_proto=True) - wait_for_node_socket(node, 120) - - # Wait for workers to show up as Alive on master - wait_for_spark_workers(3, 120) - - -class BasicGeometricUnitTestCase(BasicKeyspaceUnitTestCase): - """ - This base test class is used by all the geomteric tests. It contains class level teardown and setup - methods. It also contains the test fixtures used by those tests - """ - - @classmethod - def common_dse_setup(cls, rf, keyspace_creation=True): - cls.cluster = TestCluster() - cls.session = cls.cluster.connect() - cls.ks_name = cls.__name__.lower() - if keyspace_creation: - cls.create_keyspace(rf) - cls.cass_version, cls.cql_version = get_server_versions() - cls.session.set_keyspace(cls.ks_name) - - @classmethod - def setUpClass(cls): - cls.common_dse_setup(1) - cls.initalizeTables() - - @classmethod - def tearDownClass(cls): - drop_keyspace_shutdown_cluster(cls.ks_name, cls.session, cls.cluster) - - @classmethod - def initalizeTables(cls): - udt_type = "CREATE TYPE udt1 (g {0})".format(cls.cql_type_name) - large_table = "CREATE TABLE tbl (k uuid PRIMARY KEY, g {0}, l list<{0}>, s set<{0}>, m0 map<{0},int>, m1 map, t tuple<{0},{0},{0}>, u frozen)".format( - cls.cql_type_name) - simple_table = "CREATE TABLE tblpk (k {0} primary key, v int)".format(cls.cql_type_name) - cluster_table = "CREATE TABLE tblclustering (k0 int, k1 {0}, v int, primary key (k0, k1))".format( - cls.cql_type_name) - cls.session.execute(udt_type) - cls.session.execute(large_table) - cls.session.execute(simple_table) - cls.session.execute(cluster_table) diff --git a/tests/integration/advanced/graph/__init__.py b/tests/integration/advanced/graph/__init__.py deleted file mode 100644 index cc40c6906a..0000000000 --- a/tests/integration/advanced/graph/__init__.py +++ /dev/null @@ -1,1195 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -import logging -import inspect -from packaging.version import Version -import ipaddress -from uuid import UUID -from decimal import Decimal -import datetime - -from cassandra.util import Point, LineString, Polygon, Duration - -from cassandra.cluster import EXEC_PROFILE_GRAPH_DEFAULT, EXEC_PROFILE_GRAPH_ANALYTICS_DEFAULT -from cassandra.cluster import GraphAnalyticsExecutionProfile, GraphExecutionProfile, EXEC_PROFILE_GRAPH_SYSTEM_DEFAULT, \ - default_lbp_factory -from cassandra.policies import DSELoadBalancingPolicy - -from cassandra.graph import GraphSON1Deserializer -from cassandra.graph.graphson import InetTypeIO, GraphSON2Deserializer, GraphSON3Deserializer -from cassandra.graph import Edge, Vertex, Path -from cassandra.graph.query import GraphOptions, GraphProtocol, graph_graphson2_row_factory, \ - graph_graphson3_row_factory - -from tests.integration import DSE_VERSION -from tests.integration.advanced import * - - -def setup_module(): - if DSE_VERSION: - dse_options = {'graph': {'realtime_evaluation_timeout_in_seconds': 60}} - use_single_node_with_graph(dse_options=dse_options) - - -log = logging.getLogger(__name__) - -MAX_LONG = 9223372036854775807 -MIN_LONG = -9223372036854775808 -ZERO_LONG = 0 - -MAKE_STRICT = "schema.config().option('graph.schema_mode').set('production')" -MAKE_NON_STRICT = "schema.config().option('graph.schema_mode').set('development')" -ALLOW_SCANS = "schema.config().option('graph.allow_scan').set('true')" - -deserializer_plus_to_ipaddressv4 = lambda x: ipaddress.IPv4Address(GraphSON1Deserializer.deserialize_inet(x)) -deserializer_plus_to_ipaddressv6 = lambda x: ipaddress.IPv6Address(GraphSON1Deserializer.deserialize_inet(x)) - - -def generic_ip_deserializer(string_ip_address): - if ":" in string_ip_address: - return deserializer_plus_to_ipaddressv6(string_ip_address) - return deserializer_plus_to_ipaddressv4(string_ip_address) - - -class GenericIpAddressIO(InetTypeIO): - @classmethod - def deserialize(cls, value, reader=None): - return generic_ip_deserializer(value) - -GraphSON2Deserializer._deserializers[GenericIpAddressIO.graphson_type] = GenericIpAddressIO -GraphSON3Deserializer._deserializers[GenericIpAddressIO.graphson_type] = GenericIpAddressIO - -if DSE_VERSION: - if DSE_VERSION >= Version('6.8.0'): - CREATE_CLASSIC_GRAPH = "system.graph(name).engine(Classic).create()" - else: - CREATE_CLASSIC_GRAPH = "system.graph(name).create()" - - -def reset_graph(session, graph_name): - ks = list(session.execute( - "SELECT * FROM system_schema.keyspaces WHERE keyspace_name = '{}';".format(graph_name))) - if ks: - try: - session.execute_graph('system.graph(name).drop()', {'name': graph_name}, - execution_profile=EXEC_PROFILE_GRAPH_SYSTEM_DEFAULT) - except: - pass - - session.execute_graph(CREATE_CLASSIC_GRAPH, {'name': graph_name}, - execution_profile=EXEC_PROFILE_GRAPH_SYSTEM_DEFAULT) - wait_for_graph_inserted(session, graph_name) - - -def wait_for_graph_inserted(session, graph_name): - count = 0 - exists = session.execute_graph('system.graph(name).exists()', {'name': graph_name}, - execution_profile=EXEC_PROFILE_GRAPH_SYSTEM_DEFAULT)[0].value - while not exists and count < 50: - time.sleep(1) - exists = session.execute_graph('system.graph(name).exists()', {'name': graph_name}, - execution_profile=EXEC_PROFILE_GRAPH_SYSTEM_DEFAULT)[0].value - return exists - - -class BasicGraphUnitTestCase(BasicKeyspaceUnitTestCase): - """ - This is basic graph unit test case that provides various utility methods that can be leveraged for testcase setup and tear - down - """ - - @property - def graph_name(self): - return self._testMethodName.lower() - - def session_setup(self): - lbp = DSELoadBalancingPolicy(default_lbp_factory()) - - ep_graphson2 = GraphExecutionProfile( - request_timeout=60, - load_balancing_policy=lbp, - graph_options=GraphOptions( - graph_name=self.graph_name, - graph_protocol=GraphProtocol.GRAPHSON_2_0 - ), - row_factory=graph_graphson2_row_factory) - - ep_graphson3 = GraphExecutionProfile( - request_timeout=60, - load_balancing_policy=lbp, - graph_options=GraphOptions( - graph_name=self.graph_name, - graph_protocol=GraphProtocol.GRAPHSON_3_0 - ), - row_factory=graph_graphson3_row_factory) - - ep_graphson1 = GraphExecutionProfile( - request_timeout=60, - load_balancing_policy=lbp, - graph_options=GraphOptions( - graph_name=self.graph_name - ) - ) - - ep_analytics = GraphAnalyticsExecutionProfile( - request_timeout=60, - load_balancing_policy=lbp, - graph_options=GraphOptions( - graph_source=b'a', - graph_language=b'gremlin-groovy', - graph_name=self.graph_name - ) - ) - - self.cluster = TestCluster(execution_profiles={ - EXEC_PROFILE_GRAPH_DEFAULT: ep_graphson1, - EXEC_PROFILE_GRAPH_ANALYTICS_DEFAULT: ep_analytics, - "graphson1": ep_graphson1, - "graphson2": ep_graphson2, - "graphson3": ep_graphson3 - }) - - self.session = self.cluster.connect() - self.ks_name = self._testMethodName.lower() - self.cass_version, self.cql_version = get_server_versions() - - def setUp(self): - self.session_setup() - self.reset_graph() - self.clear_schema() - # enable dev and scan modes - self.session.execute_graph(MAKE_NON_STRICT) - self.session.execute_graph(ALLOW_SCANS) - - def tearDown(self): - self.cluster.shutdown() - - def clear_schema(self): - self.session.execute_graph(""" - schema.clear(); - """) - - def reset_graph(self): - reset_graph(self.session, self.graph_name) - - def wait_for_graph_inserted(self): - wait_for_graph_inserted(self.session, self.graph_name) - - def _execute(self, query, graphson, params=None, execution_profile_options=None, **kwargs): - queries = query if isinstance(query, list) else [query] - ep = self.get_execution_profile(graphson) - if execution_profile_options: - ep = self.session.execution_profile_clone_update(ep, **execution_profile_options) - - results = [] - for query in queries: - log.debug(query) - rf = self.session.execute_graph_async(query, parameters=params, execution_profile=ep, **kwargs) - results.append(rf.result()) - self.assertEqual(rf.message.custom_payload['graph-results'], graphson) - - return results[0] if len(results) == 1 else results - - def get_execution_profile(self, graphson, traversal=False): - ep = 'graphson1' - if graphson == GraphProtocol.GRAPHSON_2_0: - ep = 'graphson2' - elif graphson == GraphProtocol.GRAPHSON_3_0: - ep = 'graphson3' - - return ep if traversal is False else 'traversal_' + ep - - def resultset_to_list(self, rs): - results_list = [] - for result in rs: - try: - results_list.append(result.value) - except: - results_list.append(result) - - return results_list - - -class GraphUnitTestCase(BasicKeyspaceUnitTestCase): - - @property - def graph_name(self): - return self._testMethodName.lower() - - def session_setup(self): - lbp = DSELoadBalancingPolicy(default_lbp_factory()) - - ep_graphson2 = GraphExecutionProfile( - request_timeout=60, - load_balancing_policy=lbp, - graph_options=GraphOptions( - graph_name=self.graph_name, - graph_protocol=GraphProtocol.GRAPHSON_2_0 - ), - row_factory=graph_graphson2_row_factory) - - ep_graphson3 = GraphExecutionProfile( - request_timeout=60, - load_balancing_policy=lbp, - graph_options=GraphOptions( - graph_name=self.graph_name, - graph_protocol=GraphProtocol.GRAPHSON_3_0 - ), - row_factory=graph_graphson3_row_factory) - - ep_graphson1 = GraphExecutionProfile( - request_timeout=60, - load_balancing_policy=lbp, - graph_options=GraphOptions( - graph_name=self.graph_name, - graph_language='gremlin-groovy' - ) - ) - - ep_analytics = GraphAnalyticsExecutionProfile( - request_timeout=60, - load_balancing_policy=lbp, - graph_options=GraphOptions( - graph_source=b'a', - graph_language=b'gremlin-groovy', - graph_name=self.graph_name - ) - ) - - self.cluster = TestCluster(execution_profiles={ - EXEC_PROFILE_GRAPH_DEFAULT: ep_graphson1, - EXEC_PROFILE_GRAPH_ANALYTICS_DEFAULT: ep_analytics, - "graphson1": ep_graphson1, - "graphson2": ep_graphson2, - "graphson3": ep_graphson3 - }) - - self.session = self.cluster.connect() - self.ks_name = self._testMethodName.lower() - self.cass_version, self.cql_version = get_server_versions() - - def setUp(self): - """basic setup only""" - self.session_setup() - - def setup_graph(self, schema): - """Config dependant setup""" - schema.drop_graph(self.session, self.graph_name) - schema.create_graph(self.session, self.graph_name) - schema.clear(self.session) - if schema is ClassicGraphSchema: - # enable dev and scan modes - self.session.execute_graph(MAKE_NON_STRICT) - self.session.execute_graph(ALLOW_SCANS) - - def teardown_graph(self, schema): - schema.drop_graph(self.session, self.graph_name) - - def tearDown(self): - self.cluster.shutdown() - - def execute_graph_queries(self, queries, params=None, execution_profile=EXEC_PROFILE_GRAPH_DEFAULT, - verify_graphson=False, **kwargs): - results = [] - for query in queries: - log.debug(query) - rf = self.session.execute_graph_async(query, parameters=params, - execution_profile=execution_profile, **kwargs) - if verify_graphson: - self.assertEqual(rf.message.custom_payload['graph-results'], verify_graphson) - results.append(rf.result()) - - return results - - def execute_graph(self, query, graphson, params=None, execution_profile_options=None, traversal=False, **kwargs): - queries = query if isinstance(query, list) else [query] - ep = self.get_execution_profile(graphson) - if traversal: - ep = 'traversal_' + ep - if execution_profile_options: - ep = self.session.execution_profile_clone_update(ep, **execution_profile_options) - - results = self.execute_graph_queries(queries, params, ep, verify_graphson=graphson, **kwargs) - - return results[0] if len(results) == 1 else results - - def get_execution_profile(self, graphson, traversal=False): - ep = 'graphson1' - if graphson == GraphProtocol.GRAPHSON_2_0: - ep = 'graphson2' - elif graphson == GraphProtocol.GRAPHSON_3_0: - ep = 'graphson3' - - return ep if traversal is False else 'traversal_' + ep - - def resultset_to_list(self, rs): - results_list = [] - for result in rs: - try: - results_list.append(result.value) - except: - results_list.append(result) - - return results_list - - -class BasicSharedGraphUnitTestCase(BasicKeyspaceUnitTestCase): - """ - This is basic graph unit test case that provides various utility methods that can be leveraged for testcase setup and tear - down - """ - - @classmethod - def session_setup(cls): - cls.cluster = TestCluster() - cls.session = cls.cluster.connect() - cls.ks_name = cls.__name__.lower() - cls.cass_version, cls.cql_version = get_server_versions() - cls.graph_name = cls.__name__.lower() - - @classmethod - def setUpClass(cls): - if DSE_VERSION: - cls.session_setup() - cls.reset_graph() - profiles = cls.cluster.profile_manager.profiles - profiles[EXEC_PROFILE_GRAPH_DEFAULT].request_timeout = 60 - profiles[EXEC_PROFILE_GRAPH_DEFAULT].graph_options.graph_name = cls.graph_name - profiles[EXEC_PROFILE_GRAPH_ANALYTICS_DEFAULT].request_timeout = 60 - profiles[EXEC_PROFILE_GRAPH_ANALYTICS_DEFAULT].graph_options.graph_name = cls.graph_name - - @classmethod - def tearDownClass(cls): - if DSE_VERSION: - cls.cluster.shutdown() - - @classmethod - def clear_schema(self): - self.session.execute_graph('schema.clear()') - - @classmethod - def reset_graph(self): - reset_graph(self.session, self.graph_name) - - def wait_for_graph_inserted(self): - wait_for_graph_inserted(self.session, self.graph_name) - - -class GraphFixtures(object): - - @staticmethod - def line(length, single_script=True): - raise NotImplementedError() - - @staticmethod - def classic(): - raise NotImplementedError() - - @staticmethod - def multiple_fields(): - raise NotImplementedError() - - @staticmethod - def large(): - raise NotImplementedError() - - -class ClassicGraphFixtures(GraphFixtures): - - @staticmethod - def datatypes(): - data = { - "boolean1": ["Boolean()", True, None], - "boolean2": ["Boolean()", False, None], - "point1": ["Point()", Point(.5, .13), GraphSON1Deserializer.deserialize_point], - "point2": ["Point()", Point(-5, .0), GraphSON1Deserializer.deserialize_point], - - "linestring1": ["Linestring()", LineString(((1.0, 2.0), (3.0, 4.0), (-89.0, 90.0))), - GraphSON1Deserializer.deserialize_linestring], - "polygon1": ["Polygon()", Polygon([(10.0, 10.0), (80.0, 10.0), (80., 88.0), (10., 89.0), (10., 10.0)], - [[(20., 20.0), (20., 30.0), (30., 30.0), (30., 20.0), (20., 20.0)], - [(40., 20.0), (40., 30.0), (50., 30.0), (50., 20.0), (40., 20.0)]]), - GraphSON1Deserializer.deserialize_polygon], - "int1": ["Int()", 2, GraphSON1Deserializer.deserialize_int], - "smallint1": ["Smallint()", 1, GraphSON1Deserializer.deserialize_smallint], - "bigint1": ["Bigint()", MAX_LONG, GraphSON1Deserializer.deserialize_bigint], - "bigint2": ["Bigint()", MIN_LONG, GraphSON1Deserializer.deserialize_bigint], - "bigint3": ["Bigint()", ZERO_LONG, GraphSON1Deserializer.deserialize_bigint], - "varint1": ["Varint()", 2147483647, GraphSON1Deserializer.deserialize_varint], - "int1": ["Int()", 100, GraphSON1Deserializer.deserialize_int], - "float1": ["Float()", 0.3415681, GraphSON1Deserializer.deserialize_float], - "double1": ["Double()", 0.34156811237335205, GraphSON1Deserializer.deserialize_double], - "uuid1": ["Uuid()", UUID('12345678123456781234567812345678'), GraphSON1Deserializer.deserialize_uuid], - "decimal1": ["Decimal()", Decimal(10), GraphSON1Deserializer.deserialize_decimal], - "blob1": ["Blob()", bytearray(b"Hello World"), GraphSON1Deserializer.deserialize_blob], - - "timestamp1": ["Timestamp()", datetime.datetime.utcnow().replace(microsecond=0), - GraphSON1Deserializer.deserialize_timestamp], - "timestamp2": ["Timestamp()", datetime.datetime.max.replace(microsecond=0), - GraphSON1Deserializer.deserialize_timestamp], - # These are valid values but are pending for DSP-14093 to be fixed - #"timestamp3": ["Timestamp()", datetime.datetime(159, 1, 1, 23, 59, 59), - # GraphSON1TypeDeserializer.deserialize_timestamp], - #"timestamp4": ["Timestamp()", datetime.datetime.min, - # GraphSON1TypeDeserializer.deserialize_timestamp], - "inet1": ["Inet()", ipaddress.IPv4Address(u"127.0.0.1"), deserializer_plus_to_ipaddressv4], - "inet2": ["Inet()", ipaddress.IPv6Address(u"2001:db8:85a3:8d3:1319:8a2e:370:7348"), - deserializer_plus_to_ipaddressv6], - "duration1": ["Duration()", datetime.timedelta(1, 16, 0), - GraphSON1Deserializer.deserialize_duration], - "duration2": ["Duration()", datetime.timedelta(days=1, seconds=16, milliseconds=15), - GraphSON1Deserializer.deserialize_duration], - "blob3": ["Blob()", bytes(b"Hello World Again"), GraphSON1Deserializer.deserialize_blob], - "blob4": ["Blob()", memoryview(b"And Again Hello World"), GraphSON1Deserializer.deserialize_blob] - } - - if DSE_VERSION >= Version("5.1"): - data["time1"] = ["Time()", datetime.time(12, 6, 12, 444), GraphSON1Deserializer.deserialize_time] - data["time2"] = ["Time()", datetime.time(12, 6, 12), GraphSON1Deserializer.deserialize_time] - data["time3"] = ["Time()", datetime.time(12, 6), GraphSON1Deserializer.deserialize_time] - data["time4"] = ["Time()", datetime.time.min, GraphSON1Deserializer.deserialize_time] - data["time5"] = ["Time()", datetime.time.max, GraphSON1Deserializer.deserialize_time] - data["blob5"] = ["Blob()", bytearray(b"AKDLIElksadlaswqA" * 10000), GraphSON1Deserializer.deserialize_blob] - data["datetime1"] = ["Date()", datetime.date.today(), GraphSON1Deserializer.deserialize_date] - data["datetime2"] = ["Date()", datetime.date(159, 1, 3), GraphSON1Deserializer.deserialize_date] - data["datetime3"] = ["Date()", datetime.date.min, GraphSON1Deserializer.deserialize_date] - data["datetime4"] = ["Date()", datetime.date.max, GraphSON1Deserializer.deserialize_date] - data["time1"] = ["Time()", datetime.time(12, 6, 12, 444), GraphSON1Deserializer.deserialize_time] - data["time2"] = ["Time()", datetime.time(12, 6, 12), GraphSON1Deserializer.deserialize_time] - data["time3"] = ["Time()", datetime.time(12, 6), GraphSON1Deserializer.deserialize_time] - data["time4"] = ["Time()", datetime.time.min, GraphSON1Deserializer.deserialize_time] - data["time5"] = ["Time()", datetime.time.max, GraphSON1Deserializer.deserialize_time] - - return data - - @staticmethod - def line(length, single_script=False): - queries = [ALLOW_SCANS + ';', - """schema.propertyKey('index').Int().ifNotExists().create(); - schema.propertyKey('distance').Int().ifNotExists().create(); - schema.vertexLabel('lp').properties('index').ifNotExists().create(); - schema.edgeLabel('goesTo').properties('distance').connection('lp', 'lp').ifNotExists().create();"""] - - vertex_script = ["Vertex vertex0 = graph.addVertex(label, 'lp', 'index', 0);"] - for index in range(1, length): - if not single_script and len(vertex_script) > 25: - queries.append("\n".join(vertex_script)) - vertex_script = [ - "Vertex vertex{pindex} = g.V().hasLabel('lp').has('index', {pindex}).next()".format( - pindex=index-1)] - - vertex_script.append(''' - Vertex vertex{vindex} = graph.addVertex(label, 'lp', 'index', {vindex}); - vertex{pindex}.addEdge('goesTo', vertex{vindex}, 'distance', 5); '''.format( - vindex=index, pindex=index - 1)) - - queries.append("\n".join(vertex_script)) - return queries - - @staticmethod - def classic(): - queries = [ALLOW_SCANS, - '''schema.propertyKey('name').Text().ifNotExists().create(); - schema.propertyKey('age').Int().ifNotExists().create(); - schema.propertyKey('lang').Text().ifNotExists().create(); - schema.propertyKey('weight').Float().ifNotExists().create(); - schema.vertexLabel('person').properties('name', 'age').ifNotExists().create(); - schema.vertexLabel('software').properties('name', 'lang').ifNotExists().create(); - schema.edgeLabel('created').properties('weight').connection('person', 'software').ifNotExists().create(); - schema.edgeLabel('created').connection('software', 'software').add(); - schema.edgeLabel('knows').properties('weight').connection('person', 'person').ifNotExists().create();''', - - '''Vertex marko = graph.addVertex(label, 'person', 'name', 'marko', 'age', 29); - Vertex vadas = graph.addVertex(label, 'person', 'name', 'vadas', 'age', 27); - Vertex lop = graph.addVertex(label, 'software', 'name', 'lop', 'lang', 'java'); - Vertex josh = graph.addVertex(label, 'person', 'name', 'josh', 'age', 32); - Vertex ripple = graph.addVertex(label, 'software', 'name', 'ripple', 'lang', 'java'); - Vertex peter = graph.addVertex(label, 'person', 'name', 'peter', 'age', 35); - Vertex carl = graph.addVertex(label, 'person', 'name', 'carl', 'age', 35); - marko.addEdge('knows', vadas, 'weight', 0.5f); - marko.addEdge('knows', josh, 'weight', 1.0f); - marko.addEdge('created', lop, 'weight', 0.4f); - josh.addEdge('created', ripple, 'weight', 1.0f); - josh.addEdge('created', lop, 'weight', 0.4f); - peter.addEdge('created', lop, 'weight', 0.2f);'''] - - return "\n".join(queries) - - @staticmethod - def multiple_fields(): - query_params = {} - queries= [ALLOW_SCANS, - '''schema.propertyKey('shortvalue').Smallint().ifNotExists().create(); - schema.vertexLabel('shortvertex').properties('shortvalue').ifNotExists().create(); - short s1 = 5000; graph.addVertex(label, "shortvertex", "shortvalue", s1); - schema.propertyKey('intvalue').Int().ifNotExists().create(); - schema.vertexLabel('intvertex').properties('intvalue').ifNotExists().create(); - int i1 = 1000000000; graph.addVertex(label, "intvertex", "intvalue", i1); - schema.propertyKey('intvalue2').Int().ifNotExists().create(); - schema.vertexLabel('intvertex2').properties('intvalue2').ifNotExists().create(); - Integer i2 = 100000000; graph.addVertex(label, "intvertex2", "intvalue2", i2); - schema.propertyKey('longvalue').Bigint().ifNotExists().create(); - schema.vertexLabel('longvertex').properties('longvalue').ifNotExists().create(); - long l1 = 9223372036854775807; graph.addVertex(label, "longvertex", "longvalue", l1); - schema.propertyKey('longvalue2').Bigint().ifNotExists().create(); - schema.vertexLabel('longvertex2').properties('longvalue2').ifNotExists().create(); - Long l2 = 100000000000000000L; graph.addVertex(label, "longvertex2", "longvalue2", l2); - schema.propertyKey('floatvalue').Float().ifNotExists().create(); - schema.vertexLabel('floatvertex').properties('floatvalue').ifNotExists().create(); - float f1 = 3.5f; graph.addVertex(label, "floatvertex", "floatvalue", f1); - schema.propertyKey('doublevalue').Double().ifNotExists().create(); - schema.vertexLabel('doublevertex').properties('doublevalue').ifNotExists().create(); - double d1 = 3.5e40; graph.addVertex(label, "doublevertex", "doublevalue", d1); - schema.propertyKey('doublevalue2').Double().ifNotExists().create(); - schema.vertexLabel('doublevertex2').properties('doublevalue2').ifNotExists().create(); - Double d2 = 3.5e40d; graph.addVertex(label, "doublevertex2", "doublevalue2", d2);'''] - - if DSE_VERSION >= Version('5.1'): - queries.append('''schema.propertyKey('datevalue1').Date().ifNotExists().create(); - schema.vertexLabel('datevertex1').properties('datevalue1').ifNotExists().create(); - schema.propertyKey('negdatevalue2').Date().ifNotExists().create(); - schema.vertexLabel('negdatevertex2').properties('negdatevalue2').ifNotExists().create();''') - - for i in range(1, 4): - queries.append('''schema.propertyKey('timevalue{0}').Time().ifNotExists().create(); - schema.vertexLabel('timevertex{0}').properties('timevalue{0}').ifNotExists().create();'''.format( - i)) - - queries.append('graph.addVertex(label, "datevertex1", "datevalue1", date1);') - query_params['date1'] = '1999-07-29' - - queries.append('graph.addVertex(label, "negdatevertex2", "negdatevalue2", date2);') - query_params['date2'] = '-1999-07-28' - - queries.append('graph.addVertex(label, "timevertex1", "timevalue1", time1);') - query_params['time1'] = '14:02' - queries.append('graph.addVertex(label, "timevertex2", "timevalue2", time2);') - query_params['time2'] = '14:02:20' - queries.append('graph.addVertex(label, "timevertex3", "timevalue3", time3);') - query_params['time3'] = '14:02:20.222' - - return queries, query_params - - @staticmethod - def large(): - query_parts = [''' - int size = 2000; - List ids = new ArrayList(); - schema.propertyKey('ts').Int().single().ifNotExists().create(); - schema.propertyKey('sin').Int().single().ifNotExists().create(); - schema.propertyKey('cos').Int().single().ifNotExists().create(); - schema.propertyKey('ii').Int().single().ifNotExists().create(); - schema.vertexLabel('lcg').properties('ts', 'sin', 'cos', 'ii').ifNotExists().create(); - schema.edgeLabel('linked').connection('lcg', 'lcg').ifNotExists().create(); - Vertex v = graph.addVertex(label, 'lcg'); - v.property("ts", 100001); - v.property("sin", 0); - v.property("cos", 1); - v.property("ii", 0); - ids.add(v.id()); - Random rand = new Random(); - for (int ii = 1; ii < size; ii++) { - v = graph.addVertex(label, 'lcg'); - v.property("ii", ii); - v.property("ts", 100001 + ii); - v.property("sin", Math.sin(ii/5.0)); - v.property("cos", Math.cos(ii/5.0)); - Vertex u = g.V(ids.get(rand.nextInt(ids.size()))).next(); - v.addEdge("linked", u); - ids.add(v.id()); - } - g.V().count();'''] - - return "\n".join(query_parts) - - @staticmethod - def address_book(): - p1 = "Point()" - p2 = "Point()" - if DSE_VERSION >= Version('5.1'): - p1 = "Point().withBounds(-100, -100, 100, 100)" - p2 = "Point().withGeoBounds()" - - queries = [ - ALLOW_SCANS, - "schema.propertyKey('name').Text().ifNotExists().create()", - "schema.propertyKey('pointPropWithBoundsWithSearchIndex').{}.ifNotExists().create()".format(p1), - "schema.propertyKey('pointPropWithBounds').{}.ifNotExists().create()".format(p1), - "schema.propertyKey('pointPropWithGeoBoundsWithSearchIndex').{}.ifNotExists().create()".format(p2), - "schema.propertyKey('pointPropWithGeoBounds').{}.ifNotExists().create()".format(p2), - "schema.propertyKey('city').Text().ifNotExists().create()", - "schema.propertyKey('state').Text().ifNotExists().create()", - "schema.propertyKey('description').Text().ifNotExists().create()", - "schema.vertexLabel('person').properties('name', 'city', 'state', 'description', 'pointPropWithBoundsWithSearchIndex', 'pointPropWithBounds', 'pointPropWithGeoBoundsWithSearchIndex', 'pointPropWithGeoBounds').ifNotExists().create()", - "schema.vertexLabel('person').index('searchPointWithBounds').secondary().by('pointPropWithBounds').ifNotExists().add()", - "schema.vertexLabel('person').index('searchPointWithGeoBounds').secondary().by('pointPropWithGeoBounds').ifNotExists().add()", - - "g.addV('person').property('name', 'Paul Thomas Joe').property('city', 'Rochester').property('state', 'MN').property('pointPropWithBoundsWithSearchIndex', Geo.point(-92.46295, 44.0234)).property('pointPropWithBounds', Geo.point(-92.46295, 44.0234)).property('pointPropWithGeoBoundsWithSearchIndex', Geo.point(-92.46295, 44.0234)).property('pointPropWithGeoBounds', Geo.point(-92.46295, 44.0234)).property('description', 'Lives by the hospital').next()", - "g.addV('person').property('name', 'George Bill Steve').property('city', 'Minneapolis').property('state', 'MN').property('pointPropWithBoundsWithSearchIndex', Geo.point(-93.266667, 44.093333)).property('pointPropWithBounds', Geo.point(-93.266667, 44.093333)).property('pointPropWithGeoBoundsWithSearchIndex', Geo.point(-93.266667, 44.093333)).property('pointPropWithGeoBounds', Geo.point(-93.266667, 44.093333)).property('description', 'A cold dude').next()", - "g.addV('person').property('name', 'James Paul Smith').property('city', 'Chicago').property('state', 'IL').property('pointPropWithBoundsWithSearchIndex', Geo.point(-87.684722, 41.836944)).property('description', 'Likes to hang out').next()", - "g.addV('person').property('name', 'Jill Alice').property('city', 'Atlanta').property('state', 'GA').property('pointPropWithBoundsWithSearchIndex', Geo.point(-84.39, 33.755)).property('description', 'Enjoys a nice cold coca cola').next()" - ] - - if not Version('5.0') <= DSE_VERSION < Version('5.1'): - queries.append("schema.vertexLabel('person').index('search').search().by('pointPropWithBoundsWithSearchIndex').withError(0.00001, 0.0).by('pointPropWithGeoBoundsWithSearchIndex').withError(0.00001, 0.0).ifNotExists().add()") - - return "\n".join(queries) - - -class CoreGraphFixtures(GraphFixtures): - - @staticmethod - def datatypes(): - data = ClassicGraphFixtures.datatypes() - del data['duration1'] - del data['duration2'] - - # Core Graphs only types - data["map1"] = ["mapOf(Text, Text)", {'test': 'test'}, None] - data["map2"] = ["mapOf(Text, Point)", {'test': Point(.5, .13)}, None] - data["map3"] = ["frozen(mapOf(Int, Varchar))", {42: 'test'}, None] - - data["list1"] = ["listOf(Text)", ['test', 'hello', 'world'], None] - data["list2"] = ["listOf(Int)", [42, 632, 32], None] - data["list3"] = ["listOf(Point)", [Point(.5, .13), Point(42.5, .13)], None] - data["list4"] = ["frozen(listOf(Int))", [42, 55, 33], None] - - data["set1"] = ["setOf(Text)", {'test', 'hello', 'world'}, None] - data["set2"] = ["setOf(Int)", {42, 632, 32}, None] - data["set3"] = ["setOf(Point)", {Point(.5, .13), Point(42.5, .13)}, None] - data["set4"] = ["frozen(setOf(Int))", {42, 55, 33}, None] - - data["tuple1"] = ["tupleOf(Int, Text)", (42, "world"), None] - data["tuple2"] = ["tupleOf(Int, tupleOf(Text, tupleOf(Text, Point)))", (42, ("world", ('this', Point(.5, .13)))), None] - data["tuple3"] = ["tupleOf(Int, tupleOf(Text, frozen(mapOf(Text, Text))))", (42, ("world", {'test': 'test'})), None] - data["tuple4"] = ["tupleOf(Int, tupleOf(Text, frozen(listOf(Int))))", (42, ("world", [65, 89])), None] - data["tuple5"] = ["tupleOf(Int, tupleOf(Text, frozen(setOf(Int))))", (42, ("world", {65, 55})), None] - data["tuple6"] = ["tupleOf(Int, tupleOf(Text, tupleOf(Text, LineString)))", - (42, ("world", ('this', LineString(((1.0, 2.0), (3.0, 4.0), (-89.0, 90.0)))))), None] - - data["tuple7"] = ["tupleOf(Int, tupleOf(Text, tupleOf(Text, Polygon)))", - (42, ("world", ('this', Polygon([(10.0, 10.0), (80.0, 10.0), (80., 88.0), (10., 89.0), (10., 10.0)], - [[(20., 20.0), (20., 30.0), (30., 30.0), (30., 20.0), (20., 20.0)], - [(40., 20.0), (40., 30.0), (50., 30.0), (50., 20.0), (40., 20.0)]])))), None] - data["dse_duration1"] = ["Duration()", Duration(42, 12, 10303312), None] - data["dse_duration2"] = ["Duration()", Duration(50, 32, 11), None] - - return data - - @staticmethod - def line(length, single_script=False): - queries = [""" - schema.vertexLabel('lp').ifNotExists().partitionBy('index', Int).create(); - schema.edgeLabel('goesTo').ifNotExists().from('lp').to('lp').property('distance', Int).create(); - """] - - vertex_script = ["g.addV('lp').property('index', 0).next();"] - for index in range(1, length): - if not single_script and len(vertex_script) > 25: - queries.append("\n".join(vertex_script)) - vertex_script = [] - - vertex_script.append(''' - g.addV('lp').property('index', {index}).next(); - g.V().hasLabel('lp').has('index', {pindex}).as('pp').V().hasLabel('lp').has('index', {index}).as('p'). - addE('goesTo').from('pp').to('p').property('distance', 5).next(); - '''.format( - index=index, pindex=index - 1)) - - queries.append("\n".join(vertex_script)) - return queries - - @staticmethod - def classic(): - queries = [ - ''' - schema.vertexLabel('person').ifNotExists().partitionBy('name', Text).property('age', Int).create(); - schema.vertexLabel('software')ifNotExists().partitionBy('name', Text).property('lang', Text).create(); - schema.edgeLabel('created').ifNotExists().from('person').to('software').property('weight', Double).create(); - schema.edgeLabel('knows').ifNotExists().from('person').to('person').property('weight', Double).create(); - ''', - - ''' - Vertex marko = g.addV('person').property('name', 'marko').property('age', 29).next(); - Vertex vadas = g.addV('person').property('name', 'vadas').property('age', 27).next(); - Vertex lop = g.addV('software').property('name', 'lop').property('lang', 'java').next(); - Vertex josh = g.addV('person').property('name', 'josh').property('age', 32).next(); - Vertex peter = g.addV('person').property('name', 'peter').property('age', 35).next(); - Vertex carl = g.addV('person').property('name', 'carl').property('age', 35).next(); - Vertex ripple = g.addV('software').property('name', 'ripple').property('lang', 'java').next(); - - // TODO, switch to VertexReference and use v.id() - g.V().hasLabel('person').has('name', 'vadas').as('v').V().hasLabel('person').has('name', 'marko').as('m').addE('knows').from('m').to('v').property('weight', 0.5d).next(); - g.V().hasLabel('person').has('name', 'josh').as('j').V().hasLabel('person').has('name', 'marko').as('m').addE('knows').from('m').to('j').property('weight', 1.0d).next(); - g.V().hasLabel('software').has('name', 'lop').as('l').V().hasLabel('person').has('name', 'marko').as('m').addE('created').from('m').to('l').property('weight', 0.4d).next(); - g.V().hasLabel('software').has('name', 'ripple').as('r').V().hasLabel('person').has('name', 'josh').as('j').addE('created').from('j').to('r').property('weight', 1.0d).next(); - g.V().hasLabel('software').has('name', 'lop').as('l').V().hasLabel('person').has('name', 'josh').as('j').addE('created').from('j').to('l').property('weight', 0.4d).next(); - g.V().hasLabel('software').has('name', 'lop').as('l').V().hasLabel('person').has('name', 'peter').as('p').addE('created').from('p').to('l').property('weight', 0.2d).next(); - - '''] - - return queries - - @staticmethod - def multiple_fields(): - ## no generic test currently needs this - raise NotImplementedError() - - @staticmethod - def large(): - query_parts = [ - ''' - schema.vertexLabel('lcg').ifNotExists().partitionBy('ts', Int).property('sin', Double). - property('cos', Double).property('ii', Int).create(); - schema.edgeLabel('linked').ifNotExists().from('lcg').to('lcg').create(); - ''', - - ''' - int size = 2000; - List ids = new ArrayList(); - v = g.addV('lcg').property('ts', 100001).property('sin', 0d).property('cos', 1d).property('ii', 0).next(); - ids.add(v.id()); - Random rand = new Random(); - for (int ii = 1; ii < size; ii++) { - v = g.addV('lcg').property('ts', 100001 + ii).property('sin', Math.sin(ii/5.0)).property('cos', Math.cos(ii/5.0)).property('ii', ii).next(); - - uid = ids.get(rand.nextInt(ids.size())) - g.V(v.id()).as('v').V(uid).as('u').addE('linked').from('v').to('u').next(); - ids.add(v.id()); - } - g.V().count();''' - ] - - return query_parts - - @staticmethod - def address_book(): - queries = [ - "schema.vertexLabel('person').ifNotExists().partitionBy('name', Text)." - "property('pointPropWithBoundsWithSearchIndex', Point)." - "property('pointPropWithBounds', Point)." - "property('pointPropWithGeoBoundsWithSearchIndex', Point)." - "property('pointPropWithGeoBounds', Point)." - "property('city', Text)." - "property('state', Text)." - "property('description', Text).create()", - "schema.vertexLabel('person').searchIndex().by('name').by('pointPropWithBounds').by('pointPropWithGeoBounds').by('description').asText().create()", - "g.addV('person').property('name', 'Paul Thomas Joe').property('city', 'Rochester').property('state', 'MN').property('pointPropWithBoundsWithSearchIndex', Geo.point(-92.46295, 44.0234)).property('pointPropWithBounds', Geo.point(-92.46295, 44.0234)).property('pointPropWithGeoBoundsWithSearchIndex', Geo.point(-92.46295, 44.0234)).property('pointPropWithGeoBounds', Geo.point(-92.46295, 44.0234)).property('description', 'Lives by the hospital').next()", - "g.addV('person').property('name', 'George Bill Steve').property('city', 'Minneapolis').property('state', 'MN').property('pointPropWithBoundsWithSearchIndex', Geo.point(-93.266667, 44.093333)).property('pointPropWithBounds', Geo.point(-93.266667, 44.093333)).property('pointPropWithGeoBoundsWithSearchIndex', Geo.point(-93.266667, 44.093333)).property('pointPropWithGeoBounds', Geo.point(-93.266667, 44.093333)).property('description', 'A cold dude').next()", - "g.addV('person').property('name', 'James Paul Smith').property('city', 'Chicago').property('state', 'IL').property('pointPropWithBoundsWithSearchIndex', Geo.point(-87.684722, 41.836944)).property('description', 'Likes to hang out').next()", - "g.addV('person').property('name', 'Jill Alice').property('city', 'Atlanta').property('state', 'GA').property('pointPropWithBoundsWithSearchIndex', Geo.point(-84.39, 33.755)).property('description', 'Enjoys a nice cold coca cola').next()" - ] - - if not Version('5.0') <= DSE_VERSION < Version('5.1'): - queries.append("schema.vertexLabel('person').searchIndex().by('pointPropWithBoundsWithSearchIndex').by('pointPropWithGeoBounds')" - ".by('pointPropWithGeoBoundsWithSearchIndex').create()") - - return queries - - -def validate_classic_vertex(test, vertex): - vertex_props = vertex.properties.keys() - test.assertEqual(len(vertex_props), 2) - test.assertIn('name', vertex_props) - test.assertTrue('lang' in vertex_props or 'age' in vertex_props) - - -def validate_classic_vertex_return_type(test, vertex): - validate_generic_vertex_result_type(vertex) - vertex_props = vertex.properties - test.assertIn('name', vertex_props) - test.assertTrue('lang' in vertex_props or 'age' in vertex_props) - - -def validate_generic_vertex_result_type(test, vertex): - test.assertIsInstance(vertex, Vertex) - for attr in ('id', 'type', 'label', 'properties'): - test.assertIsNotNone(getattr(vertex, attr)) - - -def validate_classic_edge_properties(test, edge_properties): - test.assertEqual(len(edge_properties.keys()), 1) - test.assertIn('weight', edge_properties) - test.assertIsInstance(edge_properties, dict) - - -def validate_classic_edge(test, edge): - validate_generic_edge_result_type(test, edge) - validate_classic_edge_properties(test, edge.properties) - - -def validate_line_edge(test, edge): - validate_generic_edge_result_type(test, edge) - edge_props = edge.properties - test.assertEqual(len(edge_props.keys()), 1) - test.assertIn('distance', edge_props) - - -def validate_generic_edge_result_type(test, edge): - test.assertIsInstance(edge, Edge) - for attr in ('properties', 'outV', 'outVLabel', 'inV', 'inVLabel', 'label', 'type', 'id'): - test.assertIsNotNone(getattr(edge, attr)) - - -def validate_path_result_type(test, path): - test.assertIsInstance(path, Path) - test.assertIsNotNone(path.labels) - for obj in path.objects: - if isinstance(obj, Edge): - validate_classic_edge(test, obj) - elif isinstance(obj, Vertex): - validate_classic_vertex(test, obj) - else: - test.fail("Invalid object found in path " + str(object.type)) - - -class GraphTestConfiguration(object): - """Possible Configurations: - ClassicGraphSchema: - graphson1 - graphson2 - graphson3 - - CoreGraphSchema - graphson3 - """ - - @classmethod - def schemas(cls): - schemas = [ClassicGraphSchema] - if DSE_VERSION >= Version("6.8"): - schemas.append(CoreGraphSchema) - return schemas - - @classmethod - def graphson_versions(cls): - graphson_versions = [GraphProtocol.GRAPHSON_1_0] - if DSE_VERSION >= Version("6.0"): - graphson_versions.append(GraphProtocol.GRAPHSON_2_0) - if DSE_VERSION >= Version("6.8"): - graphson_versions.append(GraphProtocol.GRAPHSON_3_0) - return graphson_versions - - @classmethod - def schema_configurations(cls, schema=None): - schemas = cls.schemas() if schema is None else [schema] - configurations = [] - for s in schemas: - configurations.append(s) - - return configurations - - @classmethod - def configurations(cls, schema=None, graphson=None): - schemas = cls.schemas() if schema is None else [schema] - graphson_versions = cls.graphson_versions() if graphson is None else [graphson] - - configurations = [] - for s in schemas: - for g in graphson_versions: - if s is CoreGraphSchema and g != GraphProtocol.GRAPHSON_3_0: - continue - configurations.append((s, g)) - - return configurations - - @staticmethod - def _make_graph_schema_test_method(func, schema): - def test_input(self): - self.setup_graph(schema) - try: - func(self, schema) - except: - raise - finally: - self.teardown_graph(schema) - - schema_name = 'classic' if schema is ClassicGraphSchema else 'core' - test_input.__name__ = '{func}_{schema}'.format( - func=func.__name__.lstrip('_'), schema=schema_name) - return test_input - - @staticmethod - def _make_graph_test_method(func, schema, graphson): - def test_input(self): - self.setup_graph(schema) - try: - func(self, schema, graphson) - except: - raise - finally: - self.teardown_graph(schema) - - graphson_name = 'graphson1' - if graphson == GraphProtocol.GRAPHSON_2_0: - graphson_name = 'graphson2' - elif graphson == GraphProtocol.GRAPHSON_3_0: - graphson_name = 'graphson3' - - schema_name = 'classic' if schema is ClassicGraphSchema else 'core' - - # avoid keyspace name too long issue - if DSE_VERSION < Version('6.7'): - schema_name = schema_name[0] - graphson_name = 'g' + graphson_name[-1] - - test_input.__name__ = '{func}_{schema}_{graphson}'.format( - func=func.__name__.lstrip('_'), schema=schema_name, graphson=graphson_name) - return test_input - - @classmethod - def generate_tests(cls, schema=None, graphson=None, traversal=False): - """Generate tests for a graph configuration""" - def decorator(klass): - if DSE_VERSION: - predicate = inspect.isfunction - for name, func in inspect.getmembers(klass, predicate=predicate): - if not name.startswith('_test'): - continue - for _schema, _graphson in cls.configurations(schema, graphson): - if traversal and _graphson == GraphProtocol.GRAPHSON_1_0: - continue - test_input = cls._make_graph_test_method(func, _schema, _graphson) - log.debug("Generated test '{}.{}'".format(klass.__name__, test_input.__name__)) - setattr(klass, test_input.__name__, test_input) - return klass - - return decorator - - @classmethod - def generate_schema_tests(cls, schema=None): - """Generate schema tests for a graph configuration""" - def decorator(klass): - if DSE_VERSION: - predicate = inspect.isfunction - for name, func in inspect.getmembers(klass, predicate=predicate): - if not name.startswith('_test'): - continue - for _schema in cls.schema_configurations(schema): - test_input = cls._make_graph_schema_test_method(func, _schema) - log.debug("Generated test '{}.{}'".format(klass.__name__, test_input.__name__)) - setattr(klass, test_input.__name__, test_input) - return klass - - return decorator - - -class VertexLabel(object): - """ - Helper that represents a new VertexLabel: - - VertexLabel(['Int()', 'Float()']) # a vertex with 2 properties named property1 and property2 - VertexLabel([('int1', 'Int()'), 'Float()']) # a vertex with 2 properties named int1 and property1 - """ - - id = 0 - label = None - properties = None - - def __init__(self, properties): - VertexLabel.id += 1 - self.id = VertexLabel.id - self.label = "vertex{}".format(self.id) - self.properties = {'pkid': self.id} - property_count = 0 - for p in properties: - if isinstance(p, tuple): - name, typ = p - else: - property_count += 1 - name = "property-v{}-{}".format(self.id, property_count) - typ = p - self.properties[name] = typ - - @property - def non_pk_properties(self): - return {p: v for p, v in self.properties.items() if p != 'pkid'} - - -class GraphSchema(object): - - has_geo_bounds = DSE_VERSION and DSE_VERSION >= Version('5.1') - fixtures = GraphFixtures - - @classmethod - def sanitize_type(cls, typ): - if typ.lower().startswith("point"): - return cls.sanitize_point_type() - elif typ.lower().startswith("line"): - return cls.sanitize_line_type() - elif typ.lower().startswith("poly"): - return cls.sanitize_polygon_type() - else: - return typ - - @classmethod - def sanitize_point_type(cls): - return "Point().withGeoBounds()" if cls.has_geo_bounds else "Point()" - - @classmethod - def sanitize_line_type(cls): - return "Linestring().withGeoBounds()" if cls.has_geo_bounds else "Linestring()" - - @classmethod - def sanitize_polygon_type(cls): - return "Polygon().withGeoBounds()" if cls.has_geo_bounds else "Polygon()" - - @staticmethod - def drop_graph(session, graph_name): - ks = list(session.execute( - "SELECT * FROM system_schema.keyspaces WHERE keyspace_name = '{}';".format(graph_name))) - if not ks: - return - - try: - session.execute_graph('system.graph(name).drop()', {'name': graph_name}, - execution_profile=EXEC_PROFILE_GRAPH_SYSTEM_DEFAULT) - except: - pass - - @staticmethod - def create_graph(session, graph_name): - raise NotImplementedError() - - @staticmethod - def clear(session): - pass - - @staticmethod - def create_vertex_label(session, vertex_label, execution_profile=EXEC_PROFILE_GRAPH_DEFAULT): - raise NotImplementedError() - - @staticmethod - def add_vertex(session, vertex_label, name, value, execution_profile=EXEC_PROFILE_GRAPH_DEFAULT): - raise NotImplementedError() - - @classmethod - def ensure_properties(cls, session, obj, execution_profile=EXEC_PROFILE_GRAPH_DEFAULT): - if not isinstance(obj, (Vertex, Edge)): - return - - # This pre-processing is due to a change in TinkerPop - # properties are not returned automatically anymore - # with some queries. - if not obj.properties: - if isinstance(obj, Edge): - obj.properties = {} - for p in cls.get_edge_properties(session, obj, execution_profile=execution_profile): - obj.properties.update(p) - elif isinstance(obj, Vertex): - obj.properties = { - p.label: p - for p in cls.get_vertex_properties(session, obj, execution_profile=execution_profile) - } - - @staticmethod - def get_vertex_properties(session, vertex, execution_profile=EXEC_PROFILE_GRAPH_DEFAULT): - return session.execute_graph("g.V(vertex_id).properties().toList()", {'vertex_id': vertex.id}, - execution_profile=execution_profile) - - @staticmethod - def get_edge_properties(session, edge, execution_profile=EXEC_PROFILE_GRAPH_DEFAULT): - v = session.execute_graph("g.E(edge_id).properties().toList()", {'edge_id': edge.id}, - execution_profile=execution_profile) - return v - - -class ClassicGraphSchema(GraphSchema): - - fixtures = ClassicGraphFixtures - - @staticmethod - def create_graph(session, graph_name): - session.execute_graph(CREATE_CLASSIC_GRAPH, {'name': graph_name}, - execution_profile=EXEC_PROFILE_GRAPH_SYSTEM_DEFAULT) - wait_for_graph_inserted(session, graph_name) - - @staticmethod - def clear(session): - session.execute_graph('schema.clear()') - - @classmethod - def create_vertex_label(cls, session, vertex_label, execution_profile=EXEC_PROFILE_GRAPH_DEFAULT): - statements = ["schema.propertyKey('pkid').Int().ifNotExists().create();"] - for k, v in vertex_label.non_pk_properties.items(): - typ = cls.sanitize_type(v) - statements.append("schema.propertyKey('{name}').{type}.create();".format( - name=k, type=typ - )) - - statements.append("schema.vertexLabel('{label}').partitionKey('pkid').properties(".format( - label=vertex_label.label)) - property_names = [name for name in vertex_label.non_pk_properties.keys()] - statements.append(", ".join(["'{}'".format(p) for p in property_names])) - statements.append(").create();") - - to_run = "\n".join(statements) - session.execute_graph(to_run, execution_profile=execution_profile) - - @staticmethod - def add_vertex(session, vertex_label, name, value, execution_profile=EXEC_PROFILE_GRAPH_DEFAULT): - statement = "g.addV('{label}').property('pkid', {pkid}).property('{property_name}', val);".format( - pkid=vertex_label.id, label=vertex_label.label, property_name=name) - parameters = {'val': value} - return session.execute_graph(statement, parameters, execution_profile=execution_profile) - - -class CoreGraphSchema(GraphSchema): - - fixtures = CoreGraphFixtures - - @classmethod - def sanitize_type(cls, typ): - typ = super(CoreGraphSchema, cls).sanitize_type(typ) - return typ.replace('()', '') - - @classmethod - def sanitize_point_type(cls): - return "Point" - - @classmethod - def sanitize_line_type(cls): - return "LineString" - - @classmethod - def sanitize_polygon_type(cls): - return "Polygon" - - @staticmethod - def create_graph(session, graph_name): - session.execute_graph('system.graph(name).create()', {'name': graph_name}, - execution_profile=EXEC_PROFILE_GRAPH_SYSTEM_DEFAULT) - wait_for_graph_inserted(session, graph_name) - - @classmethod - def create_vertex_label(cls, session, vertex_label, execution_profile=EXEC_PROFILE_GRAPH_DEFAULT): - statements = ["schema.vertexLabel('{label}').partitionBy('pkid', Int)".format( - label=vertex_label.label)] - - for name, typ in vertex_label.non_pk_properties.items(): - typ = cls.sanitize_type(typ) - statements.append(".property('{name}', {type})".format(name=name, type=typ)) - statements.append(".create();") - - to_run = "\n".join(statements) - session.execute_graph(to_run, execution_profile=execution_profile) - - @staticmethod - def add_vertex(session, vertex_label, name, value, execution_profile=EXEC_PROFILE_GRAPH_DEFAULT): - statement = "g.addV('{label}').property('pkid', {pkid}).property('{property_name}', val);".format( - pkid=vertex_label.id, label=vertex_label.label, property_name=name) - parameters = {'val': value} - return session.execute_graph(statement, parameters, execution_profile=execution_profile) diff --git a/tests/integration/advanced/graph/fluent/__init__.py b/tests/integration/advanced/graph/fluent/__init__.py deleted file mode 100644 index 155de026c5..0000000000 --- a/tests/integration/advanced/graph/fluent/__init__.py +++ /dev/null @@ -1,718 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -import datetime -import time -from collections import namedtuple -from packaging.version import Version - -from cassandra.datastax.graph.fluent import DseGraph -from cassandra.graph import VertexProperty, GraphProtocol -from cassandra.util import Point, Polygon, LineString - -from gremlin_python.process.graph_traversal import GraphTraversal, GraphTraversalSource -from gremlin_python.process.traversal import P -from gremlin_python.structure.graph import Edge as TravEdge -from gremlin_python.structure.graph import Vertex as TravVertex, VertexProperty as TravVertexProperty - -from tests.util import wait_until_not_raised -from tests.integration import DSE_VERSION -from tests.integration.advanced.graph import ( - GraphUnitTestCase, ClassicGraphSchema, CoreGraphSchema, - VertexLabel) -from tests.integration import requiredse - -import unittest - - -import ipaddress - - -def check_equality_base(testcase, original, read_value): - if isinstance(original, float): - testcase.assertAlmostEqual(original, read_value, delta=.01) - elif isinstance(original, ipaddress.IPv4Address): - testcase.assertAlmostEqual(original, ipaddress.IPv4Address(read_value)) - elif isinstance(original, ipaddress.IPv6Address): - testcase.assertAlmostEqual(original, ipaddress.IPv6Address(read_value)) - else: - testcase.assertEqual(original, read_value) - - -def create_traversal_profiles(cluster, graph_name): - ep_graphson2 = DseGraph().create_execution_profile( - graph_name, graph_protocol=GraphProtocol.GRAPHSON_2_0) - ep_graphson3 = DseGraph().create_execution_profile( - graph_name, graph_protocol=GraphProtocol.GRAPHSON_3_0) - - cluster.add_execution_profile('traversal_graphson2', ep_graphson2) - cluster.add_execution_profile('traversal_graphson3', ep_graphson3) - - return ep_graphson2, ep_graphson3 - - -class _AbstractTraversalTest(GraphUnitTestCase): - - def setUp(self): - super(_AbstractTraversalTest, self).setUp() - self.ep_graphson2, self.ep_graphson3 = create_traversal_profiles(self.cluster, self.graph_name) - - def _test_basic_query(self, schema, graphson): - """ - Test to validate that basic graph queries works - - Creates a simple classic tinkerpot graph, and attempts to preform a basic query - using Tinkerpop's GLV with both explicit and implicit execution - ensuring that each one is correct. See reference graph here - http://www.tinkerpop.com/docs/3.0.0.M1/ - - @since 1.0.0 - @jira_ticket PYTHON-641 - @expected_result graph should generate and all vertices and edge results should be - - @test_category dse graph - """ - - g = self.fetch_traversal_source(graphson) - self.execute_graph(schema.fixtures.classic(), graphson) - traversal = g.V().has('name', 'marko').out('knows').values('name') - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 2) - self.assertIn('vadas', results_list) - self.assertIn('josh', results_list) - - def _test_classic_graph(self, schema, graphson): - """ - Test to validate that basic graph generation, and vertex and edges are surfaced correctly - - Creates a simple classic tinkerpot graph, and iterates over the the vertices and edges - using Tinkerpop's GLV with both explicit and implicit execution - ensuring that each one iscorrect. See reference graph here - http://www.tinkerpop.com/docs/3.0.0.M1/ - - @since 1.0.0 - @jira_ticket PYTHON-641 - @expected_result graph should generate and all vertices and edge results should be - - @test_category dse graph - """ - - self.execute_graph(schema.fixtures.classic(), graphson) - ep = self.get_execution_profile(graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.V() - vert_list = self.execute_traversal(traversal, graphson) - - for vertex in vert_list: - schema.ensure_properties(self.session, vertex, execution_profile=ep) - self._validate_classic_vertex(g, vertex) - traversal = g.E() - edge_list = self.execute_traversal(traversal, graphson) - for edge in edge_list: - schema.ensure_properties(self.session, edge, execution_profile=ep) - self._validate_classic_edge(g, edge) - - def _test_graph_classic_path(self, schema, graphson): - """ - Test to validate that the path version of the result type is generated correctly. It also - tests basic path results as that is not covered elsewhere - - @since 1.0.0 - @jira_ticket PYTHON-641 - @expected_result path object should be unpacked correctly including all nested edges and vertices - @test_category dse graph - """ - self.execute_graph(schema.fixtures.classic(), graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.V().hasLabel('person').has('name', 'marko').as_('a').outE('knows').inV().as_('c', 'd').outE('created').as_('e', 'f', 'g').inV().path() - path_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(path_list), 2) - for path in path_list: - self._validate_path_result_type(g, path) - - def _test_range_query(self, schema, graphson): - """ - Test to validate range queries are handled correctly. - - Creates a very large line graph script and executes it. Then proceeds to to a range - limited query against it, and ensure that the results are formated correctly and that - the result set is properly sized. - - @since 1.0.0 - @jira_ticket PYTHON-641 - @expected_result result set should be properly formated and properly sized - - @test_category dse graph - """ - - self.execute_graph(schema.fixtures.line(150), graphson) - ep = self.get_execution_profile(graphson) - g = self.fetch_traversal_source(graphson) - - traversal = g.E().range(0, 10) - edges = self.execute_traversal(traversal, graphson) - self.assertEqual(len(edges), 10) - for edge in edges: - schema.ensure_properties(self.session, edge, execution_profile=ep) - self._validate_line_edge(g, edge) - - def _test_result_types(self, schema, graphson): - """ - Test to validate that the edge and vertex version of results are constructed correctly. - - @since 1.0.0 - @jira_ticket PYTHON-641 - @expected_result edge/vertex result types should be unpacked correctly. - @test_category dse graph - """ - self.execute_graph(schema.fixtures.line(150), graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.V() - vertices = self.execute_traversal(traversal, graphson) - for vertex in vertices: - self._validate_type(g, vertex) - - def _test_large_result_set(self, schema, graphson): - """ - Test to validate that large result sets return correctly. - - Creates a very large graph. Ensures that large result sets are handled appropriately. - - @since 1.0.0 - @jira_ticket PYTHON-641 - @expected_result when limits of result sets are hit errors should be surfaced appropriately - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.large(), graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.V() - vertices = self.execute_traversal(traversal, graphson) - for vertex in vertices: - self._validate_generic_vertex_result_type(g, vertex) - - def _test_vertex_meta_properties(self, schema, graphson): - """ - Test verifying vertex property properties - - @since 1.0.0 - @jira_ticket PYTHON-641 - - @test_category dse graph - """ - if schema is not ClassicGraphSchema: - raise unittest.SkipTest('skipped because multiple properties are only supported with classic graphs') - - s = self.session - s.execute_graph("schema.propertyKey('k0').Text().ifNotExists().create();") - s.execute_graph("schema.propertyKey('k1').Text().ifNotExists().create();") - s.execute_graph("schema.propertyKey('key').Text().properties('k0', 'k1').ifNotExists().create();") - s.execute_graph("schema.vertexLabel('MLP').properties('key').ifNotExists().create();") - s.execute_graph("schema.config().option('graph.allow_scan').set('true');") - v = s.execute_graph('''v = graph.addVertex('MLP') - v.property('key', 'meta_prop', 'k0', 'v0', 'k1', 'v1') - v''')[0] - - g = self.fetch_traversal_source(graphson) - - traversal = g.V() - # This should contain key, and value where value is a property - # This should be a vertex property and should contain sub properties - results = self.execute_traversal(traversal, graphson) - self._validate_meta_property(g, results[0]) - - def _test_vertex_multiple_properties(self, schema, graphson): - """ - Test verifying vertex property form for various Cardinality - - All key types are encoded as a list, regardless of cardinality - - Single cardinality properties have only one value -- the last one added - - Default is single (this is config dependent) - - @since 1.0.0 - @jira_ticket PYTHON-641 - - @test_category dse graph - """ - if schema is not ClassicGraphSchema: - raise unittest.SkipTest('skipped because multiple properties are only supported with classic graphs') - - s = self.session - s.execute_graph('''Schema schema = graph.schema(); - schema.propertyKey('mult_key').Text().multiple().ifNotExists().create(); - schema.propertyKey('single_key').Text().single().ifNotExists().create(); - schema.vertexLabel('MPW1').properties('mult_key').ifNotExists().create(); - schema.vertexLabel('MPW2').properties('mult_key').ifNotExists().create(); - schema.vertexLabel('SW1').properties('single_key').ifNotExists().create();''') - - mpw1v = s.execute_graph('''v = graph.addVertex('MPW1') - v.property('mult_key', 'value') - v''')[0] - - mpw2v = s.execute_graph('''g.addV('MPW2').property('mult_key', 'value0').property('mult_key', 'value1')''')[0] - - g = self.fetch_traversal_source(graphson) - traversal = g.V(mpw1v.id).properties() - - vertex_props = self.execute_traversal(traversal, graphson) - - self.assertEqual(len(vertex_props), 1) - - self.assertEqual(self.fetch_key_from_prop(vertex_props[0]), "mult_key") - self.assertEqual(vertex_props[0].value, "value") - - # multiple_with_two_values - #v = s.execute_graph('''g.addV(label, 'MPW2', 'mult_key', 'value0', 'mult_key', 'value1')''')[0] - traversal = g.V(mpw2v.id).properties() - - vertex_props = self.execute_traversal(traversal, graphson) - - self.assertEqual(len(vertex_props), 2) - self.assertEqual(self.fetch_key_from_prop(vertex_props[0]), 'mult_key') - self.assertEqual(self.fetch_key_from_prop(vertex_props[1]), 'mult_key') - self.assertEqual(vertex_props[0].value, 'value0') - self.assertEqual(vertex_props[1].value, 'value1') - - # single_with_one_value - v = s.execute_graph('''v = graph.addVertex('SW1') - v.property('single_key', 'value') - v''')[0] - traversal = g.V(v.id).properties() - vertex_props = self.execute_traversal(traversal, graphson) - self.assertEqual(len(vertex_props), 1) - self.assertEqual(self.fetch_key_from_prop(vertex_props[0]), "single_key") - self.assertEqual(vertex_props[0].value, "value") - - def should_parse_meta_properties(self): - g = self.fetch_traversal_source() - g.addV("meta_v").property("meta_prop", "hello", "sub_prop", "hi", "sub_prop2", "hi2") - - def _test_all_graph_types_with_schema(self, schema, graphson): - """ - Exhaustively goes through each type that is supported by dse_graph. - creates a vertex for each type using a dse-tinkerpop traversal, - It then attempts to fetch it from the server and compares it to what was inserted - Prime the graph with the correct schema first - - @since 1.0.0 - @jira_ticket PYTHON-641 - @expected_result inserted objects are equivalent to those retrieved - - @test_category dse graph - """ - self._write_and_read_data_types(schema, graphson) - - def _test_all_graph_types_without_schema(self, schema, graphson): - """ - Exhaustively goes through each type that is supported by dse_graph. - creates a vertex for each type using a dse-tinkerpop traversal, - It then attempts to fetch it from the server and compares it to what was inserted - Do not prime the graph with the correct schema first - @since 1.0.0 - @jira_ticket PYTHON-641 - @expected_result inserted objects are equivalent to those retrieved - @test_category dse graph - """ - if schema is not ClassicGraphSchema: - raise unittest.SkipTest('schema-less is only for classic graphs') - self._write_and_read_data_types(schema, graphson, use_schema=False) - - def _test_dsl(self, schema, graphson): - """ - The test creates a SocialTraversal and a SocialTraversalSource as part of - a DSL. Then calls it's method and checks the results to verify - we have the expected results - - @since @since 1.1.0a1 - @jira_ticket PYTHON-790 - @expected_result only the vertex corresponding to marko is in the result - - @test_category dse graph - """ - class SocialTraversal(GraphTraversal): - def knows(self, person_name): - return self.out("knows").hasLabel("person").has("name", person_name).in_() - - class SocialTraversalSource(GraphTraversalSource): - def __init__(self, *args, **kwargs): - super(SocialTraversalSource, self).__init__(*args, **kwargs) - self.graph_traversal = SocialTraversal - - def people(self, *names): - return self.get_graph_traversal().V().has("name", P.within(*names)) - - self.execute_graph(schema.fixtures.classic(), graphson) - if schema is CoreGraphSchema: - self.execute_graph(""" - schema.edgeLabel('knows').from('person').to('person').materializedView('person__knows__person_by_in_name'). - ifNotExists().partitionBy('in_name').clusterBy('out_name', Asc).create() - """, graphson) - time.sleep(1) # give some time to the MV to be populated - g = self.fetch_traversal_source(graphson, traversal_class=SocialTraversalSource) - - traversal = g.people("marko", "albert").knows("vadas") - results = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results), 1) - only_vertex = results[0] - schema.ensure_properties(self.session, only_vertex, - execution_profile=self.get_execution_profile(graphson)) - self._validate_classic_vertex(g, only_vertex) - - def _test_bulked_results(self, schema, graphson): - """ - Send a query expecting a bulked result and the driver "undoes" - the bulk and returns the expected list - - @since 1.1.0a1 - @jira_ticket PYTHON-771 - @expected_result the expanded list - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.classic(), graphson) - g = self.fetch_traversal_source(graphson) - barrier_traversal = g.E().label().barrier() - results = self.execute_traversal(barrier_traversal, graphson) - self.assertEqual(sorted(["created", "created", "created", "created", "knows", "knows"]), sorted(results)) - - def _test_udt_with_classes(self, schema, graphson): - class Address(object): - - def __init__(self, address, city, state): - self.address = address - self.city = city - self.state = state - - def __eq__(self, other): - return self.address == other.address and self.city == other.city and self.state == other.state - - class AddressWithTags(object): - - def __init__(self, address, city, state, tags): - self.address = address - self.city = city - self.state = state - self.tags = tags - - def __eq__(self, other): - return (self.address == other.address and self.city == other.city - and self.state == other.state and self.tags == other.tags) - - class ComplexAddress(object): - - def __init__(self, address, address_tags, city, state, props): - self.address = address - self.address_tags = address_tags - self.city = city - self.state = state - self.props = props - - def __eq__(self, other): - return (self.address == other.address and self.address_tags == other.address_tags - and self.city == other.city and self.state == other.state - and self.props == other.props) - - class ComplexAddressWithOwners(object): - - def __init__(self, address, address_tags, city, state, props, owners): - self.address = address - self.address_tags = address_tags - self.city = city - self.state = state - self.props = props - self.owners = owners - - def __eq__(self, other): - return (self.address == other.address and self.address_tags == other.address_tags - and self.city == other.city and self.state == other.state - and self.props == other.props and self.owners == other.owners) - - self.__test_udt(schema, graphson, Address, AddressWithTags, ComplexAddress, ComplexAddressWithOwners) - - def _test_udt_with_namedtuples(self, schema, graphson): - AddressTuple = namedtuple('Address', ('address', 'city', 'state')) - AddressWithTagsTuple = namedtuple('AddressWithTags', ('address', 'city', 'state', 'tags')) - ComplexAddressTuple = namedtuple('ComplexAddress', ('address', 'address_tags', 'city', 'state', 'props')) - ComplexAddressWithOwnersTuple = namedtuple('ComplexAddressWithOwners', ('address', 'address_tags', 'city', - 'state', 'props', 'owners')) - - self.__test_udt(schema, graphson, AddressTuple, AddressWithTagsTuple, - ComplexAddressTuple, ComplexAddressWithOwnersTuple) - - def _write_and_read_data_types(self, schema, graphson, use_schema=True): - g = self.fetch_traversal_source(graphson) - ep = self.get_execution_profile(graphson) - for data in schema.fixtures.datatypes().values(): - typ, value, deserializer = data - vertex_label = VertexLabel([typ]) - property_name = next(iter(vertex_label.non_pk_properties.keys())) - if use_schema or schema is CoreGraphSchema: - schema.create_vertex_label(self.session, vertex_label, execution_profile=ep) - - write_traversal = g.addV(str(vertex_label.label)).property('pkid', vertex_label.id).\ - property(property_name, value) - self.execute_traversal(write_traversal, graphson) - - read_traversal = g.V().hasLabel(str(vertex_label.label)).has(property_name).properties() - results = self.execute_traversal(read_traversal, graphson) - - for result in results: - if result.label == 'pkid': - continue - self._check_equality(g, value, result.value) - - def __test_udt(self, schema, graphson, address_class, address_with_tags_class, - complex_address_class, complex_address_with_owners_class): - if schema is not CoreGraphSchema or DSE_VERSION < Version('6.8'): - raise unittest.SkipTest("Graph UDT is only supported with DSE 6.8+ and Core graphs.") - - ep = self.get_execution_profile(graphson) - - Address = address_class - AddressWithTags = address_with_tags_class - ComplexAddress = complex_address_class - ComplexAddressWithOwners = complex_address_with_owners_class - - # setup udt - self.session.execute_graph(""" - schema.type('address').property('address', Text).property('city', Text).property('state', Text).create(); - schema.type('addressTags').property('address', Text).property('city', Text).property('state', Text). - property('tags', setOf(Text)).create(); - schema.type('complexAddress').property('address', Text).property('address_tags', frozen(typeOf('addressTags'))). - property('city', Text).property('state', Text).property('props', mapOf(Text, Int)).create(); - schema.type('complexAddressWithOwners').property('address', Text). - property('address_tags', frozen(typeOf('addressTags'))). - property('city', Text).property('state', Text).property('props', mapOf(Text, Int)). - property('owners', frozen(listOf(tupleOf(Text, Int)))).create(); - """, execution_profile=ep) - - # wait max 10 seconds to get the UDT discovered. - wait_until_not_raised( - lambda: self.session.cluster.register_user_type(self.graph_name, 'address', Address), - 1, 10) - wait_until_not_raised( - lambda: self.session.cluster.register_user_type(self.graph_name, 'addressTags', AddressWithTags), - 1, 10) - wait_until_not_raised( - lambda: self.session.cluster.register_user_type(self.graph_name, 'complexAddress', ComplexAddress), - 1, 10) - wait_until_not_raised( - lambda: self.session.cluster.register_user_type(self.graph_name, 'complexAddressWithOwners', ComplexAddressWithOwners), - 1, 10) - - data = { - "udt1": ["typeOf('address')", Address('1440 Rd Smith', 'Quebec', 'QC')], - "udt2": ["tupleOf(typeOf('address'), Text)", (Address('1440 Rd Smith', 'Quebec', 'QC'), 'hello')], - "udt3": ["tupleOf(frozen(typeOf('address')), Text)", (Address('1440 Rd Smith', 'Quebec', 'QC'), 'hello')], - "udt4": ["tupleOf(tupleOf(Int, typeOf('address')), Text)", - ((42, Address('1440 Rd Smith', 'Quebec', 'QC')), 'hello')], - "udt5": ["tupleOf(tupleOf(Int, typeOf('addressTags')), Text)", - ((42, AddressWithTags('1440 Rd Smith', 'Quebec', 'QC', {'t1', 't2'})), 'hello')], - "udt6": ["tupleOf(tupleOf(Int, typeOf('complexAddress')), Text)", - ((42, ComplexAddress('1440 Rd Smith', - AddressWithTags('1440 Rd Smith', 'Quebec', 'QC', {'t1', 't2'}), - 'Quebec', 'QC', {'p1': 42, 'p2': 33})), 'hello')], - "udt7": ["tupleOf(tupleOf(Int, frozen(typeOf('complexAddressWithOwners'))), Text)", - ((42, ComplexAddressWithOwners( - '1440 Rd Smith', - AddressWithTags('1440 CRd Smith', 'Quebec', 'QC', {'t1', 't2'}), - 'Quebec', 'QC', {'p1': 42, 'p2': 33}, [('Mike', 43), ('Gina', 39)]) - ), 'hello')] - } - - g = self.fetch_traversal_source(graphson) - for typ, value in data.values(): - vertex_label = VertexLabel([typ]) - property_name = next(iter(vertex_label.non_pk_properties.keys())) - schema.create_vertex_label(self.session, vertex_label, execution_profile=ep) - - write_traversal = g.addV(str(vertex_label.label)).property('pkid', vertex_label.id). \ - property(property_name, value) - self.execute_traversal(write_traversal, graphson) - - #vertex = list(schema.add_vertex(self.session, vertex_label, property_name, value, execution_profile=ep))[0] - #vertex_properties = list(schema.get_vertex_properties( - # self.session, vertex, execution_profile=ep)) - - read_traversal = g.V().hasLabel(str(vertex_label.label)).has(property_name).properties() - vertex_properties = self.execute_traversal(read_traversal, graphson) - - self.assertEqual(len(vertex_properties), 2) # include pkid - for vp in vertex_properties: - if vp.label == 'pkid': - continue - - self.assertIsInstance(vp, (VertexProperty, TravVertexProperty)) - self.assertEqual(vp.label, property_name) - self.assertEqual(vp.value, value) - - @staticmethod - def fetch_edge_props(g, edge): - edge_props = g.E(edge.id).properties().toList() - return edge_props - - @staticmethod - def fetch_vertex_props(g, vertex): - - vertex_props = g.V(vertex.id).properties().toList() - return vertex_props - - def _check_equality(self, g, original, read_value): - return check_equality_base(self, original, read_value) - - -def _validate_prop(key, value, unittest): - if key == 'index': - return - - if any(key.startswith(t) for t in ('int', 'short')): - typ = int - - elif any(key.startswith(t) for t in ('long',)): - if sys.version_info >= (3, 0): - typ = int - else: - typ = long - elif any(key.startswith(t) for t in ('float', 'double')): - typ = float - elif any(key.startswith(t) for t in ('polygon',)): - typ = Polygon - elif any(key.startswith(t) for t in ('point',)): - typ = Point - elif any(key.startswith(t) for t in ('Linestring',)): - typ = LineString - elif any(key.startswith(t) for t in ('neg',)): - typ = str - elif any(key.startswith(t) for t in ('date',)): - typ = datetime.date - elif any(key.startswith(t) for t in ('time',)): - typ = datetime.time - else: - unittest.fail("Received unexpected type: %s" % key) - - -@requiredse -class BaseImplicitExecutionTest(GraphUnitTestCase): - """ - This test class will execute all tests of the AbstractTraversalTestClass using implicit execution - This all traversal will be run directly using toList() - """ - def setUp(self): - super(BaseImplicitExecutionTest, self).setUp() - if DSE_VERSION: - self.ep = DseGraph().create_execution_profile(self.graph_name) - self.cluster.add_execution_profile(self.graph_name, self.ep) - - @staticmethod - def fetch_key_from_prop(property): - return property.key - - def fetch_traversal_source(self, graphson, **kwargs): - ep = self.get_execution_profile(graphson, traversal=True) - return DseGraph().traversal_source(self.session, self.graph_name, execution_profile=ep, **kwargs) - - def execute_traversal(self, traversal, graphson=None): - return traversal.toList() - - def _validate_classic_vertex(self, g, vertex): - # Checks the properties on a classic vertex for correctness - vertex_props = self.fetch_vertex_props(g, vertex) - vertex_prop_keys = [vp.key for vp in vertex_props] - self.assertEqual(len(vertex_prop_keys), 2) - self.assertIn('name', vertex_prop_keys) - self.assertTrue('lang' in vertex_prop_keys or 'age' in vertex_prop_keys) - - def _validate_generic_vertex_result_type(self, g, vertex): - # Checks a vertex object for it's generic properties - properties = self.fetch_vertex_props(g, vertex) - for attr in ('id', 'label'): - self.assertIsNotNone(getattr(vertex, attr)) - self.assertTrue(len(properties) > 2) - - def _validate_classic_edge_properties(self, g, edge): - # Checks the properties on a classic edge for correctness - edge_props = self.fetch_edge_props(g, edge) - edge_prop_keys = [ep.key for ep in edge_props] - self.assertEqual(len(edge_prop_keys), 1) - self.assertIn('weight', edge_prop_keys) - - def _validate_classic_edge(self, g, edge): - self._validate_generic_edge_result_type(edge) - self._validate_classic_edge_properties(g, edge) - - def _validate_line_edge(self, g, edge): - self._validate_generic_edge_result_type(edge) - edge_props = self.fetch_edge_props(g, edge) - edge_prop_keys = [ep.key for ep in edge_props] - self.assertEqual(len(edge_prop_keys), 1) - self.assertIn('distance', edge_prop_keys) - - def _validate_generic_edge_result_type(self, edge): - self.assertIsInstance(edge, TravEdge) - - for attr in ('outV', 'inV', 'label', 'id'): - self.assertIsNotNone(getattr(edge, attr)) - - def _validate_path_result_type(self, g, objects_path): - for obj in objects_path: - if isinstance(obj, TravEdge): - self._validate_classic_edge(g, obj) - elif isinstance(obj, TravVertex): - self._validate_classic_vertex(g, obj) - else: - self.fail("Invalid object found in path " + str(obj.type)) - - def _validate_meta_property(self, g, vertex): - meta_props = g.V(vertex.id).properties().toList() - self.assertEqual(len(meta_props), 1) - meta_prop = meta_props[0] - self.assertEqual(meta_prop.value, "meta_prop") - self.assertEqual(meta_prop.key, "key") - - nested_props = g.V(vertex.id).properties().properties().toList() - self.assertEqual(len(nested_props), 2) - for nested_prop in nested_props: - self.assertTrue(nested_prop.key in ['k0', 'k1']) - self.assertTrue(nested_prop.value in ['v0', 'v1']) - - def _validate_type(self, g, vertex): - props = self.fetch_vertex_props(g, vertex) - for prop in props: - value = prop.value - key = prop.key - _validate_prop(key, value, self) - - -class BaseExplicitExecutionTest(GraphUnitTestCase): - - def fetch_traversal_source(self, graphson, **kwargs): - ep = self.get_execution_profile(graphson, traversal=True) - return DseGraph().traversal_source(self.session, self.graph_name, execution_profile=ep, **kwargs) - - def execute_traversal(self, traversal, graphson): - ep = self.get_execution_profile(graphson, traversal=True) - ep = self.session.get_execution_profile(ep) - context = None - if graphson == GraphProtocol.GRAPHSON_3_0: - context = { - 'cluster': self.cluster, - 'graph_name': ep.graph_options.graph_name.decode('utf-8') if ep.graph_options.graph_name else None - } - query = DseGraph.query_from_traversal(traversal, graphson, context=context) - # Use an ep that is configured with the correct row factory, and bytecode-json language flat set - result_set = self.execute_graph(query, graphson, traversal=True) - return list(result_set) diff --git a/tests/integration/advanced/graph/fluent/test_graph.py b/tests/integration/advanced/graph/fluent/test_graph.py deleted file mode 100644 index 911e6d5d57..0000000000 --- a/tests/integration/advanced/graph/fluent/test_graph.py +++ /dev/null @@ -1,241 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from cassandra import cluster -from cassandra.cluster import ContinuousPagingOptions -from cassandra.datastax.graph.fluent import DseGraph -from cassandra.graph import VertexProperty - -from tests.integration import greaterthanorequaldse68 -from tests.integration.advanced.graph import ( - GraphUnitTestCase, ClassicGraphSchema, CoreGraphSchema, - VertexLabel, GraphTestConfiguration -) -from tests.integration import greaterthanorequaldse60 -from tests.integration.advanced.graph.fluent import ( - BaseExplicitExecutionTest, create_traversal_profiles, check_equality_base) - -import unittest - - -@greaterthanorequaldse60 -@GraphTestConfiguration.generate_tests(traversal=True) -class BatchStatementTests(BaseExplicitExecutionTest): - - def setUp(self): - super(BatchStatementTests, self).setUp() - self.ep_graphson2, self.ep_graphson3 = create_traversal_profiles(self.cluster, self.graph_name) - - def _test_batch_with_schema(self, schema, graphson): - """ - Sends a Batch statement and verifies it has succeeded with a schema created - - @since 1.1.0 - @jira_ticket PYTHON-789 - @expected_result ValueError is arisen - - @test_category dse graph - """ - self._send_batch_and_read_results(schema, graphson) - - def _test_batch_without_schema(self, schema, graphson): - """ - Sends a Batch statement and verifies it has succeeded without a schema created - - @since 1.1.0 - @jira_ticket PYTHON-789 - @expected_result ValueError is arisen - - @test_category dse graph - """ - if schema is not ClassicGraphSchema: - raise unittest.SkipTest('schema-less is only for classic graphs') - self._send_batch_and_read_results(schema, graphson, use_schema=False) - - def _test_batch_with_schema_add_all(self, schema, graphson): - """ - Sends a Batch statement and verifies it has succeeded with a schema created. - Uses :method:`dse_graph.query._BatchGraphStatement.add_all` to add the statements - instead of :method:`dse_graph.query._BatchGraphStatement.add` - - @since 1.1.0 - @jira_ticket PYTHON-789 - @expected_result ValueError is arisen - - @test_category dse graph - """ - self._send_batch_and_read_results(schema, graphson, add_all=True) - - def _test_batch_without_schema_add_all(self, schema, graphson): - """ - Sends a Batch statement and verifies it has succeeded without a schema created - Uses :method:`dse_graph.query._BatchGraphStatement.add_all` to add the statements - instead of :method:`dse_graph.query._BatchGraphStatement.add` - - @since 1.1.0 - @jira_ticket PYTHON-789 - @expected_result ValueError is arisen - - @test_category dse graph - """ - if schema is not ClassicGraphSchema: - raise unittest.SkipTest('schema-less is only for classic graphs') - self._send_batch_and_read_results(schema, graphson, add_all=True, use_schema=False) - - def test_only_graph_traversals_are_accepted(self): - """ - Verifies that ValueError is risen if the parameter add is not a traversal - - @since 1.1.0 - @jira_ticket PYTHON-789 - @expected_result ValueError is arisen - - @test_category dse graph - """ - batch = DseGraph.batch() - self.assertRaises(ValueError, batch.add, '{"@value":{"step":[["addV","poc_int"],' - '["property","bigint1value",{"@value":12,"@type":"g:Int32"}]]},' - '"@type":"g:Bytecode"}') - another_batch = DseGraph.batch() - self.assertRaises(ValueError, batch.add, another_batch) - - def _send_batch_and_read_results(self, schema, graphson, add_all=False, use_schema=True): - traversals = [] - datatypes = schema.fixtures.datatypes() - values = {} - g = self.fetch_traversal_source(graphson) - ep = self.get_execution_profile(graphson) - batch = DseGraph.batch(session=self.session, - execution_profile=self.get_execution_profile(graphson, traversal=True)) - for data in datatypes.values(): - typ, value, deserializer = data - vertex_label = VertexLabel([typ]) - property_name = next(iter(vertex_label.non_pk_properties.keys())) - values[property_name] = value - if use_schema or schema is CoreGraphSchema: - schema.create_vertex_label(self.session, vertex_label, execution_profile=ep) - - traversal = g.addV(str(vertex_label.label)).property('pkid', vertex_label.id).property(property_name, value) - if not add_all: - batch.add(traversal) - traversals.append(traversal) - - if add_all: - batch.add_all(traversals) - - self.assertEqual(len(datatypes), len(batch)) - - batch.execute() - - vertices = self.execute_traversal(g.V(), graphson) - self.assertEqual(len(vertices), len(datatypes), "g.V() returned {}".format(vertices)) - - # Iterate over all the vertices and check that they match the original input - for vertex in vertices: - schema.ensure_properties(self.session, vertex, execution_profile=ep) - key = [k for k in list(vertex.properties.keys()) if k != 'pkid'][0].replace("value", "") - original = values[key] - self._check_equality(original, vertex) - - def _check_equality(self, original, vertex): - for key in vertex.properties: - if key == 'pkid': - continue - value = vertex.properties[key].value \ - if isinstance(vertex.properties[key], VertexProperty) else vertex.properties[key][0].value - check_equality_base(self, original, value) - - -class ContinuousPagingOptionsForTests(ContinuousPagingOptions): - def __init__(self, - page_unit=ContinuousPagingOptions.PagingUnit.ROWS, max_pages=1, # max_pages=1 - max_pages_per_second=0, max_queue_size=4): - super(ContinuousPagingOptionsForTests, self).__init__(page_unit, max_pages, max_pages_per_second, - max_queue_size) - - -def reset_paging_options(): - cluster.ContinuousPagingOptions = ContinuousPagingOptions - - -@greaterthanorequaldse68 -@GraphTestConfiguration.generate_tests(schema=CoreGraphSchema) -class GraphPagingTest(GraphUnitTestCase): - - def setUp(self): - super(GraphPagingTest, self).setUp() - self.addCleanup(reset_paging_options) - self.ep_graphson2, self.ep_graphson3 = create_traversal_profiles(self.cluster, self.graph_name) - - def _setup_data(self, schema, graphson): - self.execute_graph( - "schema.vertexLabel('person').ifNotExists().partitionBy('name', Text).property('age', Int).create();", - graphson) - for i in range(100): - self.execute_graph("g.addV('person').property('name', 'batman-{}')".format(i), graphson) - - def _test_cont_paging_is_enabled_by_default(self, schema, graphson): - """ - Test that graph paging is automatically enabled with a >=6.8 cluster. - - @jira_ticket PYTHON-1045 - @expected_result the default continuous paging options are used - - @test_category dse graph - """ - # with traversals... I don't have access to the response future... so this is a hack to ensure paging is on - cluster.ContinuousPagingOptions = ContinuousPagingOptionsForTests - ep = self.get_execution_profile(graphson, traversal=True) - self._setup_data(schema, graphson) - self.session.default_fetch_size = 10 - g = DseGraph.traversal_source(self.session, execution_profile=ep) - results = g.V().toList() - self.assertEqual(len(results), 10) # only 10 results due to our hack - - def _test_cont_paging_can_be_disabled(self, schema, graphson): - """ - Test that graph paging can be disabled. - - @jira_ticket PYTHON-1045 - @expected_result the default continuous paging options are not used - - @test_category dse graph - """ - # with traversals... I don't have access to the response future... so this is a hack to ensure paging is on - cluster.ContinuousPagingOptions = ContinuousPagingOptionsForTests - ep = self.get_execution_profile(graphson, traversal=True) - ep = self.session.execution_profile_clone_update(ep, continuous_paging_options=None) - self._setup_data(schema, graphson) - self.session.default_fetch_size = 10 - g = DseGraph.traversal_source(self.session, execution_profile=ep) - results = g.V().toList() - self.assertEqual(len(results), 100) # 100 results since paging is disabled - - def _test_cont_paging_with_custom_options(self, schema, graphson): - """ - Test that we can specify custom paging options. - - @jira_ticket PYTHON-1045 - @expected_result we get only the desired number of results - - @test_category dse graph - """ - ep = self.get_execution_profile(graphson, traversal=True) - ep = self.session.execution_profile_clone_update(ep, - continuous_paging_options=ContinuousPagingOptions(max_pages=1)) - self._setup_data(schema, graphson) - self.session.default_fetch_size = 10 - g = DseGraph.traversal_source(self.session, execution_profile=ep) - results = g.V().toList() - self.assertEqual(len(results), 10) # only 10 results since paging is disabled diff --git a/tests/integration/advanced/graph/fluent/test_graph_explicit_execution.py b/tests/integration/advanced/graph/fluent/test_graph_explicit_execution.py deleted file mode 100644 index 1a5846203d..0000000000 --- a/tests/integration/advanced/graph/fluent/test_graph_explicit_execution.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from cassandra.graph import Vertex, Edge - -from tests.integration.advanced.graph import ( - validate_classic_vertex, validate_classic_edge, validate_generic_vertex_result_type, - validate_classic_edge_properties, validate_line_edge, - validate_generic_edge_result_type, validate_path_result_type) - -from tests.integration import requiredse, DSE_VERSION -from tests.integration.advanced import use_single_node_with_graph -from tests.integration.advanced.graph import GraphTestConfiguration -from tests.integration.advanced.graph.fluent import ( - BaseExplicitExecutionTest, _AbstractTraversalTest, _validate_prop) - - -def setup_module(): - if DSE_VERSION: - dse_options = {'graph': {'realtime_evaluation_timeout_in_seconds': 60}} - use_single_node_with_graph(dse_options=dse_options) - - -@requiredse -@GraphTestConfiguration.generate_tests(traversal=True) -class ExplicitExecutionTest(BaseExplicitExecutionTest, _AbstractTraversalTest): - """ - This test class will execute all tests of the AbstractTraversalTestClass using Explicit execution - All queries will be run by converting them to byte code, and calling execute graph explicitly with a generated ep. - """ - @staticmethod - def fetch_key_from_prop(property): - return property.label - - def _validate_classic_vertex(self, g, vertex): - validate_classic_vertex(self, vertex) - - def _validate_generic_vertex_result_type(self, g, vertex): - validate_generic_vertex_result_type(self, vertex) - - def _validate_classic_edge_properties(self, g, edge): - validate_classic_edge_properties(self, edge) - - def _validate_classic_edge(self, g, edge): - validate_classic_edge(self, edge) - - def _validate_line_edge(self, g, edge): - validate_line_edge(self, edge) - - def _validate_generic_edge_result_type(self, edge): - validate_generic_edge_result_type(self, edge) - - def _validate_type(self, g, vertex): - for key in vertex.properties: - value = vertex.properties[key][0].value - _validate_prop(key, value, self) - - def _validate_path_result_type(self, g, path_obj): - # This pre-processing is due to a change in TinkerPop - # properties are not returned automatically anymore - # with some queries. - for obj in path_obj.objects: - if not obj.properties: - props = [] - if isinstance(obj, Edge): - obj.properties = { - p.key: p.value - for p in self.fetch_edge_props(g, obj) - } - elif isinstance(obj, Vertex): - obj.properties = { - p.label: p.value - for p in self.fetch_vertex_props(g, obj) - } - - validate_path_result_type(self, path_obj) - - def _validate_meta_property(self, g, vertex): - - self.assertEqual(len(vertex.properties), 1) - self.assertEqual(len(vertex.properties['key']), 1) - p = vertex.properties['key'][0] - self.assertEqual(p.label, 'key') - self.assertEqual(p.value, 'meta_prop') - self.assertEqual(p.properties, {'k0': 'v0', 'k1': 'v1'}) diff --git a/tests/integration/advanced/graph/fluent/test_graph_implicit_execution.py b/tests/integration/advanced/graph/fluent/test_graph_implicit_execution.py deleted file mode 100644 index 50e6795867..0000000000 --- a/tests/integration/advanced/graph/fluent/test_graph_implicit_execution.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from concurrent.futures import Future -from cassandra.datastax.graph.fluent import DseGraph - -from tests.integration import requiredse, DSE_VERSION -from tests.integration.advanced import use_single_node_with_graph -from tests.integration.advanced.graph import GraphTestConfiguration -from tests.integration.advanced.graph.fluent import ( - BaseImplicitExecutionTest, create_traversal_profiles, _AbstractTraversalTest) - - -def setup_module(): - if DSE_VERSION: - dse_options = {'graph': {'realtime_evaluation_timeout_in_seconds': 60}} - use_single_node_with_graph(dse_options=dse_options) - - -@requiredse -@GraphTestConfiguration.generate_tests(traversal=True) -class ImplicitExecutionTest(BaseImplicitExecutionTest, _AbstractTraversalTest): - def _test_iterate_step(self, schema, graphson): - """ - Test to validate that the iterate() step work on all dse versions. - @jira_ticket PYTHON-1155 - @expected_result iterate step works - @test_category dse graph - """ - - g = self.fetch_traversal_source(graphson) - self.execute_graph(schema.fixtures.classic(), graphson) - g.addV('person').property('name', 'Person1').iterate() - - -@requiredse -@GraphTestConfiguration.generate_tests(traversal=True) -class ImplicitAsyncExecutionTest(BaseImplicitExecutionTest): - """ - Test to validate that the traversal async execution works properly. - - @since 3.21.0 - @jira_ticket PYTHON-1129 - - @test_category dse graph - """ - - def setUp(self): - super(ImplicitAsyncExecutionTest, self).setUp() - self.ep_graphson2, self.ep_graphson3 = create_traversal_profiles(self.cluster, self.graph_name) - - def _validate_results(self, results): - results = list(results) - self.assertEqual(len(results), 2) - self.assertIn('vadas', results) - self.assertIn('josh', results) - - def _test_promise(self, schema, graphson): - self.execute_graph(schema.fixtures.classic(), graphson) - g = self.fetch_traversal_source(graphson) - traversal_future = g.V().has('name', 'marko').out('knows').values('name').promise() - self._validate_results(traversal_future.result()) - - def _test_promise_error_is_propagated(self, schema, graphson): - self.execute_graph(schema.fixtures.classic(), graphson) - g = DseGraph().traversal_source(self.session, 'wrong_graph', execution_profile=self.ep) - traversal_future = g.V().has('name', 'marko').out('knows').values('name').promise() - with self.assertRaises(Exception): - traversal_future.result() - - def _test_promise_callback(self, schema, graphson): - self.execute_graph(schema.fixtures.classic(), graphson) - g = self.fetch_traversal_source(graphson) - future = Future() - - def cb(f): - future.set_result(f.result()) - - traversal_future = g.V().has('name', 'marko').out('knows').values('name').promise() - traversal_future.add_done_callback(cb) - self._validate_results(future.result()) - - def _test_promise_callback_on_error(self, schema, graphson): - self.execute_graph(schema.fixtures.classic(), graphson) - g = DseGraph().traversal_source(self.session, 'wrong_graph', execution_profile=self.ep) - future = Future() - - def cb(f): - try: - f.result() - except Exception as e: - future.set_exception(e) - - traversal_future = g.V().has('name', 'marko').out('knows').values('name').promise() - traversal_future.add_done_callback(cb) - with self.assertRaises(Exception): - future.result() diff --git a/tests/integration/advanced/graph/fluent/test_search.py b/tests/integration/advanced/graph/fluent/test_search.py deleted file mode 100644 index d50016d576..0000000000 --- a/tests/integration/advanced/graph/fluent/test_search.py +++ /dev/null @@ -1,539 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from cassandra.util import Distance -from cassandra import InvalidRequest -from cassandra.graph import GraphProtocol -from cassandra.datastax.graph.fluent import DseGraph -from cassandra.datastax.graph.fluent.predicates import Search, Geo, GeoUnit, CqlCollection - -from tests.integration.advanced import use_single_node_with_graph_and_solr -from tests.integration.advanced.graph import GraphUnitTestCase, CoreGraphSchema, ClassicGraphSchema, GraphTestConfiguration -from tests.integration import greaterthanorequaldse51, DSE_VERSION, requiredse - - -def setup_module(): - if DSE_VERSION: - use_single_node_with_graph_and_solr() - - -class AbstractSearchTest(GraphUnitTestCase): - - def setUp(self): - super(AbstractSearchTest, self).setUp() - self.ep_graphson2 = DseGraph().create_execution_profile(self.graph_name, - graph_protocol=GraphProtocol.GRAPHSON_2_0) - self.ep_graphson3 = DseGraph().create_execution_profile(self.graph_name, - graph_protocol=GraphProtocol.GRAPHSON_3_0) - - self.cluster.add_execution_profile('traversal_graphson2', self.ep_graphson2) - self.cluster.add_execution_profile('traversal_graphson3', self.ep_graphson3) - - def fetch_traversal_source(self, graphson): - ep = self.get_execution_profile(graphson, traversal=True) - return DseGraph().traversal_source(self.session, self.graph_name, execution_profile=ep) - - def _test_search_by_prefix(self, schema, graphson): - """ - Test to validate that solr searches by prefix function. - - @since 1.0.0 - @jira_ticket PYTHON-660 - @expected_result all names starting with Paul should be returned - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.address_book(), graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.V().has("person", "name", Search.prefix("Paul")).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 1) - self.assertEqual(results_list[0], "Paul Thomas Joe") - - def _test_search_by_regex(self, schema, graphson): - """ - Test to validate that solr searches by regex function. - - @since 1.0.0 - @jira_ticket PYTHON-660 - @expected_result all names containing Paul should be returned - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.address_book(), graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.V().has("person", "name", Search.regex(".*Paul.*")).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 2) - self.assertIn("Paul Thomas Joe", results_list) - self.assertIn("James Paul Smith", results_list) - - def _test_search_by_token(self, schema, graphson): - """ - Test to validate that solr searches by token. - - @since 1.0.0 - @jira_ticket PYTHON-660 - @expected_result all names with description containing could shoud be returned - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.address_book(), graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.V().has("person", "description", Search.token("cold")).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 2) - self.assertIn("Jill Alice", results_list) - self.assertIn("George Bill Steve", results_list) - - def _test_search_by_token_prefix(self, schema, graphson): - """ - Test to validate that solr searches by token prefix. - - @since 1.0.0 - @jira_ticket PYTHON-660 - @expected_result all names with description containing a token starting with h are returned - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.address_book(), graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.V().has("person", "description", Search.token_prefix("h")).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 2) - self.assertIn("Paul Thomas Joe", results_list) - self.assertIn( "James Paul Smith", results_list) - - def _test_search_by_token_regex(self, schema, graphson): - """ - Test to validate that solr searches by token regex. - - @since 1.0.0 - @jira_ticket PYTHON-660 - @expected_result all names with description containing nice or hospital are returned - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.address_book(), graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.V().has("person", "description", Search.token_regex("(nice|hospital)")).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 2) - self.assertIn("Paul Thomas Joe", results_list ) - self.assertIn( "Jill Alice", results_list ) - - def _assert_in_distance(self, schema, graphson, inside, names): - """ - Helper function that asserts that an exception is arisen if geodetic predicates are used - in cartesian geometry. Also asserts that the expected list is equal to the returned from - the transversal using different search indexes. - """ - def assert_equal_list(L1, L2): - return len(L1) == len(L2) and sorted(L1) == sorted(L2) - - self.execute_graph(schema.fixtures.address_book(), graphson) - g = self.fetch_traversal_source(graphson) - - traversal = g.V().has("person", "pointPropWithBoundsWithSearchIndex", inside).values("name") - if schema is ClassicGraphSchema: - # throws an exception because of a SOLR/Search limitation in the indexing process - # may be resolved in the future - self.assertRaises(InvalidRequest, self.execute_traversal, traversal, graphson) - else: - traversal = g.V().has("person", "pointPropWithBoundsWithSearchIndex", inside).values("name") - results_list = self.execute_traversal(traversal, graphson) - assert_equal_list(names, results_list) - - traversal = g.V().has("person", "pointPropWithBounds", inside).values("name") - results_list = self.execute_traversal(traversal, graphson) - assert_equal_list(names, results_list) - - traversal = g.V().has("person", "pointPropWithGeoBoundsWithSearchIndex", inside).values("name") - results_list = self.execute_traversal(traversal, graphson) - assert_equal_list(names, results_list) - - traversal = g.V().has("person", "pointPropWithGeoBounds", inside).values("name") - results_list = self.execute_traversal(traversal, graphson) - assert_equal_list(names, results_list) - - @greaterthanorequaldse51 - def _test_search_by_distance(self, schema, graphson): - """ - Test to validate that solr searches by distance. - - @since 1.0.0 - @jira_ticket PYTHON-660 - @expected_result all names with a geo location within a 2 degree distance of -92,44 are returned - - @test_category dse graph - """ - self._assert_in_distance(schema, graphson, - Geo.inside(Distance(-92, 44, 2)), - ["Paul Thomas Joe", "George Bill Steve"] - ) - - @greaterthanorequaldse51 - def _test_search_by_distance_meters_units(self, schema, graphson): - """ - Test to validate that solr searches by distance. - - @since 2.0.0 - @jira_ticket PYTHON-698 - @expected_result all names with a geo location within a 56k-meter radius of -92,44 are returned - - @test_category dse graph - """ - self._assert_in_distance(schema, graphson, - Geo.inside(Distance(-92, 44, 56000), GeoUnit.METERS), - ["Paul Thomas Joe"] - ) - - @greaterthanorequaldse51 - def _test_search_by_distance_miles_units(self, schema, graphson): - """ - Test to validate that solr searches by distance. - - @since 2.0.0 - @jira_ticket PYTHON-698 - @expected_result all names with a geo location within a 70-mile radius of -92,44 are returned - - @test_category dse graph - """ - self._assert_in_distance(schema, graphson, - Geo.inside(Distance(-92, 44, 70), GeoUnit.MILES), - ["Paul Thomas Joe", "George Bill Steve"] - ) - - @greaterthanorequaldse51 - def _test_search_by_distance_check_limit(self, schema, graphson): - """ - Test to validate that solr searches by distance using several units. It will also validate - that and exception is arisen if geodetic predicates are used against cartesian geometry - - @since 2.0.0 - @jira_ticket PYTHON-698 - @expected_result if the search distance is below the real distance only one - name will be in the list, otherwise, two - - @test_category dse graph - """ - # Paul Thomas Joe and George Bill Steve are 64.6923761881464 km apart - self._assert_in_distance(schema, graphson, - Geo.inside(Distance(-92.46295, 44.0234, 65), GeoUnit.KILOMETERS), - ["George Bill Steve", "Paul Thomas Joe"] - ) - - self._assert_in_distance(schema, graphson, - Geo.inside(Distance(-92.46295, 44.0234, 64), GeoUnit.KILOMETERS), - ["Paul Thomas Joe"] - ) - - # Paul Thomas Joe and George Bill Steve are 40.19797892069464 miles apart - self._assert_in_distance(schema, graphson, - Geo.inside(Distance(-92.46295, 44.0234, 41), GeoUnit.MILES), - ["George Bill Steve", "Paul Thomas Joe"] - ) - - self._assert_in_distance(schema, graphson, - Geo.inside(Distance(-92.46295, 44.0234, 40), GeoUnit.MILES), - ["Paul Thomas Joe"] - ) - - @greaterthanorequaldse51 - def _test_search_by_fuzzy(self, schema, graphson): - """ - Test to validate that solr searches by distance. - - @since 1.0.0 - @jira_ticket PYTHON-664 - @expected_result all names with a geo location within a 2 radius distance of -92,44 are returned - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.address_book(), graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.V().has("person", "name", Search.fuzzy("Paul Thamas Joe", 1)).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 1) - self.assertIn("Paul Thomas Joe", results_list) - - traversal = g.V().has("person", "name", Search.fuzzy("Paul Thames Joe", 1)).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 0) - - @greaterthanorequaldse51 - def _test_search_by_fuzzy_token(self, schema, graphson): - """ - Test to validate that fuzzy searches. - - @since 1.0.0 - @jira_ticket PYTHON-664 - @expected_result all names with that differ from the search criteria by one letter should be returned - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.address_book(), graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.V().has("person", "description", Search.token_fuzzy("lives", 1)).values("name") - # Should match 'Paul Thomas Joe' since description contains 'Lives' - # Should match 'James Paul Joe' since description contains 'Likes' - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 2) - self.assertIn("Paul Thomas Joe", results_list) - self.assertIn("James Paul Smith", results_list) - - traversal = g.V().has("person", "description", Search.token_fuzzy("loues", 1)).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 0) - - @greaterthanorequaldse51 - def _test_search_by_phrase(self, schema, graphson): - """ - Test to validate that phrase searches. - - @since 1.0.0 - @jira_ticket PYTHON-664 - @expected_result all names with that differ from the search phrase criteria by two letter should be returned - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.address_book(), graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.V().has("person", "description", Search.phrase("a cold", 2)).values("name") - #Should match 'George Bill Steve' since 'A cold dude' is at distance of 0 for 'a cold'. - #Should match 'Jill Alice' since 'Enjoys a very nice cold coca cola' is at distance of 2 for 'a cold'. - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 2) - self.assertIn('George Bill Steve', results_list) - self.assertIn('Jill Alice', results_list) - - traversal = g.V().has("person", "description", Search.phrase("a bald", 2)).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 0) - - -@requiredse -@GraphTestConfiguration.generate_tests(traversal=True) -class ImplicitSearchTest(AbstractSearchTest): - """ - This test class will execute all tests of the AbstractSearchTest using implicit execution - All traversals will be run directly using toList() - """ - def fetch_key_from_prop(self, property): - return property.key - - def execute_traversal(self, traversal, graphson=None): - return traversal.toList() - - -@requiredse -@GraphTestConfiguration.generate_tests(traversal=True) -class ExplicitSearchTest(AbstractSearchTest): - """ - This test class will execute all tests of the AbstractSearchTest using implicit execution - All traversals will be converted to byte code then they will be executed explicitly. - """ - - def execute_traversal(self, traversal, graphson): - ep = self.get_execution_profile(graphson, traversal=True) - ep = self.session.get_execution_profile(ep) - context = None - if graphson == GraphProtocol.GRAPHSON_3_0: - context = { - 'cluster': self.cluster, - 'graph_name': ep.graph_options.graph_name.decode('utf-8') if ep.graph_options.graph_name else None - } - query = DseGraph.query_from_traversal(traversal, graphson, context=context) - #Use an ep that is configured with the correct row factory, and bytecode-json language flat set - result_set = self.execute_graph(query, graphson, traversal=True) - return list(result_set) - - -@requiredse -class BaseCqlCollectionPredicatesTest(GraphUnitTestCase): - - def setUp(self): - super(BaseCqlCollectionPredicatesTest, self).setUp() - self.ep_graphson3 = DseGraph().create_execution_profile(self.graph_name, - graph_protocol=GraphProtocol.GRAPHSON_3_0) - self.cluster.add_execution_profile('traversal_graphson3', self.ep_graphson3) - - def fetch_traversal_source(self, graphson): - ep = self.get_execution_profile(graphson, traversal=True) - return DseGraph().traversal_source(self.session, self.graph_name, execution_profile=ep) - - def setup_vertex_label(self, graphson): - ep = self.get_execution_profile(graphson) - self.session.execute_graph(""" - schema.vertexLabel('cqlcollections').ifNotExists().partitionBy('name', Varchar) - .property('list', listOf(Text)) - .property('frozen_list', frozen(listOf(Text))) - .property('set', setOf(Text)) - .property('frozen_set', frozen(setOf(Text))) - .property('map_keys', mapOf(Int, Text)) - .property('map_values', mapOf(Int, Text)) - .property('map_entries', mapOf(Int, Text)) - .property('frozen_map', frozen(mapOf(Int, Text))) - .create() - """, execution_profile=ep) - - self.session.execute_graph(""" - schema.vertexLabel('cqlcollections').secondaryIndex('list').by('list').create(); - schema.vertexLabel('cqlcollections').secondaryIndex('frozen_list').by('frozen_list').indexFull().create(); - schema.vertexLabel('cqlcollections').secondaryIndex('set').by('set').create(); - schema.vertexLabel('cqlcollections').secondaryIndex('frozen_set').by('frozen_set').indexFull().create(); - schema.vertexLabel('cqlcollections').secondaryIndex('map_keys').by('map_keys').indexKeys().create(); - schema.vertexLabel('cqlcollections').secondaryIndex('map_values').by('map_values').indexValues().create(); - schema.vertexLabel('cqlcollections').secondaryIndex('map_entries').by('map_entries').indexEntries().create(); - schema.vertexLabel('cqlcollections').secondaryIndex('frozen_map').by('frozen_map').indexFull().create(); - """, execution_profile=ep) - - def _test_contains_list(self, schema, graphson): - """ - Test to validate that the cql predicate contains works with list - - @since TODO dse 6.8 - @jira_ticket PYTHON-1039 - @expected_result contains predicate work on a list - - @test_category dse graph - """ - self.setup_vertex_label(graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.addV("cqlcollections").property("name", "list1").property("list", ['item1', 'item2']) - self.execute_traversal(traversal, graphson) - traversal = g.addV("cqlcollections").property("name", "list2").property("list", ['item3', 'item4']) - self.execute_traversal(traversal, graphson) - traversal = g.V().has("cqlcollections", "list", CqlCollection.contains("item1")).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 1) - self.assertIn("list1", results_list) - - def _test_contains_set(self, schema, graphson): - """ - Test to validate that the cql predicate contains works with set - - @since TODO dse 6.8 - @jira_ticket PYTHON-1039 - @expected_result contains predicate work on a set - - @test_category dse graph - """ - self.setup_vertex_label(graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.addV("cqlcollections").property("name", "set1").property("set", {'item1', 'item2'}) - self.execute_traversal(traversal, graphson) - traversal = g.addV("cqlcollections").property("name", "set2").property("set", {'item3', 'item4'}) - self.execute_traversal(traversal, graphson) - traversal = g.V().has("cqlcollections", "set", CqlCollection.contains("item1")).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 1) - self.assertIn("set1", results_list) - - def _test_contains_key_map(self, schema, graphson): - """ - Test to validate that the cql predicate contains_key works with map - - @since TODO dse 6.8 - @jira_ticket PYTHON-1039 - @expected_result contains_key predicate work on a map - - @test_category dse graph - """ - self.setup_vertex_label(graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.addV("cqlcollections").property("name", "map1").property("map_keys", {0: 'item1', 1: 'item2'}) - self.execute_traversal(traversal, graphson) - traversal = g.addV("cqlcollections").property("name", "map2").property("map_keys", {2: 'item3', 3: 'item4'}) - self.execute_traversal(traversal, graphson) - traversal = g.V().has("cqlcollections", "map_keys", CqlCollection.contains_key(0)).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 1) - self.assertIn("map1", results_list) - - def _test_contains_value_map(self, schema, graphson): - """ - Test to validate that the cql predicate contains_value works with map - - @since TODO dse 6.8 - @jira_ticket PYTHON-1039 - @expected_result contains_value predicate work on a map - - @test_category dse graph - """ - self.setup_vertex_label(graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.addV("cqlcollections").property("name", "map1").property("map_values", {0: 'item1', 1: 'item2'}) - self.execute_traversal(traversal, graphson) - traversal = g.addV("cqlcollections").property("name", "map2").property("map_values", {2: 'item3', 3: 'item4'}) - self.execute_traversal(traversal, graphson) - traversal = g.V().has("cqlcollections", "map_values", CqlCollection.contains_value('item3')).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 1) - self.assertIn("map2", results_list) - - def _test_entry_eq_map(self, schema, graphson): - """ - Test to validate that the cql predicate entry_eq works with map - - @since TODO dse 6.8 - @jira_ticket PYTHON-1039 - @expected_result entry_eq predicate work on a map - - @test_category dse graph - """ - self.setup_vertex_label(graphson) - g = self.fetch_traversal_source(graphson) - traversal = g.addV("cqlcollections").property("name", "map1").property("map_entries", {0: 'item1', 1: 'item2'}) - self.execute_traversal(traversal, graphson) - traversal = g.addV("cqlcollections").property("name", "map2").property("map_entries", {2: 'item3', 3: 'item4'}) - self.execute_traversal(traversal, graphson) - traversal = g.V().has("cqlcollections", "map_entries", CqlCollection.entry_eq([2, 'item3'])).values("name") - results_list = self.execute_traversal(traversal, graphson) - self.assertEqual(len(results_list), 1) - self.assertIn("map2", results_list) - - -@requiredse -@GraphTestConfiguration.generate_tests(traversal=True, schema=CoreGraphSchema) -class ImplicitCqlCollectionPredicatesTest(BaseCqlCollectionPredicatesTest): - """ - This test class will execute all tests of the BaseCqlCollectionTest using implicit execution - All traversals will be run directly using toList() - """ - - def execute_traversal(self, traversal, graphson=None): - return traversal.toList() - - -@requiredse -@GraphTestConfiguration.generate_tests(traversal=True, schema=CoreGraphSchema) -class ExplicitCqlCollectionPredicatesTest(BaseCqlCollectionPredicatesTest): - """ - This test class will execute all tests of the AbstractSearchTest using implicit execution - All traversals will be converted to byte code then they will be executed explicitly. - """ - - def execute_traversal(self, traversal, graphson): - ep = self.get_execution_profile(graphson, traversal=True) - ep = self.session.get_execution_profile(ep) - context = None - if graphson == GraphProtocol.GRAPHSON_3_0: - context = { - 'cluster': self.cluster, - 'graph_name': ep.graph_options.graph_name.decode('utf-8') if ep.graph_options.graph_name else None - } - query = DseGraph.query_from_traversal(traversal, graphson, context=context) - result_set = self.execute_graph(query, graphson, traversal=True) - return list(result_set) diff --git a/tests/integration/advanced/graph/test_graph.py b/tests/integration/advanced/graph/test_graph.py deleted file mode 100644 index 7f55229911..0000000000 --- a/tests/integration/advanced/graph/test_graph.py +++ /dev/null @@ -1,270 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import re - -from cassandra import OperationTimedOut, InvalidRequest -from cassandra.protocol import SyntaxException -from cassandra.policies import WhiteListRoundRobinPolicy -from cassandra.cluster import NoHostAvailable -from cassandra.cluster import EXEC_PROFILE_GRAPH_DEFAULT, GraphExecutionProfile -from cassandra.graph import single_object_row_factory, Vertex, graph_object_row_factory, \ - graph_graphson2_row_factory, graph_graphson3_row_factory -from cassandra.util import SortedSet - -from tests.integration import DSE_VERSION, greaterthanorequaldse51, greaterthanorequaldse68, \ - requiredse, TestCluster -from tests.integration.advanced.graph import BasicGraphUnitTestCase, GraphUnitTestCase, \ - GraphProtocol, ClassicGraphSchema, CoreGraphSchema, use_single_node_with_graph - - -def setup_module(): - if DSE_VERSION: - dse_options = {'graph': {'realtime_evaluation_timeout_in_seconds': 60}} - use_single_node_with_graph(dse_options=dse_options) - - -@requiredse -class GraphTimeoutTests(BasicGraphUnitTestCase): - - def test_should_wait_indefinitely_by_default(self): - """ - Tests that by default the client should wait indefinitely for server timeouts - - @since 1.0.0 - @jira_ticket PYTHON-589 - - @test_category dse graph - """ - desired_timeout = 1000 - - graph_source = "test_timeout_1" - ep_name = graph_source - ep = self.session.execution_profile_clone_update(EXEC_PROFILE_GRAPH_DEFAULT) - ep.graph_options = ep.graph_options.copy() - ep.graph_options.graph_source = graph_source - self.cluster.add_execution_profile(ep_name, ep) - - to_run = '''graph.schema().config().option("graph.traversal_sources.{0}.evaluation_timeout").set('{1} ms')'''.format( - graph_source, desired_timeout) - self.session.execute_graph(to_run, execution_profile=ep_name) - with self.assertRaises(InvalidRequest) as ir: - self.session.execute_graph("java.util.concurrent.TimeUnit.MILLISECONDS.sleep(35000L);1+1", - execution_profile=ep_name) - self.assertTrue("evaluation exceeded the configured threshold of 1000" in str(ir.exception) or - "evaluation exceeded the configured threshold of evaluation_timeout at 1000" in str( - ir.exception)) - - def test_request_timeout_less_then_server(self): - """ - Tests that with explicit request_timeouts set, that a server timeout is honored if it's relieved prior to the - client timeout - - @since 1.0.0 - @jira_ticket PYTHON-589 - - @test_category dse graph - """ - desired_timeout = 1000 - graph_source = "test_timeout_2" - ep_name = graph_source - ep = self.session.execution_profile_clone_update(EXEC_PROFILE_GRAPH_DEFAULT, request_timeout=32) - ep.graph_options = ep.graph_options.copy() - ep.graph_options.graph_source = graph_source - self.cluster.add_execution_profile(ep_name, ep) - - to_run = '''graph.schema().config().option("graph.traversal_sources.{0}.evaluation_timeout").set('{1} ms')'''.format( - graph_source, desired_timeout) - self.session.execute_graph(to_run, execution_profile=ep_name) - with self.assertRaises(InvalidRequest) as ir: - self.session.execute_graph("java.util.concurrent.TimeUnit.MILLISECONDS.sleep(35000L);1+1", - execution_profile=ep_name) - self.assertTrue("evaluation exceeded the configured threshold of 1000" in str(ir.exception) or - "evaluation exceeded the configured threshold of evaluation_timeout at 1000" in str( - ir.exception)) - - def test_server_timeout_less_then_request(self): - """ - Tests that with explicit request_timeouts set, that a client timeout is honored if it's triggered prior to the - server sending a timeout. - - @since 1.0.0 - @jira_ticket PYTHON-589 - - @test_category dse graph - """ - graph_source = "test_timeout_3" - ep_name = graph_source - ep = self.session.execution_profile_clone_update(EXEC_PROFILE_GRAPH_DEFAULT, request_timeout=1) - ep.graph_options = ep.graph_options.copy() - ep.graph_options.graph_source = graph_source - self.cluster.add_execution_profile(ep_name, ep) - server_timeout = 10000 - to_run = '''graph.schema().config().option("graph.traversal_sources.{0}.evaluation_timeout").set('{1} ms')'''.format( - graph_source, server_timeout) - self.session.execute_graph(to_run, execution_profile=ep_name) - - with self.assertRaises(Exception) as e: - self.session.execute_graph("java.util.concurrent.TimeUnit.MILLISECONDS.sleep(35000L);1+1", - execution_profile=ep_name) - self.assertTrue(isinstance(e, InvalidRequest) or isinstance(e, OperationTimedOut)) - - -@requiredse -class GraphProfileTests(BasicGraphUnitTestCase): - def test_graph_profile(self): - """ - Test verifying various aspects of graph config properties. - - @since 1.0.0 - @jira_ticket PYTHON-570 - - @test_category dse graph - """ - hosts = self.cluster.metadata.all_hosts() - first_host = hosts[0].address - second_hosts = "1.2.3.4" - - self._execute(ClassicGraphSchema.fixtures.classic(), graphson=GraphProtocol.GRAPHSON_1_0) - # Create various execution policies - exec_dif_factory = GraphExecutionProfile(row_factory=single_object_row_factory) - exec_dif_factory.graph_options.graph_name = self.graph_name - exec_dif_lbp = GraphExecutionProfile(load_balancing_policy=WhiteListRoundRobinPolicy([first_host])) - exec_dif_lbp.graph_options.graph_name = self.graph_name - exec_bad_lbp = GraphExecutionProfile(load_balancing_policy=WhiteListRoundRobinPolicy([second_hosts])) - exec_dif_lbp.graph_options.graph_name = self.graph_name - exec_short_timeout = GraphExecutionProfile(request_timeout=1, - load_balancing_policy=WhiteListRoundRobinPolicy([first_host])) - exec_short_timeout.graph_options.graph_name = self.graph_name - - # Add a single execution policy on cluster creation - local_cluster = TestCluster(execution_profiles={"exec_dif_factory": exec_dif_factory}) - local_session = local_cluster.connect() - self.addCleanup(local_cluster.shutdown) - - rs1 = self.session.execute_graph('g.V()') - rs2 = local_session.execute_graph('g.V()', execution_profile='exec_dif_factory') - - # Verify default and non default policy works - self.assertFalse(isinstance(rs2[0], Vertex)) - self.assertTrue(isinstance(rs1[0], Vertex)) - # Add other policies validate that lbp are honored - local_cluster.add_execution_profile("exec_dif_ldp", exec_dif_lbp) - local_session.execute_graph('g.V()', execution_profile="exec_dif_ldp") - local_cluster.add_execution_profile("exec_bad_lbp", exec_bad_lbp) - with self.assertRaises(NoHostAvailable): - local_session.execute_graph('g.V()', execution_profile="exec_bad_lbp") - - # Try with missing EP - with self.assertRaises(ValueError): - local_session.execute_graph('g.V()', execution_profile='bad_exec_profile') - - # Validate that timeout is honored - local_cluster.add_execution_profile("exec_short_timeout", exec_short_timeout) - with self.assertRaises(Exception) as e: - self.assertTrue(isinstance(e, InvalidRequest) or isinstance(e, OperationTimedOut)) - local_session.execute_graph('java.util.concurrent.TimeUnit.MILLISECONDS.sleep(2000L);', - execution_profile='exec_short_timeout') - - -@requiredse -class GraphMetadataTest(BasicGraphUnitTestCase): - - @greaterthanorequaldse51 - def test_dse_workloads(self): - """ - Test to ensure dse_workloads is populated appropriately. - Field added in DSE 5.1 - - @since DSE 2.0 - @jira_ticket PYTHON-667 - @expected_result dse_workloads set is set on host model - - @test_category metadata - """ - for host in self.cluster.metadata.all_hosts(): - self.assertIsInstance(host.dse_workloads, SortedSet) - self.assertIn("Cassandra", host.dse_workloads) - self.assertIn("Graph", host.dse_workloads) - - -@requiredse -class GraphExecutionProfileOptionsResolveTest(GraphUnitTestCase): - """ - Test that the execution profile options are properly resolved for graph queries. - - @since DSE 6.8 - @jira_ticket PYTHON-1004 PYTHON-1056 - @expected_result execution profile options are properly determined following the rules. - """ - - def test_default_options(self): - ep = self.session.get_execution_profile(EXEC_PROFILE_GRAPH_DEFAULT) - self.assertEqual(ep.graph_options.graph_protocol, None) - self.assertEqual(ep.row_factory, None) - self.session._resolve_execution_profile_options(ep) - self.assertEqual(ep.graph_options.graph_protocol, GraphProtocol.GRAPHSON_1_0) - self.assertEqual(ep.row_factory, graph_object_row_factory) - - def test_default_options_when_not_groovy(self): - ep = self.session.get_execution_profile(EXEC_PROFILE_GRAPH_DEFAULT) - self.assertEqual(ep.graph_options.graph_protocol, None) - self.assertEqual(ep.row_factory, None) - ep.graph_options.graph_language = 'whatever' - self.session._resolve_execution_profile_options(ep) - self.assertEqual(ep.graph_options.graph_protocol, GraphProtocol.GRAPHSON_2_0) - self.assertEqual(ep.row_factory, graph_graphson2_row_factory) - - def test_default_options_when_explicitly_specified(self): - ep = self.session.get_execution_profile(EXEC_PROFILE_GRAPH_DEFAULT) - self.assertEqual(ep.graph_options.graph_protocol, None) - self.assertEqual(ep.row_factory, None) - obj = object() - ep.graph_options.graph_protocol = obj - ep.row_factory = obj - self.session._resolve_execution_profile_options(ep) - self.assertEqual(ep.graph_options.graph_protocol, obj) - self.assertEqual(ep.row_factory, obj) - - @greaterthanorequaldse68 - def test_graph_protocol_default_for_core_is_graphson3(self): - """Test that graphson3 is automatically resolved for a core graph query""" - self.setup_graph(CoreGraphSchema) - ep = self.session.get_execution_profile(EXEC_PROFILE_GRAPH_DEFAULT) - self.assertEqual(ep.graph_options.graph_protocol, None) - self.assertEqual(ep.row_factory, None) - # Ensure we have the graph metadata - self.session.cluster.refresh_schema_metadata() - self.session._resolve_execution_profile_options(ep) - self.assertEqual(ep.graph_options.graph_protocol, GraphProtocol.GRAPHSON_3_0) - self.assertEqual(ep.row_factory, graph_graphson3_row_factory) - - self.execute_graph_queries(CoreGraphSchema.fixtures.classic(), verify_graphson=GraphProtocol.GRAPHSON_3_0) - - @greaterthanorequaldse68 - def test_graph_protocol_default_for_core_fallback_to_graphson1_if_no_graph_name(self): - """Test that graphson1 is set when we cannot detect if it's a core graph""" - self.setup_graph(CoreGraphSchema) - default_ep = self.session.get_execution_profile(EXEC_PROFILE_GRAPH_DEFAULT) - graph_options = default_ep.graph_options.copy() - graph_options.graph_name = None - ep = self.session.execution_profile_clone_update(EXEC_PROFILE_GRAPH_DEFAULT, graph_options=graph_options) - self.session._resolve_execution_profile_options(ep) - self.assertEqual(ep.graph_options.graph_protocol, GraphProtocol.GRAPHSON_1_0) - self.assertEqual(ep.row_factory, graph_object_row_factory) - - regex = re.compile(".*Variable.*is unknown.*", re.S) - with self.assertRaisesRegex(SyntaxException, regex): - self.execute_graph_queries(CoreGraphSchema.fixtures.classic(), - execution_profile=ep, verify_graphson=GraphProtocol.GRAPHSON_1_0) diff --git a/tests/integration/advanced/graph/test_graph_cont_paging.py b/tests/integration/advanced/graph/test_graph_cont_paging.py deleted file mode 100644 index 065d01d939..0000000000 --- a/tests/integration/advanced/graph/test_graph_cont_paging.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from cassandra.cluster import ContinuousPagingOptions - -from tests.integration import greaterthanorequaldse68 -from tests.integration.advanced.graph import GraphUnitTestCase, CoreGraphSchema, GraphTestConfiguration - - -@greaterthanorequaldse68 -@GraphTestConfiguration.generate_tests(schema=CoreGraphSchema) -class GraphPagingTest(GraphUnitTestCase): - - def _setup_data(self, schema, graphson): - self.execute_graph("schema.vertexLabel('person').ifNotExists().partitionBy('name', Text).property('age', Int).create();", graphson) - for i in range(100): - self.execute_graph("g.addV('person').property('name', 'batman-{}')".format(i), graphson) - - def _test_cont_paging_is_enabled_by_default(self, schema, graphson): - """ - Test that graph paging is automatically enabled with a >=6.8 cluster. - - @jira_ticket PYTHON-1045 - @expected_result the response future has a continuous_paging_session since graph paging is enabled - - @test_category dse graph - """ - ep = self.get_execution_profile(graphson) - self._setup_data(schema, graphson) - rf = self.session.execute_graph_async("g.V()", execution_profile=ep) - results = list(rf.result()) - self.assertIsNotNone(rf._continuous_paging_session) - self.assertEqual(len(results), 100) - - def _test_cont_paging_can_be_disabled(self, schema, graphson): - """ - Test that graph paging can be disabled. - - @jira_ticket PYTHON-1045 - @expected_result the response future doesn't have a continuous_paging_session since graph paging is disabled - - @test_category dse graph - """ - ep = self.get_execution_profile(graphson) - new_ep = self.session.execution_profile_clone_update(ep, continuous_paging_options=None) - self._setup_data(schema, graphson) - rf = self.session.execute_graph_async("g.V()", execution_profile=new_ep) - results = list(rf.result()) - self.assertIsNone(rf._continuous_paging_session) - self.assertEqual(len(results), 100) - - def _test_cont_paging_with_custom_options(self, schema, graphson): - """ - Test that we can specify custom paging options. - - @jira_ticket PYTHON-1045 - @expected_result we get only the desired number of results - - @test_category dse graph - """ - ep = self.get_execution_profile(graphson) - new_ep = self.session.execution_profile_clone_update( - ep, continuous_paging_options=ContinuousPagingOptions(max_pages=1)) - self._setup_data(schema, graphson) - self.session.default_fetch_size = 10 - results = list(self.session.execute_graph("g.V()", execution_profile=new_ep)) - self.assertEqual(len(results), 10) diff --git a/tests/integration/advanced/graph/test_graph_datatype.py b/tests/integration/advanced/graph/test_graph_datatype.py deleted file mode 100644 index 8a261c94d9..0000000000 --- a/tests/integration/advanced/graph/test_graph_datatype.py +++ /dev/null @@ -1,266 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest - -import time -import logging -from packaging.version import Version -from collections import namedtuple - -from cassandra.cluster import EXEC_PROFILE_GRAPH_DEFAULT -from cassandra.graph import graph_result_row_factory -from cassandra.graph.query import GraphProtocol -from cassandra.graph.types import VertexProperty - -from tests.util import wait_until -from tests.integration.advanced.graph import BasicGraphUnitTestCase, ClassicGraphFixtures, \ - ClassicGraphSchema, CoreGraphSchema -from tests.integration.advanced.graph import VertexLabel, GraphTestConfiguration, GraphUnitTestCase -from tests.integration import DSE_VERSION, requiredse - -log = logging.getLogger(__name__) - - -@requiredse -class GraphBasicDataTypesTests(BasicGraphUnitTestCase): - - def test_result_types(self): - """ - Test to validate that the edge and vertex version of results are constructed correctly. - - @since 1.0.0 - @jira_ticket PYTHON-479 - @expected_result edge/vertex result types should be unpacked correctly. - @test_category dse graph - """ - queries, params = ClassicGraphFixtures.multiple_fields() - for query in queries: - self.session.execute_graph(query, params) - - prof = self.session.execution_profile_clone_update(EXEC_PROFILE_GRAPH_DEFAULT, row_factory=graph_result_row_factory) # requires simplified row factory to avoid shedding id/~type information used for validation below - rs = self.session.execute_graph("g.V()", execution_profile=prof) - - for result in rs: - self._validate_type(result) - - def _validate_type(self, vertex): - for properties in vertex.properties.values(): - prop = properties[0] - - if DSE_VERSION >= Version("5.1"): - type_indicator = prop['id']['~label'] - else: - type_indicator = prop['id']['~type'] - - if any(type_indicator.startswith(t) for t in - ('int', 'short', 'long', 'bigint', 'decimal', 'smallint', 'varint')): - typ = int - elif any(type_indicator.startswith(t) for t in ('float', 'double')): - typ = float - elif any(type_indicator.startswith(t) for t in ('duration', 'date', 'negdate', 'time', - 'blob', 'timestamp', 'point', 'linestring', 'polygon', - 'inet', 'uuid')): - typ = str - else: - pass - self.fail("Received unexpected type: %s" % type_indicator) - self.assertIsInstance(prop['value'], typ) - - -class GenericGraphDataTypeTest(GraphUnitTestCase): - - def _test_all_datatypes(self, schema, graphson): - ep = self.get_execution_profile(graphson) - - for data in schema.fixtures.datatypes().values(): - typ, value, deserializer = data - vertex_label = VertexLabel([typ]) - property_name = next(iter(vertex_label.non_pk_properties.keys())) - schema.create_vertex_label(self.session, vertex_label, execution_profile=ep) - vertex = list(schema.add_vertex(self.session, vertex_label, property_name, value, execution_profile=ep))[0] - - def get_vertex_properties(): - return list(schema.get_vertex_properties( - self.session, vertex, execution_profile=ep)) - - prop_returned = 1 if DSE_VERSION < Version('5.1') else 2 # include pkid >=5.1 - wait_until( - lambda: len(get_vertex_properties()) == prop_returned, 0.2, 15) - - vertex_properties = get_vertex_properties() - if graphson == GraphProtocol.GRAPHSON_1_0: - vertex_properties = [vp.as_vertex_property() for vp in vertex_properties] - - for vp in vertex_properties: - if vp.label == 'pkid': - continue - - self.assertIsInstance(vp, VertexProperty) - self.assertEqual(vp.label, property_name) - if graphson == GraphProtocol.GRAPHSON_1_0: - deserialized_value = deserializer(vp.value) if deserializer else vp.value - self.assertEqual(deserialized_value, value) - else: - self.assertEqual(vp.value, value) - - def __test_udt(self, schema, graphson, address_class, address_with_tags_class, - complex_address_class, complex_address_with_owners_class): - if schema is not CoreGraphSchema or DSE_VERSION < Version('6.8'): - raise unittest.SkipTest("Graph UDT is only supported with DSE 6.8+ and Core graphs.") - - ep = self.get_execution_profile(graphson) - - Address = address_class - AddressWithTags = address_with_tags_class - ComplexAddress = complex_address_class - ComplexAddressWithOwners = complex_address_with_owners_class - - # setup udt - self.session.execute_graph(""" - schema.type('address').property('address', Text).property('city', Text).property('state', Text).create(); - schema.type('addressTags').property('address', Text).property('city', Text).property('state', Text). - property('tags', setOf(Text)).create(); - schema.type('complexAddress').property('address', Text).property('address_tags', frozen(typeOf('addressTags'))). - property('city', Text).property('state', Text).property('props', mapOf(Text, Int)).create(); - schema.type('complexAddressWithOwners').property('address', Text). - property('address_tags', frozen(typeOf('addressTags'))). - property('city', Text).property('state', Text).property('props', mapOf(Text, Int)). - property('owners', frozen(listOf(tupleOf(Text, Int)))).create(); - """, execution_profile=ep) - - time.sleep(2) # wait the UDT to be discovered - self.session.cluster.register_user_type(self.graph_name, 'address', Address) - self.session.cluster.register_user_type(self.graph_name, 'addressTags', AddressWithTags) - self.session.cluster.register_user_type(self.graph_name, 'complexAddress', ComplexAddress) - self.session.cluster.register_user_type(self.graph_name, 'complexAddressWithOwners', ComplexAddressWithOwners) - - data = { - "udt1": ["typeOf('address')", Address('1440 Rd Smith', 'Quebec', 'QC')], - "udt2": ["tupleOf(typeOf('address'), Text)", (Address('1440 Rd Smith', 'Quebec', 'QC'), 'hello')], - "udt3": ["tupleOf(frozen(typeOf('address')), Text)", (Address('1440 Rd Smith', 'Quebec', 'QC'), 'hello')], - "udt4": ["tupleOf(tupleOf(Int, typeOf('address')), Text)", - ((42, Address('1440 Rd Smith', 'Quebec', 'QC')), 'hello')], - "udt5": ["tupleOf(tupleOf(Int, typeOf('addressTags')), Text)", - ((42, AddressWithTags('1440 Rd Smith', 'Quebec', 'QC', {'t1', 't2'})), 'hello')], - "udt6": ["tupleOf(tupleOf(Int, typeOf('complexAddress')), Text)", - ((42, ComplexAddress('1440 Rd Smith', - AddressWithTags('1440 Rd Smith', 'Quebec', 'QC', {'t1', 't2'}), - 'Quebec', 'QC', {'p1': 42, 'p2': 33})), 'hello')], - "udt7": ["tupleOf(tupleOf(Int, frozen(typeOf('complexAddressWithOwners'))), Text)", - ((42, ComplexAddressWithOwners( - '1440 Rd Smith', - AddressWithTags('1440 CRd Smith', 'Quebec', 'QC', {'t1', 't2'}), - 'Quebec', 'QC', {'p1': 42, 'p2': 33}, [('Mike', 43), ('Gina', 39)]) - ), 'hello')] - } - - for typ, value in data.values(): - vertex_label = VertexLabel([typ]) - property_name = next(iter(vertex_label.non_pk_properties.keys())) - schema.create_vertex_label(self.session, vertex_label, execution_profile=ep) - - vertex = list(schema.add_vertex(self.session, vertex_label, property_name, value, execution_profile=ep))[0] - - def get_vertex_properties(): - return list(schema.get_vertex_properties( - self.session, vertex, execution_profile=ep)) - - wait_until( - lambda: len(get_vertex_properties()) == 2, 0.2, 15) - - vertex_properties = get_vertex_properties() - for vp in vertex_properties: - if vp.label == 'pkid': - continue - - self.assertIsInstance(vp, VertexProperty) - self.assertEqual(vp.label, property_name) - self.assertEqual(vp.value, value) - - def _test_udt_with_classes(self, schema, graphson): - class Address(object): - - def __init__(self, address, city, state): - self.address = address - self.city = city - self.state = state - - def __eq__(self, other): - return self.address == other.address and self.city == other.city and self.state == other.state - - class AddressWithTags(object): - - def __init__(self, address, city, state, tags): - self.address = address - self.city = city - self.state = state - self.tags = tags - - def __eq__(self, other): - return (self.address == other.address and self.city == other.city - and self.state == other.state and self.tags == other.tags) - - class ComplexAddress(object): - - def __init__(self, address, address_tags, city, state, props): - self.address = address - self.address_tags = address_tags - self.city = city - self.state = state - self.props = props - - def __eq__(self, other): - return (self.address == other.address and self.address_tags == other.address_tags - and self.city == other.city and self.state == other.state - and self.props == other.props) - - class ComplexAddressWithOwners(object): - - def __init__(self, address, address_tags, city, state, props, owners): - self.address = address - self.address_tags = address_tags - self.city = city - self.state = state - self.props = props - self.owners = owners - - def __eq__(self, other): - return (self.address == other.address and self.address_tags == other.address_tags - and self.city == other.city and self.state == other.state - and self.props == other.props and self.owners == other.owners) - - self.__test_udt(schema, graphson, Address, AddressWithTags, ComplexAddress, ComplexAddressWithOwners) - - def _test_udt_with_namedtuples(self, schema, graphson): - AddressTuple = namedtuple('Address', ('address', 'city', 'state')) - AddressWithTagsTuple = namedtuple('AddressWithTags', ('address', 'city', 'state', 'tags')) - ComplexAddressTuple = namedtuple('ComplexAddress', ('address', 'address_tags', 'city', 'state', 'props')) - ComplexAddressWithOwnersTuple = namedtuple('ComplexAddressWithOwners', ('address', 'address_tags', 'city', - 'state', 'props', 'owners')) - - self.__test_udt(schema, graphson, AddressTuple, AddressWithTagsTuple, - ComplexAddressTuple, ComplexAddressWithOwnersTuple) - - -@requiredse -@GraphTestConfiguration.generate_tests(schema=ClassicGraphSchema) -class ClassicGraphDataTypeTest(GenericGraphDataTypeTest): - pass - - -@requiredse -@GraphTestConfiguration.generate_tests(schema=CoreGraphSchema) -class CoreGraphDataTypeTest(GenericGraphDataTypeTest): - pass diff --git a/tests/integration/advanced/graph/test_graph_query.py b/tests/integration/advanced/graph/test_graph_query.py deleted file mode 100644 index 0c889938d8..0000000000 --- a/tests/integration/advanced/graph/test_graph_query.py +++ /dev/null @@ -1,594 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import sys -from packaging.version import Version - -from copy import copy -from itertools import chain -import json -import time - -import unittest - -from cassandra import OperationTimedOut, ConsistencyLevel, InvalidRequest -from cassandra.cluster import EXEC_PROFILE_GRAPH_DEFAULT, NoHostAvailable -from cassandra.protocol import ServerError, SyntaxException -from cassandra.query import QueryTrace -from cassandra.util import Point -from cassandra.graph import (SimpleGraphStatement, single_object_row_factory, - Result, GraphOptions, GraphProtocol, to_bigint) -from cassandra.datastax.graph.query import _graph_options -from cassandra.datastax.graph.types import T - -from tests.integration import DSE_VERSION, requiredse, greaterthanorequaldse68 -from tests.integration.advanced.graph import BasicGraphUnitTestCase, GraphTestConfiguration, \ - validate_classic_vertex, GraphUnitTestCase, validate_classic_edge, validate_path_result_type, \ - validate_line_edge, validate_generic_vertex_result_type, \ - ClassicGraphSchema, CoreGraphSchema, VertexLabel - - -@requiredse -class BasicGraphQueryTest(BasicGraphUnitTestCase): - - def test_consistency_passing(self): - """ - Test to validated that graph consistency levels are properly surfaced to the base driver - - @since 1.0.0 - @jira_ticket PYTHON-509 - @expected_result graph consistency levels are surfaced correctly - @test_category dse graph - """ - cl_attrs = ('graph_read_consistency_level', 'graph_write_consistency_level') - - # Iterates over the graph options and constructs an array containing - # The graph_options that correlate to graoh read and write consistency levels - graph_params = [a[2] for a in _graph_options if a[0] in cl_attrs] - - s = self.session - default_profile = s.cluster.profile_manager.profiles[EXEC_PROFILE_GRAPH_DEFAULT] - default_graph_opts = default_profile.graph_options - try: - # Checks the default graph attributes and ensures that both graph_read_consistency_level and graph_write_consistency_level - # Are None by default - for attr in cl_attrs: - self.assertIsNone(getattr(default_graph_opts, attr)) - - res = s.execute_graph("null") - for param in graph_params: - self.assertNotIn(param, res.response_future.message.custom_payload) - - # session defaults are passed - opts = GraphOptions() - opts.update(default_graph_opts) - cl = {0: ConsistencyLevel.ONE, 1: ConsistencyLevel.LOCAL_QUORUM} - for k, v in cl.items(): - setattr(opts, cl_attrs[k], v) - default_profile.graph_options = opts - - res = s.execute_graph("null") - - for k, v in cl.items(): - self.assertEqual(res.response_future.message.custom_payload[graph_params[k]], ConsistencyLevel.value_to_name[v].encode()) - - # passed profile values override session defaults - cl = {0: ConsistencyLevel.ALL, 1: ConsistencyLevel.QUORUM} - opts = GraphOptions() - opts.update(default_graph_opts) - for k, v in cl.items(): - attr_name = cl_attrs[k] - setattr(opts, attr_name, v) - self.assertNotEqual(getattr(default_profile.graph_options, attr_name), getattr(opts, attr_name)) - tmp_profile = s.execution_profile_clone_update(EXEC_PROFILE_GRAPH_DEFAULT, graph_options=opts) - res = s.execute_graph("null", execution_profile=tmp_profile) - - for k, v in cl.items(): - self.assertEqual(res.response_future.message.custom_payload[graph_params[k]], ConsistencyLevel.value_to_name[v].encode()) - finally: - default_profile.graph_options = default_graph_opts - - def test_execute_graph_row_factory(self): - s = self.session - - # default Results - default_profile = s.cluster.profile_manager.profiles[EXEC_PROFILE_GRAPH_DEFAULT] - self.assertEqual(default_profile.row_factory, None) # will be resolved to graph_object_row_factory - result = s.execute_graph("123")[0] - self.assertIsInstance(result, Result) - self.assertEqual(result.value, 123) - - # other via parameter - prof = s.execution_profile_clone_update(EXEC_PROFILE_GRAPH_DEFAULT, row_factory=single_object_row_factory) - rs = s.execute_graph("123", execution_profile=prof) - self.assertEqual(rs.response_future.row_factory, single_object_row_factory) - self.assertEqual(json.loads(rs[0]), {'result': 123}) - - def test_execute_graph_timeout(self): - s = self.session - - value = [1, 2, 3] - query = "[%r]" % (value,) - - # default is passed down - default_graph_profile = s.cluster.profile_manager.profiles[EXEC_PROFILE_GRAPH_DEFAULT] - rs = self.session.execute_graph(query) - self.assertEqual(rs[0].value, value) - self.assertEqual(rs.response_future.timeout, default_graph_profile.request_timeout) - - # tiny timeout times out as expected - tmp_profile = copy(default_graph_profile) - tmp_profile.request_timeout = sys.float_info.min - - max_retry_count = 10 - for _ in range(max_retry_count): - start = time.time() - try: - with self.assertRaises(OperationTimedOut): - s.execute_graph(query, execution_profile=tmp_profile) - break - except: - end = time.time() - self.assertAlmostEqual(start, end, 1) - else: - raise Exception("session.execute_graph didn't time out in {0} tries".format(max_retry_count)) - - def test_profile_graph_options(self): - s = self.session - statement = SimpleGraphStatement("true") - ep = self.session.execution_profile_clone_update(EXEC_PROFILE_GRAPH_DEFAULT) - self.assertTrue(s.execute_graph(statement, execution_profile=ep)[0].value) - - # bad graph name to verify it's passed - ep.graph_options = ep.graph_options.copy() - ep.graph_options.graph_name = "definitely_not_correct" - try: - s.execute_graph(statement, execution_profile=ep) - except NoHostAvailable: - self.assertTrue(DSE_VERSION >= Version("6.0")) - except InvalidRequest: - self.assertTrue(DSE_VERSION >= Version("5.0")) - else: - if DSE_VERSION < Version("6.8"): # >6.8 returns true - self.fail("Should have risen ServerError or InvalidRequest") - - def test_additional_custom_payload(self): - s = self.session - custom_payload = {'some': 'example'.encode('utf-8'), 'items': 'here'.encode('utf-8')} - sgs = SimpleGraphStatement("null", custom_payload=custom_payload) - future = s.execute_graph_async(sgs) - - default_profile = s.cluster.profile_manager.profiles[EXEC_PROFILE_GRAPH_DEFAULT] - default_graph_opts = default_profile.graph_options - for k, v in chain(custom_payload.items(), default_graph_opts.get_options_map().items()): - self.assertEqual(future.message.custom_payload[k], v) - - -class GenericGraphQueryTest(GraphUnitTestCase): - - def _test_basic_query(self, schema, graphson): - """ - Test to validate that basic graph query results can be executed with a sane result set. - - Creates a simple classic tinkerpot graph, and attempts to find all vertices - related the vertex marco, that have a label of knows. - See reference graph here - http://www.tinkerpop.com/docs/3.0.0.M1/ - - @since 1.0.0 - @jira_ticket PYTHON-457 - @expected_result graph should find two vertices related to marco via 'knows' edges. - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.classic(), graphson) - rs = self.execute_graph('''g.V().has('name','marko').out('knows').values('name')''', graphson) - self.assertFalse(rs.has_more_pages) - results_list = self.resultset_to_list(rs) - self.assertEqual(len(results_list), 2) - self.assertIn('vadas', results_list) - self.assertIn('josh', results_list) - - def _test_geometric_graph_types(self, schema, graphson): - """ - Test to validate that geometric types function correctly - - Creates a very simple graph, and tries to insert a simple point type - - @since 1.0.0 - @jira_ticket DSP-8087 - @expected_result json types associated with insert is parsed correctly - - @test_category dse graph - """ - vertex_label = VertexLabel([('pointP', "Point()")]) - ep = self.get_execution_profile(graphson) - schema.create_vertex_label(self.session, vertex_label, ep) - # import org.apache.cassandra.db.marshal.geometry.Point; - rs = schema.add_vertex(self.session, vertex_label, 'pointP', Point(0, 1), ep) - - # if result set is not parsed correctly this will throw an exception - self.assertIsNotNone(rs) - - def _test_execute_graph_trace(self, schema, graphson): - value = [1, 2, 3] - query = "[%r]" % (value,) - - # default is no trace - rs = self.execute_graph(query, graphson) - results = self.resultset_to_list(rs) - self.assertEqual(results[0], value) - self.assertIsNone(rs.get_query_trace()) - - # request trace - rs = self.execute_graph(query, graphson, trace=True) - results = self.resultset_to_list(rs) - self.assertEqual(results[0], value) - qt = rs.get_query_trace(max_wait_sec=10) - self.assertIsInstance(qt, QueryTrace) - self.assertIsNotNone(qt.duration) - - def _test_range_query(self, schema, graphson): - """ - Test to validate range queries are handled correctly. - - Creates a very large line graph script and executes it. Then proceeds to to a range - limited query against it, and ensure that the results are formatted correctly and that - the result set is properly sized. - - @since 1.0.0 - @jira_ticket PYTHON-457 - @expected_result result set should be properly formatted and properly sized - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.line(150), graphson) - rs = self.execute_graph("g.E().range(0,10)", graphson) - self.assertFalse(rs.has_more_pages) - results = self.resultset_to_list(rs) - self.assertEqual(len(results), 10) - ep = self.get_execution_profile(graphson) - for result in results: - schema.ensure_properties(self.session, result, execution_profile=ep) - validate_line_edge(self, result) - - def _test_classic_graph(self, schema, graphson): - """ - Test to validate that basic graph generation, and vertex and edges are surfaced correctly - - Creates a simple classic tinkerpot graph, and iterates over the the vertices and edges - ensureing that each one is correct. See reference graph here - http://www.tinkerpop.com/docs/3.0.0.M1/ - - @since 1.0.0 - @jira_ticket PYTHON-457 - @expected_result graph should generate and all vertices and edge results should be - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.classic(), graphson) - rs = self.execute_graph('g.V()', graphson) - ep = self.get_execution_profile(graphson) - for vertex in rs: - schema.ensure_properties(self.session, vertex, execution_profile=ep) - validate_classic_vertex(self, vertex) - rs = self.execute_graph('g.E()', graphson) - for edge in rs: - schema.ensure_properties(self.session, edge, execution_profile=ep) - validate_classic_edge(self, edge) - - def _test_graph_classic_path(self, schema, graphson): - """ - Test to validate that the path version of the result type is generated correctly. It also - tests basic path results as that is not covered elsewhere - - @since 1.0.0 - @jira_ticket PYTHON-479 - @expected_result path object should be unpacked correctly including all nested edges and verticies - @test_category dse graph - """ - self.execute_graph(schema.fixtures.classic(), graphson) - rs = self.execute_graph("g.V().hasLabel('person').has('name', 'marko').as('a').outE('knows').inV().as('c', 'd')." - " outE('created').as('e', 'f', 'g').inV().path()", - graphson) - rs_list = list(rs) - self.assertEqual(len(rs_list), 2) - for result in rs_list: - try: - path = result.as_path() - except: - path = result - - ep = self.get_execution_profile(graphson) - for obj in path.objects: - schema.ensure_properties(self.session, obj, ep) - - validate_path_result_type(self, path) - - def _test_large_create_script(self, schema, graphson): - """ - Test to validate that server errors due to large groovy scripts are properly surfaced - - Creates a very large line graph script and executes it. Then proceeds to create a line graph script - that is to large for the server to handle expects a server error to be returned - - @since 1.0.0 - @jira_ticket PYTHON-457 - @expected_result graph should generate and all vertices and edge results should be - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.line(150), graphson) - self.execute_graph(schema.fixtures.line(300), graphson) # This should passed since the queries are splitted - self.assertRaises(SyntaxException, self.execute_graph, schema.fixtures.line(300, single_script=True), graphson) # this is not and too big - - def _test_large_result_set(self, schema, graphson): - """ - Test to validate that large result sets return correctly. - - Creates a very large graph. Ensures that large result sets are handled appropriately. - - @since 1.0.0 - @jira_ticket PYTHON-457 - @expected_result when limits of result sets are hit errors should be surfaced appropriately - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.large(), graphson, execution_profile_options={'request_timeout': 32}) - rs = self.execute_graph("g.V()", graphson) - for result in rs: - validate_generic_vertex_result_type(self, result) - - def _test_param_passing(self, schema, graphson): - """ - Test to validate that parameter passing works as expected - - @since 1.0.0 - @jira_ticket PYTHON-457 - @expected_result parameters work as expected - - @test_category dse graph - """ - - # unused parameters are passed, but ignored - self.execute_graph("null", graphson, params={"doesn't": "matter", "what's": "passed"}) - - # multiple params - rs = self.execute_graph("[a, b]", graphson, params={'a': 0, 'b': 1}) - results = self.resultset_to_list(rs) - self.assertEqual(results[0], 0) - self.assertEqual(results[1], 1) - - if graphson == GraphProtocol.GRAPHSON_1_0: - # different value types - for param in (None, "string", 1234, 5.678, True, False): - result = self.resultset_to_list(self.execute_graph('x', graphson, params={'x': param}))[0] - self.assertEqual(result, param) - - def _test_vertex_property_properties(self, schema, graphson): - """ - Test verifying vertex property properties - - @since 1.0.0 - @jira_ticket PYTHON-487 - - @test_category dse graph - """ - if schema is not ClassicGraphSchema: - raise unittest.SkipTest('skipped because rich properties are only supported with classic graphs') - - self.execute_graph("schema.propertyKey('k0').Text().ifNotExists().create();", graphson) - self.execute_graph("schema.propertyKey('k1').Text().ifNotExists().create();", graphson) - self.execute_graph("schema.propertyKey('key').Text().properties('k0', 'k1').ifNotExists().create();", graphson) - self.execute_graph("schema.vertexLabel('MLP').properties('key').ifNotExists().create();", graphson) - v = self.execute_graph('''v = graph.addVertex('MLP') - v.property('key', 'value', 'k0', 'v0', 'k1', 'v1') - v''', graphson)[0] - self.assertEqual(len(v.properties), 1) - self.assertEqual(len(v.properties['key']), 1) - p = v.properties['key'][0] - self.assertEqual(p.label, 'key') - self.assertEqual(p.value, 'value') - self.assertEqual(p.properties, {'k0': 'v0', 'k1': 'v1'}) - - def _test_vertex_multiple_properties(self, schema, graphson): - """ - Test verifying vertex property form for various Cardinality - - All key types are encoded as a list, regardless of cardinality - - Single cardinality properties have only one value -- the last one added - - Default is single (this is config dependent) - - @since 1.0.0 - @jira_ticket PYTHON-487 - - @test_category dse graph - """ - if schema is not ClassicGraphSchema: - raise unittest.SkipTest('skipped because multiple properties are only supported with classic graphs') - - self.execute_graph('''Schema schema = graph.schema(); - schema.propertyKey('mult_key').Text().multiple().ifNotExists().create(); - schema.propertyKey('single_key').Text().single().ifNotExists().create(); - schema.vertexLabel('MPW1').properties('mult_key').ifNotExists().create(); - schema.vertexLabel('SW1').properties('single_key').ifNotExists().create();''', graphson) - - v = self.execute_graph('''v = graph.addVertex('MPW1') - v.property('mult_key', 'value') - v''', graphson)[0] - self.assertEqual(len(v.properties), 1) - self.assertEqual(len(v.properties['mult_key']), 1) - self.assertEqual(v.properties['mult_key'][0].label, 'mult_key') - self.assertEqual(v.properties['mult_key'][0].value, 'value') - - # multiple_with_two_values - v = self.execute_graph('''g.addV('MPW1').property('mult_key', 'value0').property('mult_key', 'value1')''', graphson)[0] - self.assertEqual(len(v.properties), 1) - self.assertEqual(len(v.properties['mult_key']), 2) - self.assertEqual(v.properties['mult_key'][0].label, 'mult_key') - self.assertEqual(v.properties['mult_key'][1].label, 'mult_key') - self.assertEqual(v.properties['mult_key'][0].value, 'value0') - self.assertEqual(v.properties['mult_key'][1].value, 'value1') - - # single_with_one_value - v = self.execute_graph('''v = graph.addVertex('SW1') - v.property('single_key', 'value') - v''', graphson)[0] - self.assertEqual(len(v.properties), 1) - self.assertEqual(len(v.properties['single_key']), 1) - self.assertEqual(v.properties['single_key'][0].label, 'single_key') - self.assertEqual(v.properties['single_key'][0].value, 'value') - - if DSE_VERSION < Version('6.8'): - # single_with_two_values - with self.assertRaises(InvalidRequest): - v = self.execute_graph(''' - v = graph.addVertex('SW1') - v.property('single_key', 'value0').property('single_key', 'value1').next() - v - ''', graphson)[0] - else: - # >=6.8 single_with_two_values, first one wins - v = self.execute_graph('''v = graph.addVertex('SW1') - v.property('single_key', 'value0').property('single_key', 'value1') - v''', graphson)[0] - self.assertEqual(v.properties['single_key'][0].value, 'value0') - - def _test_result_forms(self, schema, graphson): - """ - Test to validate that geometric types function correctly - - Creates a very simple graph, and tries to insert a simple point type - - @since 1.0.0 - @jira_ticket DSP-8087 - @expected_result json types associated with insert is parsed correctly - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.classic(), graphson) - ep = self.get_execution_profile(graphson) - - results = self.resultset_to_list(self.session.execute_graph('g.V()', execution_profile=ep)) - self.assertGreater(len(results), 0, "Result set was empty this was not expected") - for v in results: - schema.ensure_properties(self.session, v, ep) - validate_classic_vertex(self, v) - - results = self.resultset_to_list(self.session.execute_graph('g.E()', execution_profile=ep)) - self.assertGreater(len(results), 0, "Result set was empty this was not expected") - for e in results: - schema.ensure_properties(self.session, e, ep) - validate_classic_edge(self, e) - - def _test_query_profile(self, schema, graphson): - """ - Test to validate profiling results are deserialized properly. - - @since 1.6.0 - @jira_ticket PYTHON-1057 - @expected_result TraversalMetrics and Metrics are deserialized properly - - @test_category dse graph - """ - if graphson == GraphProtocol.GRAPHSON_1_0: - raise unittest.SkipTest('skipped because there is no metrics deserializer with graphson1') - - ep = self.get_execution_profile(graphson) - results = list(self.session.execute_graph("g.V().profile()", execution_profile=ep)) - self.assertEqual(len(results), 1) - self.assertIn('metrics', results[0]) - self.assertIn('dur', results[0]) - self.assertEqual(len(results[0]['metrics']), 2) - self.assertIn('dur', results[0]['metrics'][0]) - - def _test_query_bulkset(self, schema, graphson): - """ - Test to validate bulkset results are deserialized properly. - - @since 1.6.0 - @jira_ticket PYTHON-1060 - @expected_result BulkSet is deserialized properly to a list - - @test_category dse graph - """ - self.execute_graph(schema.fixtures.classic(), graphson) - ep = self.get_execution_profile(graphson) - results = list(self.session.execute_graph( - 'g.V().hasLabel("person").aggregate("x").by("age").cap("x")', - execution_profile=ep)) - self.assertEqual(len(results), 1) - results = results[0] - if type(results) is Result: - results = results.value - else: - self.assertEqual(len(results), 5) - self.assertEqual(results.count(35), 2) - - @greaterthanorequaldse68 - def _test_elementMap_query(self, schema, graphson): - """ - Test to validate that an elementMap can be serialized properly. - """ - self.execute_graph(schema.fixtures.classic(), graphson) - rs = self.execute_graph('''g.V().has('name','marko').elementMap()''', graphson) - results_list = self.resultset_to_list(rs) - self.assertEqual(len(results_list), 1) - row = results_list[0] - if graphson == GraphProtocol.GRAPHSON_3_0: - self.assertIn(T.id, row) - self.assertIn(T.label, row) - if schema is CoreGraphSchema: - self.assertEqual(row[T.id], 'dseg:/person/marko') - self.assertEqual(row[T.label], 'person') - else: - self.assertIn('id', row) - self.assertIn('label', row) - - -@GraphTestConfiguration.generate_tests(schema=ClassicGraphSchema) -class ClassicGraphQueryTest(GenericGraphQueryTest): - pass - - -@GraphTestConfiguration.generate_tests(schema=CoreGraphSchema) -class CoreGraphQueryTest(GenericGraphQueryTest): - pass - - -@GraphTestConfiguration.generate_tests(schema=CoreGraphSchema) -class CoreGraphQueryWithTypeWrapperTest(GraphUnitTestCase): - - def _test_basic_query_with_type_wrapper(self, schema, graphson): - """ - Test to validate that a query using a type wrapper works. - - @since 2.8.0 - @jira_ticket PYTHON-1051 - @expected_result graph query works and doesn't raise an exception - - @test_category dse graph - """ - ep = self.get_execution_profile(graphson) - vl = VertexLabel(['tupleOf(Int, Bigint)']) - schema.create_vertex_label(self.session, vl, execution_profile=ep) - - prop_name = next(iter(vl.non_pk_properties.keys())) - with self.assertRaises(InvalidRequest): - schema.add_vertex(self.session, vl, prop_name, (1, 42), execution_profile=ep) - - schema.add_vertex(self.session, vl, prop_name, (1, to_bigint(42)), execution_profile=ep) diff --git a/tests/integration/advanced/test_adv_metadata.py b/tests/integration/advanced/test_adv_metadata.py deleted file mode 100644 index 66f682fd49..0000000000 --- a/tests/integration/advanced/test_adv_metadata.py +++ /dev/null @@ -1,392 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from packaging.version import Version - -from tests.integration import (BasicExistingKeyspaceUnitTestCase, BasicSharedKeyspaceUnitTestCase, - BasicSharedKeyspaceUnitTestCaseRF1, - greaterthanorequaldse51, greaterthanorequaldse60, - greaterthanorequaldse68, use_single_node, - DSE_VERSION, requiredse, TestCluster) - -import unittest - -import logging -import time - - -log = logging.getLogger(__name__) - - -def setup_module(): - if DSE_VERSION: - use_single_node() - - -@requiredse -@greaterthanorequaldse60 -class FunctionAndAggregateMetadataTests(BasicSharedKeyspaceUnitTestCaseRF1): - - @classmethod - def setUpClass(cls): - if DSE_VERSION: - super(FunctionAndAggregateMetadataTests, cls).setUpClass() - - @classmethod - def tearDownClass(cls): - if DSE_VERSION: - super(FunctionAndAggregateMetadataTests, cls).tearDownClass() - - def setUp(self): - self.func_name = self.function_table_name + '_func' - self.agg_name = self.function_table_name + '_agg(int)' - - def _populated_ks_meta_attr(self, attr_name): - val, start_time = None, time.time() - while not val: - self.cluster.refresh_schema_metadata() - val = getattr(self.cluster.metadata.keyspaces[self.keyspace_name], - attr_name) - self.assertLess(time.time(), start_time + 30, - 'did not see func in metadata in 30s') - log.debug('done blocking; dict is populated: {}'.format(val)) - return val - - def test_monotonic_on_and_deterministic_function(self): - self.session.execute(""" - CREATE FUNCTION {ksn}.{ftn}(key int, val int) - RETURNS NULL ON NULL INPUT - RETURNS int - DETERMINISTIC - MONOTONIC ON val - LANGUAGE java AS 'return key+val;'; - """.format(ksn=self.keyspace_name, - ftn=self.func_name)) - fn = self._populated_ks_meta_attr('functions')[ - '{}(int,int)'.format(self.func_name) - ] - self.assertEqual(fn.monotonic_on, ['val']) - # monotonic is not set by MONOTONIC ON - self.assertFalse(fn.monotonic) - self.assertTrue(fn.deterministic) - self.assertEqual('CREATE FUNCTION {ksn}.{ftn}(key int, val int) ' - 'RETURNS NULL ON NULL INPUT ' - 'RETURNS int DETERMINISTIC MONOTONIC ON val ' - 'LANGUAGE java AS $$return key+val;$$' - ''.format(ksn=self.keyspace_name, - ftn=self.func_name), - fn.as_cql_query()) - self.session.execute('DROP FUNCTION {}.{}'.format(self.keyspace_name, - self.func_name)) - self.session.execute(fn.as_cql_query()) - - def test_monotonic_all_and_nondeterministic_function(self): - self.session.execute(""" - CREATE FUNCTION {ksn}.{ftn}(key int, val int) - RETURNS NULL ON NULL INPUT - RETURNS int - MONOTONIC - LANGUAGE java AS 'return key+val;'; - """.format(ksn=self.keyspace_name, - ftn=self.func_name)) - fn = self._populated_ks_meta_attr('functions')[ - '{}(int,int)'.format(self.func_name) - ] - self.assertEqual(set(fn.monotonic_on), {'key', 'val'}) - self.assertTrue(fn.monotonic) - self.assertFalse(fn.deterministic) - self.assertEqual('CREATE FUNCTION {ksn}.{ftn}(key int, val int) ' - 'RETURNS NULL ON NULL INPUT RETURNS int MONOTONIC ' - 'LANGUAGE java AS $$return key+val;$$' - ''.format(ksn=self.keyspace_name, - ftn=self.func_name), - fn.as_cql_query()) - self.session.execute('DROP FUNCTION {}.{}'.format(self.keyspace_name, - self.func_name)) - self.session.execute(fn.as_cql_query()) - - def _create_func_for_aggregate(self): - self.session.execute(""" - CREATE FUNCTION {ksn}.{ftn}(key int, val int) - RETURNS NULL ON NULL INPUT - RETURNS int - DETERMINISTIC - LANGUAGE java AS 'return key+val;'; - """.format(ksn=self.keyspace_name, - ftn=self.func_name)) - - def test_deterministic_aggregate(self): - self._create_func_for_aggregate() - self.session.execute(""" - CREATE AGGREGATE {ksn}.{an} - SFUNC {ftn} - STYPE int - INITCOND 0 - DETERMINISTIC - """.format(ksn=self.keyspace_name, - ftn=self.func_name, - an=self.agg_name)) - ag = self._populated_ks_meta_attr('aggregates')[self.agg_name] - self.assertTrue(ag.deterministic) - self.assertEqual( - 'CREATE AGGREGATE {ksn}.{an} SFUNC ' - '{ftn} STYPE int INITCOND 0 DETERMINISTIC' - ''.format(ksn=self.keyspace_name, - ftn=self.func_name, - an=self.agg_name), - ag.as_cql_query()) - self.session.execute('DROP AGGREGATE {}.{}'.format(self.keyspace_name, - self.agg_name)) - self.session.execute(ag.as_cql_query()) - - def test_nondeterministic_aggregate(self): - self._create_func_for_aggregate() - self.session.execute(""" - CREATE AGGREGATE {ksn}.{an} - SFUNC {ftn} - STYPE int - INITCOND 0 - """.format(ksn=self.keyspace_name, - ftn=self.func_name, - an=self.agg_name)) - ag = self._populated_ks_meta_attr('aggregates')[self.agg_name] - self.assertFalse(ag.deterministic) - self.assertEqual( - 'CREATE AGGREGATE {ksn}.{an} SFUNC ' - '{ftn} STYPE int INITCOND 0' - ''.format(ksn=self.keyspace_name, - ftn=self.func_name, - an=self.agg_name), - ag.as_cql_query()) - self.session.execute('DROP AGGREGATE {}.{}'.format(self.keyspace_name, - self.agg_name)) - self.session.execute(ag.as_cql_query()) - - -@requiredse -class RLACMetadataTests(BasicSharedKeyspaceUnitTestCase): - - @classmethod - def setUpClass(cls): - if DSE_VERSION: - super(RLACMetadataTests, cls).setUpClass() - - @classmethod - def tearDownClass(cls): - if DSE_VERSION: - super(RLACMetadataTests, cls).setUpClass() - - @greaterthanorequaldse51 - def test_rlac_on_table(self): - """ - Checks to ensure that the RLAC table extension appends the proper cql on export to tables - - @since 3.20 - @jira_ticket PYTHON-638 - @expected_result Invalid hosts on the contact list should be excluded - - @test_category metadata - """ - self.session.execute("CREATE TABLE {0}.reports (" - " report_user text, " - " report_number int, " - " report_month int, " - " report_year int, " - " report_text text," - " PRIMARY KEY (report_user, report_number))".format(self.keyspace_name)) - restrict_cql = "RESTRICT ROWS ON {0}.reports USING report_user".format(self.keyspace_name) - self.session.execute(restrict_cql) - table_meta = self.cluster.metadata.keyspaces[self.keyspace_name].tables['reports'] - self.assertTrue(restrict_cql in table_meta.export_as_string()) - - @unittest.skip("Dse 5.1 doesn't support MV and RLAC remove after update") - @greaterthanorequaldse51 - def test_rlac_on_mv(self): - """ - Checks to ensure that the RLAC table extension appends the proper cql to export on mV's - - @since 3.20 - @jira_ticket PYTHON-682 - @expected_result Invalid hosts on the contact list should be excluded - - @test_category metadata - """ - self.session.execute("CREATE TABLE {0}.reports2 (" - " report_user text, " - " report_number int, " - " report_month int, " - " report_year int, " - " report_text text," - " PRIMARY KEY (report_user, report_number))".format(self.keyspace_name)) - self.session.execute("CREATE MATERIALIZED VIEW {0}.reports_by_year AS " - " SELECT report_year, report_user, report_number, report_text FROM {0}.reports2 " - " WHERE report_user IS NOT NULL AND report_number IS NOT NULL AND report_year IS NOT NULL " - " PRIMARY KEY ((report_year, report_user), report_number)".format(self.keyspace_name)) - - restrict_cql_table = "RESTRICT ROWS ON {0}.reports2 USING report_user".format(self.keyspace_name) - self.session.execute(restrict_cql_table) - restrict_cql_view = "RESTRICT ROWS ON {0}.reports_by_year USING report_user".format(self.keyspace_name) - self.session.execute(restrict_cql_view) - table_cql = self.cluster.metadata.keyspaces[self.keyspace_name].tables['reports2'].export_as_string() - view_cql = self.cluster.metadata.keyspaces[self.keyspace_name].tables['reports2'].views["reports_by_year"].export_as_string() - self.assertTrue(restrict_cql_table in table_cql) - self.assertTrue(restrict_cql_view in table_cql) - self.assertTrue(restrict_cql_view in view_cql) - self.assertTrue(restrict_cql_table not in view_cql) - - -@requiredse -class NodeSyncMetadataTests(BasicSharedKeyspaceUnitTestCase): - - @classmethod - def setUpClass(cls): - if DSE_VERSION: - super(NodeSyncMetadataTests, cls).setUpClass() - - @classmethod - def tearDownClass(cls): - if DSE_VERSION: - super(NodeSyncMetadataTests, cls).setUpClass() - - @greaterthanorequaldse60 - def test_nodesync_on_table(self): - """ - Checks to ensure that nodesync is visible through driver metadata - - @since 3.20 - @jira_ticket PYTHON-799 - @expected_result nodesync should be enabled - - @test_category metadata - """ - self.session.execute("CREATE TABLE {0}.reports (" - " report_user text PRIMARY KEY" - ") WITH nodesync = {{" - "'enabled': 'true', 'deadline_target_sec' : 86400 }};".format( - self.keyspace_name - )) - table_meta = self.cluster.metadata.keyspaces[self.keyspace_name].tables['reports'] - self.assertIn('nodesync =', table_meta.export_as_string()) - self.assertIn('nodesync', table_meta.options) - - -@greaterthanorequaldse68 -class GraphMetadataTests(BasicExistingKeyspaceUnitTestCase): - """ - Various tests to ensure that graph metadata are visible through driver metadata - @since DSE6.8 - @jira_ticket PYTHON-996 - @expected_result graph metadata are fetched - @test_category metadata - """ - - @classmethod - def setUpClass(cls): - if DSE_VERSION and DSE_VERSION >= Version('6.8'): - super(GraphMetadataTests, cls).setUpClass() - cls.session.execute(""" - CREATE KEYSPACE ks_no_graph_engine WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}; - """) - cls.session.execute(""" - CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1} and graph_engine = 'Core'; - """ % (cls.ks_name,)) - - cls.session.execute(""" - CREATE TABLE %s.person (name text PRIMARY KEY) WITH VERTEX LABEL; - """ % (cls.ks_name,)) - - cls.session.execute(""" - CREATE TABLE %s.software(company text, name text, version int, PRIMARY KEY((company, name), version)) WITH VERTEX LABEL rocksolidsoftware; - """ % (cls.ks_name,)) - - cls.session.execute(""" - CREATE TABLE %s.contributors (contributor text, company_name text, software_name text, software_version int, - PRIMARY KEY (contributor, company_name, software_name, software_version) ) - WITH CLUSTERING ORDER BY (company_name ASC, software_name ASC, software_version ASC) - AND EDGE LABEL contrib FROM person(contributor) TO rocksolidsoftware((company_name, software_name), software_version); - """ % (cls.ks_name,)) - - @classmethod - def tearDownClass(cls): - if DSE_VERSION and DSE_VERSION >= Version('6.8'): - cls.session.execute('DROP KEYSPACE {0}'.format('ks_no_graph_engine')) - cls.session.execute('DROP KEYSPACE {0}'.format(cls.ks_name)) - cls.cluster.shutdown() - - def test_keyspace_metadata(self): - self.assertIsNone(self.cluster.metadata.keyspaces['ks_no_graph_engine'].graph_engine, None) - self.assertEqual(self.cluster.metadata.keyspaces[self.ks_name].graph_engine, 'Core') - - def test_keyspace_metadata_alter_graph_engine(self): - self.session.execute("ALTER KEYSPACE %s WITH graph_engine = 'Tinker'" % (self.ks_name,)) - self.assertEqual(self.cluster.metadata.keyspaces[self.ks_name].graph_engine, 'Tinker') - self.session.execute("ALTER KEYSPACE %s WITH graph_engine = 'Core'" % (self.ks_name,)) - self.assertEqual(self.cluster.metadata.keyspaces[self.ks_name].graph_engine, 'Core') - - def test_vertex_metadata(self): - vertex_meta = self.cluster.metadata.keyspaces[self.ks_name].tables['person'].vertex - self.assertEqual(vertex_meta.keyspace_name, self.ks_name) - self.assertEqual(vertex_meta.table_name, 'person') - self.assertEqual(vertex_meta.label_name, 'person') - - vertex_meta = self.cluster.metadata.keyspaces[self.ks_name].tables['software'].vertex - self.assertEqual(vertex_meta.keyspace_name, self.ks_name) - self.assertEqual(vertex_meta.table_name, 'software') - self.assertEqual(vertex_meta.label_name, 'rocksolidsoftware') - - def test_edge_metadata(self): - edge_meta = self.cluster.metadata.keyspaces[self.ks_name].tables['contributors'].edge - self.assertEqual(edge_meta.keyspace_name, self.ks_name) - self.assertEqual(edge_meta.table_name, 'contributors') - self.assertEqual(edge_meta.label_name, 'contrib') - self.assertEqual(edge_meta.from_table, 'person') - self.assertEqual(edge_meta.from_label, 'person') - self.assertEqual(edge_meta.from_partition_key_columns, ['contributor']) - self.assertEqual(edge_meta.from_clustering_columns, []) - self.assertEqual(edge_meta.to_table, 'software') - self.assertEqual(edge_meta.to_label, 'rocksolidsoftware') - self.assertEqual(edge_meta.to_partition_key_columns, ['company_name', 'software_name']) - self.assertEqual(edge_meta.to_clustering_columns, ['software_version']) - - -@greaterthanorequaldse68 -class GraphMetadataSchemaErrorTests(BasicExistingKeyspaceUnitTestCase): - """ - Test that we can connect when the graph schema is broken. - """ - - def test_connection_on_graph_schema_error(self): - self.session = self.cluster.connect() - - self.session.execute(""" - CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1} and graph_engine = 'Core'; - """ % (self.ks_name,)) - - self.session.execute(""" - CREATE TABLE %s.person (name text PRIMARY KEY) WITH VERTEX LABEL; - """ % (self.ks_name,)) - - self.session.execute(""" - CREATE TABLE %s.software(company text, name text, version int, PRIMARY KEY((company, name), version)) WITH VERTEX LABEL rocksolidsoftware; - """ % (self.ks_name,)) - - self.session.execute(""" - CREATE TABLE %s.contributors (contributor text, company_name text, software_name text, software_version int, - PRIMARY KEY (contributor, company_name, software_name, software_version) ) - WITH CLUSTERING ORDER BY (company_name ASC, software_name ASC, software_version ASC) - AND EDGE LABEL contrib FROM person(contributor) TO rocksolidsoftware((company_name, software_name), software_version); - """ % (self.ks_name,)) - - self.session.execute('TRUNCATE system_schema.vertices') - TestCluster().connect().shutdown() diff --git a/tests/integration/advanced/test_auth.py b/tests/integration/advanced/test_auth.py deleted file mode 100644 index 438d4e8018..0000000000 --- a/tests/integration/advanced/test_auth.py +++ /dev/null @@ -1,532 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import unittest -import logging -import os -import subprocess -import time - -from ccmlib.dse_cluster import DseCluster -from nose.plugins.attrib import attr -from packaging.version import Version - -from cassandra.auth import (DSEGSSAPIAuthProvider, DSEPlainTextAuthProvider, - SaslAuthProvider, TransitionalModePlainTextAuthProvider) -from cassandra.cluster import EXEC_PROFILE_GRAPH_DEFAULT, NoHostAvailable -from cassandra.protocol import Unauthorized -from cassandra.query import SimpleStatement -from tests.integration import (get_cluster, greaterthanorequaldse51, - remove_cluster, requiredse, DSE_VERSION, TestCluster) -from tests.integration.advanced import ADS_HOME, use_single_node_with_graph -from tests.integration.advanced.graph import reset_graph, ClassicGraphFixtures - - -log = logging.getLogger(__name__) - - -def setup_module(): - if DSE_VERSION: - use_single_node_with_graph() - - -def teardown_module(): - if DSE_VERSION: - remove_cluster() # this test messes with config - - -def wait_role_manager_setup_then_execute(session, statements): - for s in statements: - exc = None - for attempt in range(3): - try: - session.execute(s) - break - except Exception as e: - exc = e - time.sleep(5) - else: # if we didn't reach `break` - if exc is not None: - raise exc - - -@attr('long') -@requiredse -class BasicDseAuthTest(unittest.TestCase): - - @classmethod - def setUpClass(self): - """ - This will setup the necessary infrastructure to run our authentication tests. It requres the ADS_HOME environment variable - and our custom embedded apache directory server jar in order to run. - """ - if not DSE_VERSION: - return - - clear_kerberos_tickets() - self.cluster = None - - # Setup variables for various keytab and other files - self.conf_file_dir = os.path.join(ADS_HOME, "conf/") - self.krb_conf = os.path.join(self.conf_file_dir, "krb5.conf") - self.dse_keytab = os.path.join(self.conf_file_dir, "dse.keytab") - self.dseuser_keytab = os.path.join(self.conf_file_dir, "dseuser.keytab") - self.cassandra_keytab = os.path.join(self.conf_file_dir, "cassandra.keytab") - self.bob_keytab = os.path.join(self.conf_file_dir, "bob.keytab") - self.charlie_keytab = os.path.join(self.conf_file_dir, "charlie.keytab") - actual_jar = os.path.join(ADS_HOME, "embedded-ads.jar") - - # Create configuration directories if they don't already exists - if not os.path.exists(self.conf_file_dir): - os.makedirs(self.conf_file_dir) - if not os.path.exists(actual_jar): - raise RuntimeError('could not find {}'.format(actual_jar)) - log.warning("Starting adserver") - # Start the ADS, this will create the keytab con configuration files listed above - self.proc = subprocess.Popen(['java', '-jar', actual_jar, '-k', '--confdir', self.conf_file_dir], shell=False) - time.sleep(10) - # TODO poll for server to come up - - log.warning("Starting adserver started") - ccm_cluster = get_cluster() - log.warning("fetching tickets") - # Stop cluster if running and configure it with the correct options - ccm_cluster.stop() - if isinstance(ccm_cluster, DseCluster): - # Setup kerberos options in cassandra.yaml - config_options = {'kerberos_options': {'keytab': self.dse_keytab, - 'service_principal': 'dse/_HOST@DATASTAX.COM', - 'qop': 'auth'}, - 'authentication_options': {'enabled': 'true', - 'default_scheme': 'kerberos', - 'scheme_permissions': 'true', - 'allow_digest_with_kerberos': 'true', - 'plain_text_without_ssl': 'warn', - 'transitional_mode': 'disabled'}, - 'authorization_options': {'enabled': 'true'}} - - krb5java = "-Djava.security.krb5.conf=" + self.krb_conf - # Setup dse authenticator in cassandra.yaml - ccm_cluster.set_configuration_options({ - 'authenticator': 'com.datastax.bdp.cassandra.auth.DseAuthenticator', - 'authorizer': 'com.datastax.bdp.cassandra.auth.DseAuthorizer' - }) - ccm_cluster.set_dse_configuration_options(config_options) - ccm_cluster.start(wait_for_binary_proto=True, wait_other_notice=True, jvm_args=[krb5java]) - else: - log.error("Cluster is not dse cluster test will fail") - - @classmethod - def tearDownClass(self): - """ - Terminates running ADS (Apache directory server). - """ - if not DSE_VERSION: - return - - self.proc.terminate() - - def tearDown(self): - """ - This will clear any existing kerberos tickets by using kdestroy - """ - clear_kerberos_tickets() - if self.cluster: - self.cluster.shutdown() - - def refresh_kerberos_tickets(self, keytab_file, user_name, krb_conf): - """ - Fetches a new ticket for using the keytab file and username provided. - """ - self.ads_pid = subprocess.call(['kinit', '-t', keytab_file, user_name], env={'KRB5_CONFIG': krb_conf}, shell=False) - - def connect_and_query(self, auth_provider, query=None): - """ - Runs a simple system query with the auth_provided specified. - """ - os.environ['KRB5_CONFIG'] = self.krb_conf - self.cluster = TestCluster(auth_provider=auth_provider) - self.session = self.cluster.connect() - query = query if query else "SELECT * FROM system.local WHERE key='local'" - statement = SimpleStatement(query) - rs = self.session.execute(statement) - return rs - - def test_should_not_authenticate_with_bad_user_ticket(self): - """ - This tests will attempt to authenticate with a user that has a valid ticket, but is not a valid dse user. - @since 3.20 - @jira_ticket PYTHON-457 - @test_category dse auth - @expected_result NoHostAvailable exception should be thrown - - """ - self.refresh_kerberos_tickets(self.dseuser_keytab, "dseuser@DATASTAX.COM", self.krb_conf) - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"]) - self.assertRaises(NoHostAvailable, self.connect_and_query, auth_provider) - - def test_should_not_athenticate_without_ticket(self): - """ - This tests will attempt to authenticate with a user that is valid but has no ticket - @since 3.20 - @jira_ticket PYTHON-457 - @test_category dse auth - @expected_result NoHostAvailable exception should be thrown - - """ - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"]) - self.assertRaises(NoHostAvailable, self.connect_and_query, auth_provider) - - def test_connect_with_kerberos(self): - """ - This tests will attempt to authenticate with a user that is valid and has a ticket - @since 3.20 - @jira_ticket PYTHON-457 - @test_category dse auth - @expected_result Client should be able to connect and run a basic query - - """ - self.refresh_kerberos_tickets(self.cassandra_keytab, "cassandra@DATASTAX.COM", self.krb_conf) - auth_provider = DSEGSSAPIAuthProvider() - rs = self.connect_and_query(auth_provider) - self.assertIsNotNone(rs) - connections = [c for holders in self.cluster.get_connection_holders() for c in holders.get_connections()] - # Check to make sure our server_authenticator class is being set appropriate - for connection in connections: - self.assertTrue('DseAuthenticator' in connection.authenticator.server_authenticator_class) - - def test_connect_with_kerberos_and_graph(self): - """ - This tests will attempt to authenticate with a user and execute a graph query - @since 3.20 - @jira_ticket PYTHON-457 - @test_category dse auth - @expected_result Client should be able to connect and run a basic graph query with authentication - - """ - self.refresh_kerberos_tickets(self.cassandra_keytab, "cassandra@DATASTAX.COM", self.krb_conf) - - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"]) - rs = self.connect_and_query(auth_provider) - self.assertIsNotNone(rs) - reset_graph(self.session, self._testMethodName.lower()) - profiles = self.cluster.profile_manager.profiles - profiles[EXEC_PROFILE_GRAPH_DEFAULT].graph_options.graph_name = self._testMethodName.lower() - self.session.execute_graph(ClassicGraphFixtures.classic()) - - rs = self.session.execute_graph('g.V()') - self.assertIsNotNone(rs) - - def test_connect_with_kerberos_host_not_resolved(self): - """ - This tests will attempt to authenticate with IP, this will fail on osx. - The success or failure of this test is dependent on a reverse dns lookup which can be impacted by your environment - if it fails don't panic. - @since 3.20 - @jira_ticket PYTHON-566 - @test_category dse auth - @expected_result Client should error when ip is used - - """ - self.refresh_kerberos_tickets(self.cassandra_keytab, "cassandra@DATASTAX.COM", self.krb_conf) - DSEGSSAPIAuthProvider(service='dse', qops=["auth"], resolve_host_name=False) - - def test_connect_with_explicit_principal(self): - """ - This tests will attempt to authenticate using valid and invalid user principals - @since 3.20 - @jira_ticket PYTHON-574 - @test_category dse auth - @expected_result Client principals should be used by the underlying mechanism - - """ - - # Connect with valid principal - self.refresh_kerberos_tickets(self.cassandra_keytab, "cassandra@DATASTAX.COM", self.krb_conf) - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"], principal="cassandra@DATASTAX.COM") - self.connect_and_query(auth_provider) - connections = [c for holders in self.cluster.get_connection_holders() for c in holders.get_connections()] - - # Check to make sure our server_authenticator class is being set appropriate - for connection in connections: - self.assertTrue('DseAuthenticator' in connection.authenticator.server_authenticator_class) - - # Use invalid principal - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"], principal="notauser@DATASTAX.COM") - self.assertRaises(NoHostAvailable, self.connect_and_query, auth_provider) - - @greaterthanorequaldse51 - def test_proxy_login_with_kerberos(self): - """ - Test that the proxy login works with kerberos. - """ - # Set up users for proxy login test - self._setup_for_proxy() - - query = "select * from testkrbproxy.testproxy" - - # Try normal login with Charlie - self.refresh_kerberos_tickets(self.charlie_keytab, "charlie@DATASTAX.COM", self.krb_conf) - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"], principal="charlie@DATASTAX.COM") - self.connect_and_query(auth_provider, query=query) - - # Try proxy login with bob - self.refresh_kerberos_tickets(self.bob_keytab, "bob@DATASTAX.COM", self.krb_conf) - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"], principal="bob@DATASTAX.COM", - authorization_id='charlie@DATASTAX.COM') - self.connect_and_query(auth_provider, query=query) - - # Try logging with bob without mentioning charlie - self.refresh_kerberos_tickets(self.bob_keytab, "bob@DATASTAX.COM", self.krb_conf) - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"], principal="bob@DATASTAX.COM") - self.assertRaises(Unauthorized, self.connect_and_query, auth_provider, query=query) - - self._remove_proxy_setup() - - @greaterthanorequaldse51 - def test_proxy_login_with_kerberos_forbidden(self): - """ - Test that the proxy login fail when proxy role is not granted - """ - # Set up users for proxy login test - self._setup_for_proxy(False) - query = "select * from testkrbproxy.testproxy" - - # Try normal login with Charlie - self.refresh_kerberos_tickets(self.bob_keytab, "bob@DATASTAX.COM", self.krb_conf) - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"], principal="bob@DATASTAX.COM", - authorization_id='charlie@DATASTAX.COM') - self.assertRaises(NoHostAvailable, self.connect_and_query, auth_provider, query=query) - - self.refresh_kerberos_tickets(self.bob_keytab, "bob@DATASTAX.COM", self.krb_conf) - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"], principal="bob@DATASTAX.COM") - self.assertRaises(Unauthorized, self.connect_and_query, auth_provider, query=query) - - self._remove_proxy_setup() - - def _remove_proxy_setup(self): - os.environ['KRB5_CONFIG'] = self.krb_conf - self.refresh_kerberos_tickets(self.cassandra_keytab, "cassandra@DATASTAX.COM", self.krb_conf) - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"], principal='cassandra@DATASTAX.COM') - cluster = TestCluster(auth_provider=auth_provider) - session = cluster.connect() - - session.execute("REVOKE PROXY.LOGIN ON ROLE '{0}' FROM '{1}'".format('charlie@DATASTAX.COM', 'bob@DATASTAX.COM')) - - session.execute("DROP ROLE IF EXISTS '{0}';".format('bob@DATASTAX.COM')) - session.execute("DROP ROLE IF EXISTS '{0}';".format('charlie@DATASTAX.COM')) - - # Create a keyspace and allow only charlie to query it. - - session.execute("DROP KEYSPACE testkrbproxy") - - cluster.shutdown() - - def _setup_for_proxy(self, grant=True): - os.environ['KRB5_CONFIG'] = self.krb_conf - self.refresh_kerberos_tickets(self.cassandra_keytab, "cassandra@DATASTAX.COM", self.krb_conf) - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"], principal='cassandra@DATASTAX.COM') - cluster = TestCluster(auth_provider=auth_provider) - session = cluster.connect() - - stmts = [ - "CREATE ROLE IF NOT EXISTS '{0}' WITH LOGIN = TRUE;".format('bob@DATASTAX.COM'), - "CREATE ROLE IF NOT EXISTS '{0}' WITH LOGIN = TRUE;".format('bob@DATASTAX.COM'), - "GRANT EXECUTE ON ALL AUTHENTICATION SCHEMES to 'bob@DATASTAX.COM'", - "CREATE ROLE IF NOT EXISTS '{0}' WITH LOGIN = TRUE;".format('charlie@DATASTAX.COM'), - "GRANT EXECUTE ON ALL AUTHENTICATION SCHEMES to 'charlie@DATASTAX.COM'", - # Create a keyspace and allow only charlie to query it. - "CREATE KEYSPACE testkrbproxy WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", - "CREATE TABLE testkrbproxy.testproxy (id int PRIMARY KEY, value text)", - "GRANT ALL PERMISSIONS ON KEYSPACE testkrbproxy to '{0}'".format('charlie@DATASTAX.COM'), - ] - - if grant: - stmts.append("GRANT PROXY.LOGIN ON ROLE '{0}' to '{1}'".format('charlie@DATASTAX.COM', 'bob@DATASTAX.COM')) - - wait_role_manager_setup_then_execute(session, stmts) - - cluster.shutdown() - - -def clear_kerberos_tickets(): - subprocess.call(['kdestroy'], shell=False) - - -@attr('long') -@requiredse -class BaseDseProxyAuthTest(unittest.TestCase): - - @classmethod - def setUpClass(self): - """ - This will setup the necessary infrastructure to run unified authentication tests. - """ - if not DSE_VERSION or DSE_VERSION < Version('5.1'): - return - self.cluster = None - - ccm_cluster = get_cluster() - # Stop cluster if running and configure it with the correct options - ccm_cluster.stop() - if isinstance(ccm_cluster, DseCluster): - # Setup dse options in dse.yaml - config_options = {'authentication_options': {'enabled': 'true', - 'default_scheme': 'internal', - 'scheme_permissions': 'true', - 'transitional_mode': 'normal'}, - 'authorization_options': {'enabled': 'true'} - } - - # Setup dse authenticator in cassandra.yaml - ccm_cluster.set_configuration_options({ - 'authenticator': 'com.datastax.bdp.cassandra.auth.DseAuthenticator', - 'authorizer': 'com.datastax.bdp.cassandra.auth.DseAuthorizer' - }) - ccm_cluster.set_dse_configuration_options(config_options) - ccm_cluster.start(wait_for_binary_proto=True, wait_other_notice=True) - else: - log.error("Cluster is not dse cluster test will fail") - - # Create users and test keyspace - self.user_role = 'user1' - self.server_role = 'server' - self.root_cluster = TestCluster(auth_provider=DSEPlainTextAuthProvider('cassandra', 'cassandra')) - self.root_session = self.root_cluster.connect() - - stmts = [ - "CREATE USER {0} WITH PASSWORD '{1}'".format(self.server_role, self.server_role), - "CREATE USER {0} WITH PASSWORD '{1}'".format(self.user_role, self.user_role), - "CREATE KEYSPACE testproxy WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", - "CREATE TABLE testproxy.testproxy (id int PRIMARY KEY, value text)", - "GRANT ALL PERMISSIONS ON KEYSPACE testproxy to {0}".format(self.user_role) - ] - - wait_role_manager_setup_then_execute(self.root_session, stmts) - - @classmethod - def tearDownClass(self): - """ - Shutdown the root session. - """ - if not DSE_VERSION or DSE_VERSION < Version('5.1'): - return - self.root_session.execute('DROP KEYSPACE testproxy;') - self.root_session.execute('DROP USER {0}'.format(self.user_role)) - self.root_session.execute('DROP USER {0}'.format(self.server_role)) - self.root_cluster.shutdown() - - def tearDown(self): - """ - Shutdown the cluster and reset proxy permissions - """ - self.cluster.shutdown() - - self.root_session.execute("REVOKE PROXY.LOGIN ON ROLE {0} from {1}".format(self.user_role, self.server_role)) - self.root_session.execute("REVOKE PROXY.EXECUTE ON ROLE {0} from {1}".format(self.user_role, self.server_role)) - - def grant_proxy_login(self): - """ - Grant PROXY.LOGIN permission on a role to a specific user. - """ - self.root_session.execute("GRANT PROXY.LOGIN on role {0} to {1}".format(self.user_role, self.server_role)) - - def grant_proxy_execute(self): - """ - Grant PROXY.EXECUTE permission on a role to a specific user. - """ - self.root_session.execute("GRANT PROXY.EXECUTE on role {0} to {1}".format(self.user_role, self.server_role)) - - -@attr('long') -@greaterthanorequaldse51 -class DseProxyAuthTest(BaseDseProxyAuthTest): - """ - Tests Unified Auth. Proxy Login using SASL and Proxy Execute. - """ - - @classmethod - def get_sasl_options(self, mechanism='PLAIN'): - sasl_options = { - "service": 'dse', - "username": 'server', - "mechanism": mechanism, - 'password': self.server_role, - 'authorization_id': self.user_role - } - return sasl_options - - def connect_and_query(self, auth_provider, execute_as=None, query="SELECT * FROM testproxy.testproxy"): - self.cluster = TestCluster(auth_provider=auth_provider) - self.session = self.cluster.connect() - rs = self.session.execute(query, execute_as=execute_as) - return rs - - def test_proxy_login_forbidden(self): - """ - Test that a proxy login is forbidden by default for a user. - @since 3.20 - @jira_ticket PYTHON-662 - @test_category dse auth - @expected_result connect and query should not be allowed - """ - auth_provider = SaslAuthProvider(**self.get_sasl_options()) - with self.assertRaises(Unauthorized): - self.connect_and_query(auth_provider) - - def test_proxy_login_allowed(self): - """ - Test that a proxy login is allowed with proper permissions. - @since 3.20 - @jira_ticket PYTHON-662 - @test_category dse auth - @expected_result connect and query should be allowed - """ - auth_provider = SaslAuthProvider(**self.get_sasl_options()) - self.grant_proxy_login() - self.connect_and_query(auth_provider) - - def test_proxy_execute_forbidden(self): - """ - Test that a proxy execute is forbidden by default for a user. - @since 3.20 - @jira_ticket PYTHON-662 - @test_category dse auth - @expected_result connect and query should not be allowed - """ - auth_provider = DSEPlainTextAuthProvider(self.server_role, self.server_role) - with self.assertRaises(Unauthorized): - self.connect_and_query(auth_provider, execute_as=self.user_role) - - def test_proxy_execute_allowed(self): - """ - Test that a proxy execute is allowed with proper permissions. - @since 3.20 - @jira_ticket PYTHON-662 - @test_category dse auth - @expected_result connect and query should be allowed - """ - auth_provider = DSEPlainTextAuthProvider(self.server_role, self.server_role) - self.grant_proxy_execute() - self.connect_and_query(auth_provider, execute_as=self.user_role) - - def test_connection_with_transitional_mode(self): - """ - Test that the driver can connect using TransitionalModePlainTextAuthProvider - @since 3.20 - @jira_ticket PYTHON-831 - @test_category dse auth - @expected_result connect and query should be allowed - """ - auth_provider = TransitionalModePlainTextAuthProvider() - self.assertIsNotNone(self.connect_and_query(auth_provider, query="SELECT * from system.local WHERE key='local'")) diff --git a/tests/integration/advanced/test_cont_paging.py b/tests/integration/advanced/test_cont_paging.py deleted file mode 100644 index 99de82647d..0000000000 --- a/tests/integration/advanced/test_cont_paging.py +++ /dev/null @@ -1,243 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from tests.integration import use_singledc, greaterthanorequaldse51, BasicSharedKeyspaceUnitTestCaseRF3WM, \ - DSE_VERSION, ProtocolVersion, greaterthanorequaldse60, requiredse, TestCluster - -import logging -log = logging.getLogger(__name__) - -import unittest - -from itertools import cycle, count -from packaging.version import Version -import time - -from cassandra.cluster import ExecutionProfile, ContinuousPagingOptions -from cassandra.concurrent import execute_concurrent -from cassandra.query import SimpleStatement - - -def setup_module(): - if DSE_VERSION: - use_singledc() - - -@requiredse -class BaseContPagingTests(): - @classmethod - def setUpClass(cls): - if not DSE_VERSION or DSE_VERSION < cls.required_dse_version: - return - - cls.execution_profiles = {"CONTDEFAULT": ExecutionProfile(continuous_paging_options=ContinuousPagingOptions()), - "ONEPAGE": ExecutionProfile( - continuous_paging_options=ContinuousPagingOptions(max_pages=1)), - "MANYPAGES": ExecutionProfile( - continuous_paging_options=ContinuousPagingOptions(max_pages=10)), - "BYTES": ExecutionProfile(continuous_paging_options=ContinuousPagingOptions( - page_unit=ContinuousPagingOptions.PagingUnit.BYTES)), - "SLOW": ExecutionProfile( - continuous_paging_options=ContinuousPagingOptions(max_pages_per_second=1)), } - cls.sane_eps = ["CONTDEFAULT", "BYTES"] - - @classmethod - def tearDownClass(cls): - if not DSE_VERSION or DSE_VERSION < cls.required_dse_version: - return - - @classmethod - def create_cluster(cls): - - cls.cluster_with_profiles = TestCluster(protocol_version=cls.protocol_version, execution_profiles=cls.execution_profiles) - - cls.session_with_profiles = cls.cluster_with_profiles.connect(wait_for_all_pools=True) - statements_and_params = zip( - cycle(["INSERT INTO " + cls.ks_name + "." + cls.ks_name + " (k, v) VALUES (%s, 0)"]), - [(i,) for i in range(150)]) - execute_concurrent(cls.session_with_profiles, list(statements_and_params)) - - cls.select_all_statement = "SELECT * FROM {0}.{0}".format(cls.ks_name) - - def test_continous_paging(self): - """ - Test to ensure that various continuous paging schemes return the full set of results. - @since 3.20 - @jira_ticket PYTHON-615 - @expected_result various continous paging options should fetch all the results - - @test_category queries - """ - for ep in self.execution_profiles.keys(): - results = list(self.session_with_profiles.execute(self.select_all_statement, execution_profile= ep)) - self.assertEqual(len(results), 150) - - - def test_page_fetch_size(self): - """ - Test to ensure that continuous paging works appropriately with fetch size. - @since 3.20 - @jira_ticket PYTHON-615 - @expected_result continuous paging options should work sensibly with various fetch size - - @test_category queries - """ - - # Since we fetch one page at a time results should match fetch size - for fetch_size in (2, 3, 7, 10, 99, 100, 101, 150): - self.session_with_profiles.default_fetch_size = fetch_size - results = list(self.session_with_profiles.execute(self.select_all_statement, execution_profile= "ONEPAGE")) - self.assertEqual(len(results), fetch_size) - - # Since we fetch ten pages at a time results should match fetch size * 10 - for fetch_size in (2, 3, 7, 10, 15): - self.session_with_profiles.default_fetch_size = fetch_size - results = list(self.session_with_profiles.execute(self.select_all_statement, execution_profile= "MANYPAGES")) - self.assertEqual(len(results), fetch_size*10) - - # Default settings for continuous paging should be able to fetch all results regardless of fetch size - # Changing the units should, not affect the number of results, if max_pages is not set - for profile in self.sane_eps: - for fetch_size in (2, 3, 7, 10, 15): - self.session_with_profiles.default_fetch_size = fetch_size - results = list(self.session_with_profiles.execute(self.select_all_statement, execution_profile= profile)) - self.assertEqual(len(results), 150) - - # This should take around 3 seconds to fetch but should still complete with all results - self.session_with_profiles.default_fetch_size = 50 - results = list(self.session_with_profiles.execute(self.select_all_statement, execution_profile= "SLOW")) - self.assertEqual(len(results), 150) - - def test_paging_cancel(self): - """ - Test to ensure we can cancel a continuous paging session once it's started - @since 3.20 - @jira_ticket PYTHON-615 - @expected_result This query should be canceled before any sizable amount of results can be returned - @test_category queries - """ - - self.session_with_profiles.default_fetch_size = 1 - # This combination should fetch one result a second. We should see a very few results - results = self.session_with_profiles.execute_async(self.select_all_statement, execution_profile= "SLOW") - result_set =results.result() - result_set.cancel_continuous_paging() - result_lst =list(result_set) - self.assertLess(len(result_lst), 2, "Cancel should have aborted fetch immediately") - - def test_con_paging_verify_writes(self): - """ - Test to validate results with a few continuous paging options - @since 3.20 - @jira_ticket PYTHON-615 - @expected_result all results should be returned correctly - @test_category queries - """ - prepared = self.session_with_profiles.prepare(self.select_all_statement) - - - for ep in self.sane_eps: - for fetch_size in (2, 3, 7, 10, 99, 100, 101, 10000): - self.session_with_profiles.default_fetch_size = fetch_size - results = self.session_with_profiles.execute(self.select_all_statement, execution_profile=ep) - result_array = set() - result_set = set() - for result in results: - result_array.add(result.k) - result_set.add(result.v) - - self.assertEqual(set(range(150)), result_array) - self.assertEqual(set([0]), result_set) - - statement = SimpleStatement(self.select_all_statement) - results = self.session_with_profiles.execute(statement, execution_profile=ep) - result_array = set() - result_set = set() - for result in results: - result_array.add(result.k) - result_set.add(result.v) - - self.assertEqual(set(range(150)), result_array) - self.assertEqual(set([0]), result_set) - - results = self.session_with_profiles.execute(prepared, execution_profile=ep) - result_array = set() - result_set = set() - for result in results: - result_array.add(result.k) - result_set.add(result.v) - - self.assertEqual(set(range(150)), result_array) - self.assertEqual(set([0]), result_set) - - def test_can_get_results_when_no_more_pages(self): - """ - Test to validate that the resutls can be fetched when - has_more_pages is False - @since 3.20 - @jira_ticket PYTHON-946 - @expected_result the results can be fetched - @test_category queries - """ - generator_expanded = [] - def get_all_rows(generator, future, generator_expanded): - self.assertFalse(future.has_more_pages) - - generator_expanded.extend(list(generator)) - print("Setting generator_expanded to True") - - future = self.session_with_profiles.execute_async("SELECT * from system.local LIMIT 10", - execution_profile="CONTDEFAULT") - future.add_callback(get_all_rows, future, generator_expanded) - time.sleep(5) - self.assertTrue(generator_expanded) - - -@requiredse -@greaterthanorequaldse51 -class ContPagingTestsDSEV1(BaseContPagingTests, BasicSharedKeyspaceUnitTestCaseRF3WM): - @classmethod - def setUpClass(cls): - cls.required_dse_version = BaseContPagingTests.required_dse_version = Version('5.1') - if not DSE_VERSION or DSE_VERSION < cls.required_dse_version: - return - - BasicSharedKeyspaceUnitTestCaseRF3WM.setUpClass() - BaseContPagingTests.setUpClass() - - cls.protocol_version = ProtocolVersion.DSE_V1 - cls.create_cluster() - - -@requiredse -@greaterthanorequaldse60 -class ContPagingTestsDSEV2(BaseContPagingTests, BasicSharedKeyspaceUnitTestCaseRF3WM): - @classmethod - def setUpClass(cls): - cls.required_dse_version = BaseContPagingTests.required_dse_version = Version('6.0') - if not DSE_VERSION or DSE_VERSION < cls.required_dse_version: - return - - BasicSharedKeyspaceUnitTestCaseRF3WM.setUpClass() - BaseContPagingTests.setUpClass() - - more_profiles = { - "SMALL_QUEUE": ExecutionProfile(continuous_paging_options=ContinuousPagingOptions(max_queue_size=2)), - "BIG_QUEUE": ExecutionProfile(continuous_paging_options=ContinuousPagingOptions(max_queue_size=400)) - } - cls.sane_eps += ["SMALL_QUEUE", "BIG_QUEUE"] - cls.execution_profiles.update(more_profiles) - - cls.protocol_version = ProtocolVersion.DSE_V2 - cls.create_cluster() diff --git a/tests/integration/advanced/test_cqlengine_where_operators.py b/tests/integration/advanced/test_cqlengine_where_operators.py deleted file mode 100644 index b2e4d4ba9e..0000000000 --- a/tests/integration/advanced/test_cqlengine_where_operators.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest - -import os -import time - -from cassandra.cqlengine import columns, connection, models -from cassandra.cqlengine.management import (CQLENG_ALLOW_SCHEMA_MANAGEMENT, - create_keyspace_simple, drop_table, - sync_table) -from cassandra.cqlengine.statements import IsNotNull -from tests.integration import DSE_VERSION, requiredse, CASSANDRA_IP, greaterthanorequaldse60, TestCluster -from tests.integration.advanced import use_single_node_with_graph_and_solr -from tests.integration.cqlengine import DEFAULT_KEYSPACE - - -class SimpleNullableModel(models.Model): - __keyspace__ = DEFAULT_KEYSPACE - partition = columns.Integer(primary_key=True) - nullable = columns.Integer(required=False) - # nullable = columns.Integer(required=False, custom_index=True) - - -def setup_module(): - if DSE_VERSION: - os.environ[CQLENG_ALLOW_SCHEMA_MANAGEMENT] = '1' - use_single_node_with_graph_and_solr() - setup_connection(DEFAULT_KEYSPACE) - create_keyspace_simple(DEFAULT_KEYSPACE, 1) - sync_table(SimpleNullableModel) - - -def setup_connection(keyspace_name): - connection.setup([CASSANDRA_IP], - # consistency=ConsistencyLevel.ONE, - # protocol_version=PROTOCOL_VERSION, - default_keyspace=keyspace_name) - - -def teardown_module(): - if DSE_VERSION: - drop_table(SimpleNullableModel) - - -@requiredse -class IsNotNullTests(unittest.TestCase): - - @classmethod - def setUpClass(cls): - if DSE_VERSION: - cls.cluster = TestCluster() - - @greaterthanorequaldse60 - def test_is_not_null_execution(self): - """ - Verify that CQL statements have correct syntax when executed - If we wanted them to return something meaningful and not a InvalidRequest - we'd have to create an index in search for the column we are using - IsNotNull - - @since 3.20 - @jira_ticket PYTHON-968 - @expected_result InvalidRequest is arisen - - @test_category cqlengine - """ - cluster = TestCluster() - self.addCleanup(cluster.shutdown) - session = cluster.connect() - - SimpleNullableModel.create(partition=1, nullable=2) - SimpleNullableModel.create(partition=2, nullable=None) - - self.addCleanup(session.execute, "DROP SEARCH INDEX ON {}".format( - SimpleNullableModel.column_family_name())) - create_index_stmt = ( - "CREATE SEARCH INDEX ON {} WITH COLUMNS nullable " - "".format(SimpleNullableModel.column_family_name())) - session.execute(create_index_stmt) - - SimpleNullableModel.create(partition=1, nullable=1) - SimpleNullableModel.create(partition=2, nullable=None) - - # TODO: block on indexing more precisely - time.sleep(5) - - self.assertEqual(len(list(SimpleNullableModel.objects.all())), 2) - self.assertEqual( - len(list( - SimpleNullableModel.filter(IsNotNull("nullable"), partition__eq=2) - )), - 0) - self.assertEqual( - len(list( - SimpleNullableModel.filter(IsNotNull("nullable"), partition__eq=1) - )), - 1) diff --git a/tests/integration/advanced/test_geometry.py b/tests/integration/advanced/test_geometry.py deleted file mode 100644 index 6a6737bd50..0000000000 --- a/tests/integration/advanced/test_geometry.py +++ /dev/null @@ -1,249 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from tests.integration import DSE_VERSION, requiredse -from tests.integration.advanced import BasicGeometricUnitTestCase, use_single_node_with_graph -from cassandra.util import OrderedMap, sortedset -from collections import namedtuple - -import unittest -from uuid import uuid1 -from cassandra.util import Point, LineString, Polygon -from cassandra.cqltypes import LineStringType, PointType, PolygonType - - -def setup_module(): - if DSE_VERSION: - use_single_node_with_graph() - - -class AbstractGeometricTypeTest(): - - original_value = "" - - def test_should_insert_simple(self): - """ - This tests will attempt to insert a point, polygon, or line, using simple inline formating. - @since 3.20 - @jira_ticket PYTHON-456 - @test_category dse geometric - @expected_result geometric types should be able to be inserted and queried. - """ - uuid_key = uuid1() - self.session.execute("INSERT INTO tbl (k, g) VALUES (%s, %s)", [uuid_key, self.original_value]) - self.validate('g', uuid_key, self.original_value) - - def test_should_insert_simple_prepared(self): - """ - This tests will attempt to insert a point, polygon, or line, using prepared statements. - @since 3.20 - @jira_ticket PYTHON-456 - @test_category dse geometric - @expected_result geometric types should be able to be inserted and queried. - """ - uuid_key = uuid1() - prepared = self.session.prepare("INSERT INTO tbl (k, g) VALUES (?, ?)") - self.session.execute(prepared, (uuid_key, self.original_value)) - self.validate('g', uuid_key, self.original_value) - - def test_should_insert_simple_prepared_with_bound(self): - """ - This tests will attempt to insert a point, polygon, or line, using prepared statements and bind. - @since 3.20 - @jira_ticket PYTHON-456 - @test_category dse geometric - @expected_result geometric types should be able to be inserted and queried. - """ - uuid_key = uuid1() - prepared = self.session.prepare("INSERT INTO tbl (k, g) VALUES (?, ?)") - bound_statement = prepared.bind((uuid_key, self.original_value)) - self.session.execute(bound_statement) - self.validate('g', uuid_key, self.original_value) - - def test_should_insert_as_list(self): - """ - This tests will attempt to insert a point, polygon, or line, as values of list. - @since 3.20 - @jira_ticket PYTHON-456 - @test_category dse geometric - @expected_result geometric types should be able to be inserted and queried as a list. - """ - uuid_key = uuid1() - prepared = self.session.prepare("INSERT INTO tbl (k, l) VALUES (?, ?)") - bound_statement = prepared.bind((uuid_key, [self.original_value])) - self.session.execute(bound_statement) - self.validate('l', uuid_key, [self.original_value]) - - def test_should_insert_as_set(self): - """ - This tests will attempt to insert a point, polygon, or line, as values of set. - @since 3.20 - @jira_ticket PYTHON-456 - @test_category dse geometric - @expected_result geometric types should be able to be inserted and queried as a set. - """ - uuid_key = uuid1() - prepared = self.session.prepare("INSERT INTO tbl (k, s) VALUES (?, ?)") - bound_statement = prepared.bind((uuid_key, sortedset([self.original_value]))) - self.session.execute(bound_statement) - self.validate('s', uuid_key, sortedset([self.original_value])) - - def test_should_insert_as_map_keys(self): - """ - This tests will attempt to insert a point, polygon, or line, as keys of a map. - @since 3.20 - @jira_ticket PYTHON-456 - @test_category dse geometric - @expected_result geometric types should be able to be inserted and queried as keys of a map. - """ - uuid_key = uuid1() - prepared = self.session.prepare("INSERT INTO tbl (k, m0) VALUES (?, ?)") - bound_statement = prepared.bind((uuid_key, OrderedMap(zip([self.original_value], [1])))) - self.session.execute(bound_statement) - self.validate('m0', uuid_key, OrderedMap(zip([self.original_value], [1]))) - - def test_should_insert_as_map_values(self): - """ - This tests will attempt to insert a point, polygon, or line, as values of a map. - @since 3.20 - @jira_ticket PYTHON-456 - @test_category dse geometric - @expected_result geometric types should be able to be inserted and queried as values of a map. - """ - uuid_key = uuid1() - prepared = self.session.prepare("INSERT INTO tbl (k, m1) VALUES (?, ?)") - bound_statement = prepared.bind((uuid_key, OrderedMap(zip([1], [self.original_value])))) - self.session.execute(bound_statement) - self.validate('m1', uuid_key, OrderedMap(zip([1], [self.original_value]))) - - def test_should_insert_as_tuple(self): - """ - This tests will attempt to insert a point, polygon, or line, as values of a tuple. - @since 3.20 - @jira_ticket PYTHON-456 - @test_category dse geometric - @expected_result geometric types should be able to be inserted and queried as values of a tuple. - """ - uuid_key = uuid1() - prepared = self.session.prepare("INSERT INTO tbl (k, t) VALUES (?, ?)") - bound_statement = prepared.bind((uuid_key, (self.original_value, self.original_value, self.original_value))) - self.session.execute(bound_statement) - self.validate('t', uuid_key, (self.original_value, self.original_value, self.original_value)) - - def test_should_insert_as_udt(self): - """ - This tests will attempt to insert a point, polygon, or line, as members of a udt. - @since 3.20 - @jira_ticket PYTHON-456 - @test_category dse geometric - @expected_result geometric types should be able to be inserted and queried as members of a udt. - """ - UDT1 = namedtuple('udt1', ('g')) - self.cluster.register_user_type(self.ks_name, 'udt1', UDT1) - uuid_key = uuid1() - prepared = self.session.prepare("INSERT INTO tbl (k, u) values (?, ?)") - bound_statement = prepared.bind((uuid_key, UDT1(self.original_value))) - self.session.execute(bound_statement) - rs = self.session.execute("SELECT {0} from {1} where k={2}".format('u', 'tbl', uuid_key)) - retrieved_udt = rs[0]._asdict()['u'] - - self.assertEqual(retrieved_udt.g, self.original_value) - - def test_should_accept_as_partition_key(self): - """ - This tests will attempt to insert a point, polygon, or line, as a partition key. - @since 3.20 - @jira_ticket PYTHON-456 - @test_category dse geometric - @expected_result geometric types should be able to be inserted and queried as a partition key. - """ - prepared = self.session.prepare("INSERT INTO tblpk (k, v) VALUES (?, ?)") - bound_statement = prepared.bind((self.original_value, 1)) - self.session.execute(bound_statement) - rs = self.session.execute("SELECT k, v FROM tblpk") - foundpk = rs[0]._asdict()['k'] - self.assertEqual(foundpk, self.original_value) - - def validate(self, value, key, expected): - """ - Simple utility method used for validation of inserted types. - """ - rs = self.session.execute("SELECT {0} from tbl where k={1}".format(value, key)) - retrieved = rs[0]._asdict()[value] - self.assertEqual(expected, retrieved) - - def test_insert_empty_with_string(self): - """ - This tests will attempt to insert a point, polygon, or line, as Empty - @since 3.20 - @jira_ticket PYTHON-481 - @test_category dse geometric - @expected_result EMPTY as a keyword should be honored - """ - uuid_key = uuid1() - self.session.execute("INSERT INTO tbl (k, g) VALUES (%s, %s)", [uuid_key, self.empty_statement]) - self.validate('g', uuid_key, self.empty_value) - - def test_insert_empty_with_object(self): - """ - This tests will attempt to insert a point, polygon, or line, as Empty - @since 3.20 - @jira_ticket PYTHON-481 - @test_category dse geometric - @expected_result EMPTY as a keyword should be used with empty objects - """ - uuid_key = uuid1() - prepared = self.session.prepare("INSERT INTO tbl (k, g) VALUES (?, ?)") - self.session.execute(prepared, (uuid_key, self.empty_value)) - self.validate('g', uuid_key, self.empty_value) - - -@requiredse -class BasicGeometricPointTypeTest(AbstractGeometricTypeTest, BasicGeometricUnitTestCase): - """ - Runs all the geometric tests against PointType - """ - cql_type_name = "'{0}'".format(PointType.typename) - original_value = Point(.5, .13) - - @unittest.skip("Empty String") - def test_insert_empty_with_string(self): - pass - - @unittest.skip("Empty String") - def test_insert_empty_with_object(self): - pass - - -@requiredse -class BasicGeometricLineStringTypeTest(AbstractGeometricTypeTest, BasicGeometricUnitTestCase): - """ - Runs all the geometric tests against LineStringType - """ - cql_type_name = cql_type_name = "'{0}'".format(LineStringType.typename) - original_value = LineString(((1, 2), (3, 4), (9871234, 1235487215))) - empty_statement = 'LINESTRING EMPTY' - empty_value = LineString() - - -@requiredse -class BasicGeometricPolygonTypeTest(AbstractGeometricTypeTest, BasicGeometricUnitTestCase): - """ - Runs all the geometric tests against PolygonType - """ - cql_type_name = cql_type_name = "'{0}'".format(PolygonType.typename) - original_value = Polygon([(10.0, 10.0), (110.0, 10.0), (110., 110.0), (10., 110.0), (10., 10.0)], [[(20., 20.0), (20., 30.0), (30., 30.0), (30., 20.0), (20., 20.0)], [(40., 20.0), (40., 30.0), (50., 30.0), (50., 20.0), (40., 20.0)]]) - empty_statement = 'POLYGON EMPTY' - empty_value = Polygon() diff --git a/tests/integration/advanced/test_spark.py b/tests/integration/advanced/test_spark.py deleted file mode 100644 index a307913abb..0000000000 --- a/tests/integration/advanced/test_spark.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging - -from cassandra.cluster import EXEC_PROFILE_GRAPH_ANALYTICS_DEFAULT -from cassandra.graph import SimpleGraphStatement -from tests.integration import DSE_VERSION, requiredse -from tests.integration.advanced import use_singledc_wth_graph_and_spark, find_spark_master -from tests.integration.advanced.graph import BasicGraphUnitTestCase, ClassicGraphFixtures -log = logging.getLogger(__name__) - - -def setup_module(): - if DSE_VERSION: - use_singledc_wth_graph_and_spark() - - -@requiredse -class SparkLBTests(BasicGraphUnitTestCase): - """ - Test to validate that analtics query can run in a multi-node enviroment. Also check to to ensure - that the master spark node is correctly targeted when OLAP queries are run - - @since 3.20 - @jira_ticket PYTHON-510 - @expected_result OLAP results should come back correctly, master spark coordinator should always be picked. - @test_category dse graph - """ - def test_spark_analytic_query(self): - self.session.execute_graph(ClassicGraphFixtures.classic()) - spark_master = find_spark_master(self.session) - - # Run multipltle times to ensure we don't round robin - for i in range(3): - to_run = SimpleGraphStatement("g.V().count()") - rs = self.session.execute_graph(to_run, execution_profile=EXEC_PROFILE_GRAPH_ANALYTICS_DEFAULT) - self.assertEqual(rs[0].value, 7) - self.assertEqual(rs.response_future._current_host.address, spark_master) diff --git a/tests/integration/cloud/__init__.py b/tests/integration/cloud/__init__.py deleted file mode 100644 index a6a4ab7a5d..0000000000 --- a/tests/integration/cloud/__init__.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License -from cassandra.cluster import Cluster - -import unittest - -import os -import subprocess - -from tests.integration import CLOUD_PROXY_PATH, USE_CASS_EXTERNAL - - -def setup_package(): - if CLOUD_PROXY_PATH and not USE_CASS_EXTERNAL: - start_cloud_proxy() - - -def teardown_package(): - if not USE_CASS_EXTERNAL: - stop_cloud_proxy() - - -class CloudProxyCluster(unittest.TestCase): - - creds_dir = os.path.join(os.path.abspath(CLOUD_PROXY_PATH or ''), 'certs/bundles/') - creds = os.path.join(creds_dir, 'creds-v1.zip') - creds_no_auth = os.path.join(creds_dir, 'creds-v1-wo-creds.zip') - creds_unreachable = os.path.join(creds_dir, 'creds-v1-unreachable.zip') - creds_invalid_ca = os.path.join(creds_dir, 'creds-v1-invalid-ca.zip') - - cluster, connect = None, False - session = None - - @classmethod - def connect(cls, creds, **kwargs): - cloud_config = { - 'secure_connect_bundle': creds, - } - cls.cluster = Cluster(cloud=cloud_config, protocol_version=4, **kwargs) - cls.session = cls.cluster.connect(wait_for_all_pools=True) - - def tearDown(self): - if self.cluster: - self.cluster.shutdown() - - -class CloudProxyServer(object): - """ - Class for starting and stopping the proxy (sni_single_endpoint) - """ - - ccm_command = 'docker exec $(docker ps -a -q --filter ancestor=single_endpoint) ccm {}' - - def __init__(self, CLOUD_PROXY_PATH): - self.CLOUD_PROXY_PATH = CLOUD_PROXY_PATH - self.running = False - - def start(self): - return_code = subprocess.call( - ['REQUIRE_CLIENT_CERTIFICATE=true ./run.sh'], - cwd=self.CLOUD_PROXY_PATH, - shell=True) - if return_code != 0: - raise Exception("Error while starting proxy server") - self.running = True - - def stop(self): - if self.is_running(): - subprocess.call( - ["docker kill $(docker ps -a -q --filter ancestor=single_endpoint)"], - shell=True) - self.running = False - - def is_running(self): - return self.running - - def start_node(self, id): - subcommand = 'node{} start --jvm_arg "-Ddse.product_type=DATASTAX_APOLLO" --root --wait-for-binary-proto'.format(id) - subprocess.call( - [self.ccm_command.format(subcommand)], - shell=True) - - def stop_node(self, id): - subcommand = 'node{} stop'.format(id) - subprocess.call( - [self.ccm_command.format(subcommand)], - shell=True) - - -CLOUD_PROXY_SERVER = CloudProxyServer(CLOUD_PROXY_PATH) - - -def start_cloud_proxy(): - """ - Starts and waits for the proxy to run - """ - CLOUD_PROXY_SERVER.stop() - CLOUD_PROXY_SERVER.start() - - -def stop_cloud_proxy(): - CLOUD_PROXY_SERVER.stop() diff --git a/tests/integration/cloud/conftest.py b/tests/integration/cloud/conftest.py deleted file mode 100644 index fb08b04194..0000000000 --- a/tests/integration/cloud/conftest.py +++ /dev/null @@ -1,9 +0,0 @@ -import pytest - -from tests.integration.cloud import setup_package, teardown_package - -@pytest.fixture(scope='session', autouse=True) -def setup_and_teardown_packages(): - setup_package() - yield - teardown_package() \ No newline at end of file diff --git a/tests/integration/cloud/test_cloud.py b/tests/integration/cloud/test_cloud.py deleted file mode 100644 index 514314d81e..0000000000 --- a/tests/integration/cloud/test_cloud.py +++ /dev/null @@ -1,240 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License -from cassandra.datastax.cloud import parse_metadata_info -from cassandra.query import SimpleStatement -from cassandra.cqlengine import connection -from cassandra.cqlengine.management import sync_table, create_keyspace_simple -from cassandra.cqlengine.models import Model -from cassandra.cqlengine import columns -from cassandra import DriverException, ConsistencyLevel, InvalidRequest -from cassandra.cluster import NoHostAvailable, ExecutionProfile, Cluster, _execution_profile_to_string -from cassandra.connection import SniEndPoint -from cassandra.auth import PlainTextAuthProvider -from cassandra.policies import TokenAwarePolicy, DCAwareRoundRobinPolicy, ConstantReconnectionPolicy - -from ssl import SSLContext, PROTOCOL_TLS -from unittest.mock import patch - -from tests.integration import requirescloudproxy -from tests.util import wait_until_not_raised -from tests.integration.cloud import CloudProxyCluster, CLOUD_PROXY_SERVER - -DISALLOWED_CONSISTENCIES = [ - ConsistencyLevel.ANY, - ConsistencyLevel.ONE, - ConsistencyLevel.LOCAL_ONE -] - - -@requirescloudproxy -class CloudTests(CloudProxyCluster): - def hosts_up(self): - return [h for h in self.cluster.metadata.all_hosts() if h.is_up] - - def test_resolve_and_connect(self): - self.connect(self.creds) - - self.assertEqual(len(self.hosts_up()), 3) - for host in self.cluster.metadata.all_hosts(): - self.assertTrue(host.is_up) - self.assertIsInstance(host.endpoint, SniEndPoint) - self.assertEqual(str(host.endpoint), "{}:{}:{}".format( - host.endpoint.address, host.endpoint.port, host.host_id)) - self.assertIn(host.endpoint._resolved_address, ("127.0.0.1", '::1')) - - def test_match_system_local(self): - self.connect(self.creds) - - self.assertEqual(len(self.hosts_up()), 3) - for host in self.cluster.metadata.all_hosts(): - row = self.session.execute("SELECT * FROM system.local WHERE key='local'", host=host).one() - self.assertEqual(row.host_id, host.host_id) - self.assertEqual(row.rpc_address, host.broadcast_rpc_address) - - def test_set_auth_provider(self): - self.connect(self.creds) - self.assertIsInstance(self.cluster.auth_provider, PlainTextAuthProvider) - self.assertEqual(self.cluster.auth_provider.username, 'user1') - self.assertEqual(self.cluster.auth_provider.password, 'user1') - - def test_support_leaving_the_auth_unset(self): - with self.assertRaises(NoHostAvailable): - self.connect(self.creds_no_auth) - self.assertIsNone(self.cluster.auth_provider) - - def test_support_overriding_auth_provider(self): - try: - self.connect(self.creds, auth_provider=PlainTextAuthProvider('invalid', 'invalid')) - except: - pass # this will fail soon when sni_single_endpoint is updated - self.assertIsInstance(self.cluster.auth_provider, PlainTextAuthProvider) - self.assertEqual(self.cluster.auth_provider.username, 'invalid') - self.assertEqual(self.cluster.auth_provider.password, 'invalid') - - def test_error_overriding_ssl_context(self): - with self.assertRaises(ValueError) as cm: - self.connect(self.creds, ssl_context=SSLContext(PROTOCOL_TLS)) - - self.assertIn('cannot be specified with a cloud configuration', str(cm.exception)) - - def test_error_overriding_ssl_options(self): - with self.assertRaises(ValueError) as cm: - self.connect(self.creds, ssl_options={'check_hostname': True}) - - self.assertIn('cannot be specified with a cloud configuration', str(cm.exception)) - - def _bad_hostname_metadata(self, config, http_data): - config = parse_metadata_info(config, http_data) - config.sni_host = "127.0.0.1" - return config - - def test_verify_hostname(self): - with patch('cassandra.datastax.cloud.parse_metadata_info', wraps=self._bad_hostname_metadata): - with self.assertRaises(NoHostAvailable) as e: - self.connect(self.creds) - self.assertIn("hostname", str(e.exception).lower()) - - def test_error_when_bundle_doesnt_exist(self): - try: - self.connect('/invalid/path/file.zip') - except Exception as e: - self.assertIsInstance(e, FileNotFoundError) - - def test_load_balancing_policy_is_dcawaretokenlbp(self): - self.connect(self.creds) - self.assertIsInstance(self.cluster.profile_manager.default.load_balancing_policy, - TokenAwarePolicy) - self.assertIsInstance(self.cluster.profile_manager.default.load_balancing_policy._child_policy, - DCAwareRoundRobinPolicy) - - def test_resolve_and_reconnect_on_node_down(self): - - self.connect(self.creds, - idle_heartbeat_interval=1, idle_heartbeat_timeout=1, - reconnection_policy=ConstantReconnectionPolicy(120)) - - self.assertEqual(len(self.hosts_up()), 3) - CLOUD_PROXY_SERVER.stop_node(1) - wait_until_not_raised( - lambda: self.assertEqual(len(self.hosts_up()), 2), - 0.02, 250) - - host = [h for h in self.cluster.metadata.all_hosts() if not h.is_up][0] - with patch.object(SniEndPoint, "resolve", wraps=host.endpoint.resolve) as mocked_resolve: - CLOUD_PROXY_SERVER.start_node(1) - wait_until_not_raised( - lambda: self.assertEqual(len(self.hosts_up()), 3), - 0.02, 250) - mocked_resolve.assert_called() - - def test_metadata_unreachable(self): - with self.assertRaises(DriverException) as cm: - self.connect(self.creds_unreachable, connect_timeout=1) - - self.assertIn('Unable to connect to the metadata service', str(cm.exception)) - - def test_metadata_ssl_error(self): - with self.assertRaises(DriverException) as cm: - self.connect(self.creds_invalid_ca) - - self.assertIn('Unable to connect to the metadata', str(cm.exception)) - - def test_default_consistency(self): - self.connect(self.creds) - self.assertEqual(self.session.default_consistency_level, ConsistencyLevel.LOCAL_QUORUM) - # Verify EXEC_PROFILE_DEFAULT, EXEC_PROFILE_GRAPH_DEFAULT, - # EXEC_PROFILE_GRAPH_SYSTEM_DEFAULT, EXEC_PROFILE_GRAPH_ANALYTICS_DEFAULT - for ep_key in self.cluster.profile_manager.profiles.keys(): - ep = self.cluster.profile_manager.profiles[ep_key] - self.assertEqual( - ep.consistency_level, - ConsistencyLevel.LOCAL_QUORUM, - "Expecting LOCAL QUORUM for profile {}, but got {} instead".format( - _execution_profile_to_string(ep_key), ConsistencyLevel.value_to_name[ep.consistency_level] - )) - - def test_default_consistency_of_execution_profiles(self): - cloud_config = {'secure_connect_bundle': self.creds} - self.cluster = Cluster(cloud=cloud_config, protocol_version=4, execution_profiles={ - 'pre_create_default_ep': ExecutionProfile(), - 'pre_create_changed_ep': ExecutionProfile( - consistency_level=ConsistencyLevel.LOCAL_ONE, - ), - }) - self.cluster.add_execution_profile('pre_connect_default_ep', ExecutionProfile()) - self.cluster.add_execution_profile( - 'pre_connect_changed_ep', - ExecutionProfile( - consistency_level=ConsistencyLevel.LOCAL_ONE, - ) - ) - session = self.cluster.connect(wait_for_all_pools=True) - - self.cluster.add_execution_profile('post_connect_default_ep', ExecutionProfile()) - self.cluster.add_execution_profile( - 'post_connect_changed_ep', - ExecutionProfile( - consistency_level=ConsistencyLevel.LOCAL_ONE, - ) - ) - - for default in ['pre_create_default_ep', 'pre_connect_default_ep', 'post_connect_default_ep']: - cl = self.cluster.profile_manager.profiles[default].consistency_level - self.assertEqual( - cl, ConsistencyLevel.LOCAL_QUORUM, - "Expecting LOCAL QUORUM for profile {}, but got {} instead".format(default, cl) - ) - for changed in ['pre_create_changed_ep', 'pre_connect_changed_ep', 'post_connect_changed_ep']: - cl = self.cluster.profile_manager.profiles[changed].consistency_level - self.assertEqual( - cl, ConsistencyLevel.LOCAL_ONE, - "Expecting LOCAL ONE for profile {}, but got {} instead".format(default, cl) - ) - - def test_consistency_guardrails(self): - self.connect(self.creds) - self.session.execute( - "CREATE KEYSPACE IF NOT EXISTS test_consistency_guardrails " - "with replication={'class': 'SimpleStrategy', 'replication_factor': 1}" - ) - self.session.execute("CREATE TABLE IF NOT EXISTS test_consistency_guardrails.guardrails (id int primary key)") - for consistency in DISALLOWED_CONSISTENCIES: - statement = SimpleStatement( - "INSERT INTO test_consistency_guardrails.guardrails (id) values (1)", - consistency_level=consistency - ) - with self.assertRaises(InvalidRequest) as e: - self.session.execute(statement) - self.assertIn('not allowed for Write Consistency Level', str(e.exception)) - - # Sanity check to make sure we can do a normal insert - statement = SimpleStatement( - "INSERT INTO test_consistency_guardrails.guardrails (id) values (1)", - consistency_level=ConsistencyLevel.LOCAL_QUORUM - ) - try: - self.session.execute(statement) - except InvalidRequest: - self.fail("InvalidRequest was incorrectly raised for write query at LOCAL QUORUM!") - - def test_cqlengine_can_connect(self): - class TestModel(Model): - id = columns.Integer(primary_key=True) - val = columns.Text() - - connection.setup(None, "test", cloud={'secure_connect_bundle': self.creds}) - create_keyspace_simple('test', 1) - sync_table(TestModel) - TestModel.objects.create(id=42, value='test') - self.assertEqual(len(TestModel.objects.all()), 1) diff --git a/tests/integration/cloud/test_cloud_schema.py b/tests/integration/cloud/test_cloud_schema.py deleted file mode 100644 index 1d52e8e428..0000000000 --- a/tests/integration/cloud/test_cloud_schema.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License -""" -This is mostly copypasta from integration/long/test_schema.py - -TODO: Come up with way to run cloud and local tests without duplication -""" - -import logging -import time - -from cassandra import ConsistencyLevel -from cassandra.cluster import Cluster -from cassandra.query import SimpleStatement - -from tests.integration import execute_until_pass -from tests.integration.cloud import CloudProxyCluster - -log = logging.getLogger(__name__) - - -class CloudSchemaTests(CloudProxyCluster): - def test_recreates(self): - """ - Basic test for repeated schema creation and use, using many different keyspaces - """ - self.connect(self.creds) - session = self.session - - for _ in self.cluster.metadata.all_hosts(): - for keyspace_number in range(5): - keyspace = "ks_{0}".format(keyspace_number) - - if keyspace in self.cluster.metadata.keyspaces.keys(): - drop = "DROP KEYSPACE {0}".format(keyspace) - log.debug(drop) - execute_until_pass(session, drop) - - create = "CREATE KEYSPACE {0} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': 3}}".format( - keyspace) - log.debug(create) - execute_until_pass(session, create) - - create = "CREATE TABLE {0}.cf (k int PRIMARY KEY, i int)".format(keyspace) - log.debug(create) - execute_until_pass(session, create) - - use = "USE {0}".format(keyspace) - log.debug(use) - execute_until_pass(session, use) - - insert = "INSERT INTO cf (k, i) VALUES (0, 0)" - log.debug(insert) - ss = SimpleStatement(insert, consistency_level=ConsistencyLevel.QUORUM) - execute_until_pass(session, ss) - - def test_for_schema_disagreement_attribute(self): - """ - Tests to ensure that schema disagreement is properly surfaced on the response future. - - Creates and destroys keyspaces/tables with various schema agreement timeouts set. - First part runs cql create/drop cmds with schema agreement set in such away were it will be impossible for agreement to occur during timeout. - It then validates that the correct value is set on the result. - Second part ensures that when schema agreement occurs, that the result set reflects that appropriately - - @since 3.1.0 - @jira_ticket PYTHON-458 - @expected_result is_schema_agreed is set appropriately on response thefuture - - @test_category schema - """ - # This should yield a schema disagreement - cloud_config = {'secure_connect_bundle': self.creds} - cluster = Cluster(max_schema_agreement_wait=0.001, protocol_version=4, cloud=cloud_config) - session = cluster.connect(wait_for_all_pools=True) - - rs = session.execute( - "CREATE KEYSPACE test_schema_disagreement WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}") - self.check_and_wait_for_agreement(session, rs, False) - rs = session.execute( - SimpleStatement("CREATE TABLE test_schema_disagreement.cf (key int PRIMARY KEY, value int)", - consistency_level=ConsistencyLevel.ALL)) - self.check_and_wait_for_agreement(session, rs, False) - rs = session.execute("DROP KEYSPACE test_schema_disagreement") - self.check_and_wait_for_agreement(session, rs, False) - cluster.shutdown() - - # These should have schema agreement - cluster = Cluster(protocol_version=4, max_schema_agreement_wait=100, cloud=cloud_config) - session = cluster.connect() - rs = session.execute( - "CREATE KEYSPACE test_schema_disagreement WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}") - self.check_and_wait_for_agreement(session, rs, True) - rs = session.execute( - SimpleStatement("CREATE TABLE test_schema_disagreement.cf (key int PRIMARY KEY, value int)", - consistency_level=ConsistencyLevel.ALL)) - self.check_and_wait_for_agreement(session, rs, True) - rs = session.execute("DROP KEYSPACE test_schema_disagreement") - self.check_and_wait_for_agreement(session, rs, True) - cluster.shutdown() - - def check_and_wait_for_agreement(self, session, rs, exepected): - # Wait for RESULT_KIND_SCHEMA_CHANGE message to arrive - time.sleep(1) - self.assertEqual(rs.response_future.is_schema_agreed, exepected) - if not rs.response_future.is_schema_agreed: - session.cluster.control_connection.wait_for_schema_agreement(wait_time=1000) \ No newline at end of file diff --git a/tests/integration/cqlengine/advanced/__init__.py b/tests/integration/cqlengine/advanced/__init__.py deleted file mode 100644 index 386372eb4a..0000000000 --- a/tests/integration/cqlengine/advanced/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/tests/integration/cqlengine/advanced/test_cont_paging.py b/tests/integration/cqlengine/advanced/test_cont_paging.py deleted file mode 100644 index 95fb7dc837..0000000000 --- a/tests/integration/cqlengine/advanced/test_cont_paging.py +++ /dev/null @@ -1,169 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -import unittest - -from packaging.version import Version - -from cassandra.cluster import (EXEC_PROFILE_DEFAULT, - ContinuousPagingOptions, ExecutionProfile, - ProtocolVersion) -from cassandra.cqlengine import columns, connection, models -from cassandra.cqlengine.management import drop_table, sync_table -from tests.integration import (DSE_VERSION, greaterthanorequaldse51, - greaterthanorequaldse60, requiredse, TestCluster) - - -class TestMultiKeyModel(models.Model): - __test__ = False - - partition = columns.Integer(primary_key=True) - cluster = columns.Integer(primary_key=True) - count = columns.Integer(required=False) - text = columns.Text(required=False) - - -def setup_module(): - if DSE_VERSION: - sync_table(TestMultiKeyModel) - for i in range(1000): - TestMultiKeyModel.create(partition=i, cluster=i, count=5, text="text to write") - - -def teardown_module(): - if DSE_VERSION: - drop_table(TestMultiKeyModel) - - -@requiredse -class BasicConcurrentTests(): - required_dse_version = None - protocol_version = None - connections = set() - sane_connections = {"CONTDEFAULT"} - - @classmethod - def setUpClass(cls): - if DSE_VERSION: - cls._create_cluster_with_cp_options("CONTDEFAULT", ContinuousPagingOptions()) - cls._create_cluster_with_cp_options("ONEPAGE", ContinuousPagingOptions(max_pages=1)) - cls._create_cluster_with_cp_options("MANYPAGES", ContinuousPagingOptions(max_pages=10)) - cls._create_cluster_with_cp_options("SLOW", ContinuousPagingOptions(max_pages_per_second=1)) - - @classmethod - def tearDownClass(cls): - if not DSE_VERSION or DSE_VERSION < cls.required_dse_version: - return - - cls.cluster_default.shutdown() - connection.set_default_connection("default") - - @classmethod - def _create_cluster_with_cp_options(cls, name, cp_options): - execution_profiles = {EXEC_PROFILE_DEFAULT: - ExecutionProfile(continuous_paging_options=cp_options)} - cls.cluster_default = TestCluster(protocol_version=cls.protocol_version, - execution_profiles=execution_profiles) - cls.session_default = cls.cluster_default.connect(wait_for_all_pools=True) - connection.register_connection(name, default=True, session=cls.session_default) - cls.connections.add(name) - - def test_continuous_paging_basic(self): - """ - Test to ensure that various continuous paging works with cqlengine - for session - @since DSE 2.4 - @jira_ticket PYTHON-872 - @expected_result various continous paging options should fetch all the results - - @test_category queries - """ - for connection_name in self.sane_connections: - connection.set_default_connection(connection_name) - row = TestMultiKeyModel.get(partition=0, cluster=0) - self.assertEqual(row.partition, 0) - self.assertEqual(row.cluster, 0) - rows = TestMultiKeyModel.objects().allow_filtering() - self.assertEqual(len(rows), 1000) - - def test_fetch_size(self): - """ - Test to ensure that various continuous paging works with different fetch sizes - for session - @since DSE 2.4 - @jira_ticket PYTHON-872 - @expected_result various continous paging options should fetch all the results - - @test_category queries - """ - for connection_name in self.connections: - conn = connection._connections[connection_name] - initial_default = conn.session.default_fetch_size - self.addCleanup( - setattr, - conn.session, - "default_fetch_size", - initial_default - ) - - connection.set_default_connection("ONEPAGE") - for fetch_size in (2, 3, 7, 10, 99, 100, 101, 150): - connection._connections["ONEPAGE"].session.default_fetch_size = fetch_size - rows = TestMultiKeyModel.objects().allow_filtering() - self.assertEqual(fetch_size, len(rows)) - - connection.set_default_connection("MANYPAGES") - for fetch_size in (2, 3, 7, 10, 15): - connection._connections["MANYPAGES"].session.default_fetch_size = fetch_size - rows = TestMultiKeyModel.objects().allow_filtering() - self.assertEqual(fetch_size * 10, len(rows)) - - for connection_name in self.sane_connections: - connection.set_default_connection(connection_name) - for fetch_size in (2, 3, 7, 10, 99, 100, 101, 150): - connection._connections[connection_name].session.default_fetch_size = fetch_size - rows = TestMultiKeyModel.objects().allow_filtering() - self.assertEqual(1000, len(rows)) - - -@requiredse -@greaterthanorequaldse51 -class ContPagingTestsDSEV1(BasicConcurrentTests, unittest.TestCase): - @classmethod - def setUpClass(cls): - BasicConcurrentTests.required_dse_version = Version('5.1') - if not DSE_VERSION or DSE_VERSION < BasicConcurrentTests.required_dse_version: - return - - BasicConcurrentTests.protocol_version = ProtocolVersion.DSE_V1 - BasicConcurrentTests.setUpClass() - -@requiredse -@greaterthanorequaldse60 -class ContPagingTestsDSEV2(BasicConcurrentTests, unittest.TestCase): - @classmethod - def setUpClass(cls): - BasicConcurrentTests.required_dse_version = Version('6.0') - if not DSE_VERSION or DSE_VERSION < BasicConcurrentTests.required_dse_version: - return - BasicConcurrentTests.protocol_version = ProtocolVersion.DSE_V2 - BasicConcurrentTests.setUpClass() - - cls.connections = cls.connections.union({"SMALL_QUEUE", "BIG_QUEUE"}) - cls.sane_connections = cls.sane_connections.union({"SMALL_QUEUE", "BIG_QUEUE"}) - - cls._create_cluster_with_cp_options("SMALL_QUEUE", ContinuousPagingOptions(max_queue_size=2)) - cls._create_cluster_with_cp_options("BIG_QUEUE", ContinuousPagingOptions(max_queue_size=400)) diff --git a/tests/integration/cqlengine/management/test_management.py b/tests/integration/cqlengine/management/test_management.py index ab5ea9f901..55fb62f22c 100644 --- a/tests/integration/cqlengine/management/test_management.py +++ b/tests/integration/cqlengine/management/test_management.py @@ -23,7 +23,7 @@ from cassandra.cqlengine.models import Model from cassandra.cqlengine import columns -from tests.integration import DSE_VERSION, PROTOCOL_VERSION, greaterthancass20, requires_collection_indexes, \ +from tests.integration import PROTOCOL_VERSION, greaterthancass20, requires_collection_indexes, \ MockLoggingHandler, CASSANDRA_VERSION, SCYLLA_VERSION, xfail_scylla from tests.integration.cqlengine.base import BaseCassEngTestCase from tests.integration.cqlengine.query.test_queryset import TestModel diff --git a/tests/integration/long/test_loadbalancingpolicies.py b/tests/integration/long/test_loadbalancingpolicies.py index 7848a21b1d..a6dff4d786 100644 --- a/tests/integration/long/test_loadbalancingpolicies.py +++ b/tests/integration/long/test_loadbalancingpolicies.py @@ -30,7 +30,7 @@ ) from cassandra.query import SimpleStatement -from tests.integration import use_singledc, use_multidc, remove_cluster, TestCluster, greaterthanorequalcass40, notdse +from tests.integration import use_singledc, use_multidc, remove_cluster, TestCluster, greaterthanorequalcass40 from tests.integration.long.utils import (wait_for_up, create_schema, CoordinatorStats, force_stop, wait_for_down, decommission, start, @@ -614,7 +614,6 @@ def test_token_aware_with_shuffle_rf3(self): self.coordinator_stats.assert_query_count_equals(self, 2, 0) self.coordinator_stats.assert_query_count_equals(self, 3, 12) - @notdse @greaterthanorequalcass40 def test_token_aware_with_transient_replication(self): """ diff --git a/tests/integration/long/utils.py b/tests/integration/long/utils.py index 58c3241a42..a3ae705a34 100644 --- a/tests/integration/long/utils.py +++ b/tests/integration/long/utils.py @@ -19,7 +19,7 @@ from packaging.version import Version from tests.integration import (get_node, get_cluster, wait_for_node_socket, - DSE_VERSION, CASSANDRA_VERSION) + CASSANDRA_VERSION) IP_FORMAT = '127.0.0.%s' @@ -92,7 +92,7 @@ def force_stop(node): def decommission(node): - if (DSE_VERSION and DSE_VERSION >= Version("5.1")) or CASSANDRA_VERSION >= Version("4.0-a"): + if CASSANDRA_VERSION >= Version("4.0-a"): # CASSANDRA-12510 get_node(node).decommission(force=True) else: diff --git a/tests/integration/simulacron/__init__.py b/tests/integration/simulacron/__init__.py index c959fd6e08..b75b67c540 100644 --- a/tests/integration/simulacron/__init__.py +++ b/tests/integration/simulacron/__init__.py @@ -13,7 +13,7 @@ # limitations under the License import unittest -from tests.integration import requiredse, CASSANDRA_VERSION, DSE_VERSION, SIMULACRON_JAR, PROTOCOL_VERSION +from tests.integration import CASSANDRA_VERSION, SIMULACRON_JAR, PROTOCOL_VERSION from tests.integration.simulacron.utils import ( clear_queries, start_and_prime_singledc, @@ -26,7 +26,7 @@ from packaging.version import Version -PROTOCOL_VERSION = min(4, PROTOCOL_VERSION if (DSE_VERSION is None or DSE_VERSION >= Version('5.0')) else 3) +PROTOCOL_VERSION = min(4, PROTOCOL_VERSION) def teardown_package(): @@ -61,22 +61,3 @@ def tearDownClass(cls): if cls.cluster: cls.cluster.shutdown() stop_simulacron() - - -@requiredse -class DseSimulacronCluster(SimulacronBase): - - simulacron_cluster = None - cluster, connect = None, True - nodes_per_dc = 1 - - @classmethod - def setUpClass(cls): - if DSE_VERSION is None and SIMULACRON_JAR is None or CASSANDRA_VERSION < Version("2.1"): - return - - cls.simulacron_cluster = start_and_prime_cluster_defaults(dse_version=DSE_VERSION, - nodes_per_dc=cls.nodes_per_dc) - if cls.connect: - cls.cluster = Cluster(protocol_version=PROTOCOL_VERSION, compression=False) - cls.session = cls.cluster.connect(wait_for_all_pools=True) diff --git a/tests/integration/simulacron/advanced/__init__.py b/tests/integration/simulacron/advanced/__init__.py deleted file mode 100644 index 2c9ca172f8..0000000000 --- a/tests/integration/simulacron/advanced/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/integration/simulacron/advanced/test_insights.py b/tests/integration/simulacron/advanced/test_insights.py deleted file mode 100644 index 5ddae4ec7c..0000000000 --- a/tests/integration/simulacron/advanced/test_insights.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import unittest - -import time -import json -import re - -from cassandra.cluster import Cluster -from cassandra.datastax.insights.util import version_supports_insights - -from tests.integration import requiressimulacron, requiredse, DSE_VERSION -from tests.integration.simulacron import DseSimulacronCluster, PROTOCOL_VERSION -from tests.integration.simulacron.utils import SimulacronClient, GetLogsQuery, ClearLogsQuery - - -@requiredse -@requiressimulacron -@unittest.skipUnless(DSE_VERSION and version_supports_insights(str(DSE_VERSION)), 'DSE {} does not support insights'.format(DSE_VERSION)) -class InsightsTests(DseSimulacronCluster): - """ - Tests insights integration - - @since 3.18 - @jira_ticket PYTHON-1047 - @expected_result startup and status messages are sent - """ - - connect = False - - def tearDown(self): - if self.cluster: - self.cluster.shutdown() - - @staticmethod - def _get_node_logs(raw_data): - return list(filter(lambda q: q['type'] == 'QUERY' and q['query'].startswith('CALL InsightsRpc.reportInsight'), - json.loads(raw_data)['data_centers'][0]['nodes'][0]['queries'])) - - @staticmethod - def _parse_data(data, index=0): - return json.loads(re.match( - r"CALL InsightsRpc.reportInsight\('(.+)'\)", - data[index]['frame']['message']['query']).group(1)) - - def test_startup_message(self): - self.cluster = Cluster(protocol_version=PROTOCOL_VERSION, compression=False) - self.session = self.cluster.connect(wait_for_all_pools=True) - - time.sleep(1) # wait the monitor thread is started - response = SimulacronClient().submit_request(GetLogsQuery()) - self.assertTrue('CALL InsightsRpc.reportInsight' in response) - - node_queries = self._get_node_logs(response) - self.assertEqual(1, len(node_queries)) - self.assertTrue(node_queries, "RPC query not found") - - message = self._parse_data(node_queries) - - self.assertEqual(message['metadata']['name'], 'driver.startup') - self.assertEqual(message['data']['initialControlConnection'], - self.cluster.control_connection._connection.host) - self.assertEqual(message['data']['sessionId'], str(self.session.session_id)) - self.assertEqual(message['data']['clientId'], str(self.cluster.client_id)) - self.assertEqual(message['data']['compression'], 'NONE') - - def test_status_message(self): - SimulacronClient().submit_request(ClearLogsQuery()) - - self.cluster = Cluster(protocol_version=PROTOCOL_VERSION, compression=False, monitor_reporting_interval=1) - self.session = self.cluster.connect(wait_for_all_pools=True) - - time.sleep(1.1) - response = SimulacronClient().submit_request(GetLogsQuery()) - self.assertTrue('CALL InsightsRpc.reportInsight' in response) - - node_queries = self._get_node_logs(response) - self.assertEqual(2, len(node_queries)) - self.assertTrue(node_queries, "RPC query not found") - - message = self._parse_data(node_queries, 1) - - self.assertEqual(message['metadata']['name'], 'driver.status') - self.assertEqual(message['data']['controlConnection'], - self.cluster.control_connection._connection.host) - self.assertEqual(message['data']['sessionId'], str(self.session.session_id)) - self.assertEqual(message['data']['clientId'], str(self.cluster.client_id)) - self.assertEqual(message['metadata']['insightType'], 'EVENT') - - def test_monitor_disabled(self): - SimulacronClient().submit_request(ClearLogsQuery()) - - self.cluster = Cluster(protocol_version=PROTOCOL_VERSION, compression=False, monitor_reporting_enabled=False) - self.session = self.cluster.connect(wait_for_all_pools=True) - - response = SimulacronClient().submit_request(GetLogsQuery()) - self.assertFalse('CALL InsightsRpc.reportInsight' in response) diff --git a/tests/integration/simulacron/test_cluster.py b/tests/integration/simulacron/test_cluster.py index dfbf6c0ec6..53aa9936fc 100644 --- a/tests/integration/simulacron/test_cluster.py +++ b/tests/integration/simulacron/test_cluster.py @@ -18,7 +18,7 @@ import cassandra from tests.integration.simulacron import SimulacronCluster, SimulacronBase -from tests.integration import (requiressimulacron, PROTOCOL_VERSION, DSE_VERSION, MockLoggingHandler) +from tests.integration import (requiressimulacron, PROTOCOL_VERSION, MockLoggingHandler) from tests.integration.simulacron.utils import prime_query, start_and_prime_singledc from cassandra import (WriteTimeout, WriteType, @@ -26,7 +26,7 @@ from cassandra.cluster import Cluster, ControlConnection -PROTOCOL_VERSION = min(4, PROTOCOL_VERSION if (DSE_VERSION is None or DSE_VERSION >= Version('5.0')) else 3) +PROTOCOL_VERSION = min(4, PROTOCOL_VERSION) @requiressimulacron class ClusterTests(SimulacronCluster): @@ -89,7 +89,7 @@ class DuplicateRpcTest(SimulacronCluster): def test_duplicate(self): with MockLoggingHandler().set_module_name(cassandra.cluster.__name__) as mock_handler: - address_column = "native_transport_address" if DSE_VERSION and DSE_VERSION > Version("6.0") else "rpc_address" + address_column = "rpc_address" rows = [ {"peer": "127.0.0.1", "data_center": "dc", "host_id": "dontcare1", "rack": "rack1", "release_version": "3.11.4", address_column: "127.0.0.1", "schema_version": "dontcare", "tokens": "1"}, diff --git a/tests/integration/simulacron/utils.py b/tests/integration/simulacron/utils.py index 37e259dfd7..b6136e247a 100644 --- a/tests/integration/simulacron/utils.py +++ b/tests/integration/simulacron/utils.py @@ -20,7 +20,7 @@ from cassandra.metadata import SchemaParserV4, SchemaParserDSE68 from tests.util import wait_until_not_raised -from tests.integration import CASSANDRA_VERSION, SIMULACRON_JAR, DSE_VERSION +from tests.integration import CASSANDRA_VERSION, SIMULACRON_JAR DEFAULT_CLUSTER = "python_simulacron_cluster" @@ -122,12 +122,6 @@ def prime_server_versions(self): system_local_row = {} system_local_row["cql_version"] = CASSANDRA_VERSION.base_version system_local_row["release_version"] = CASSANDRA_VERSION.base_version + "-SNAPSHOT" - if DSE_VERSION: - system_local_row["dse_version"] = DSE_VERSION.base_version - column_types = {"cql_version": "ascii", "release_version": "ascii"} - system_local = PrimeQuery("SELECT cql_version, release_version FROM system.local WHERE key='local'", - rows=[system_local_row], - column_types=column_types) self.submit_request(system_local) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 9c01fc00a9..503b9304b3 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -42,7 +42,7 @@ from tests.integration import use_cluster, get_server_versions, CASSANDRA_VERSION, \ execute_until_pass, execute_with_long_wait_retry, get_node, MockLoggingHandler, get_unsupported_lower_protocol, \ get_unsupported_upper_protocol, protocolv6, local, CASSANDRA_IP, greaterthanorequalcass30, \ - lessthanorequalcass40, DSE_VERSION, TestCluster, PROTOCOL_VERSION, xfail_scylla, incorrect_test + lessthanorequalcass40, TestCluster, PROTOCOL_VERSION, xfail_scylla, incorrect_test from tests.integration.util import assert_quiescent_pool_state import sys @@ -255,13 +255,7 @@ def test_protocol_negotiation(self): updated_protocol_version = session._protocol_version updated_cluster_version = cluster.protocol_version # Make sure the correct protocol was selected by default - if DSE_VERSION and DSE_VERSION >= Version("6.0"): - self.assertEqual(updated_protocol_version, cassandra.ProtocolVersion.DSE_V2) - self.assertEqual(updated_cluster_version, cassandra.ProtocolVersion.DSE_V2) - elif DSE_VERSION and DSE_VERSION >= Version("5.1"): - self.assertEqual(updated_protocol_version, cassandra.ProtocolVersion.DSE_V1) - self.assertEqual(updated_cluster_version, cassandra.ProtocolVersion.DSE_V1) - elif CASSANDRA_VERSION >= Version('4.0-beta5'): + if CASSANDRA_VERSION >= Version('4.0-beta5'): self.assertEqual(updated_protocol_version, cassandra.ProtocolVersion.V5) self.assertEqual(updated_cluster_version, cassandra.ProtocolVersion.V5) elif CASSANDRA_VERSION >= Version('4.0-a'): diff --git a/tests/integration/standard/test_control_connection.py b/tests/integration/standard/test_control_connection.py index b6e0d3ccd3..ea434c37c5 100644 --- a/tests/integration/standard/test_control_connection.py +++ b/tests/integration/standard/test_control_connection.py @@ -20,7 +20,7 @@ from cassandra.protocol import ConfigurationException -from tests.integration import use_singledc, PROTOCOL_VERSION, TestCluster, greaterthanorequalcass40, notdse +from tests.integration import use_singledc, PROTOCOL_VERSION, TestCluster, greaterthanorequalcass40 from tests.integration.datatype_utils import update_datatypes @@ -102,7 +102,6 @@ def test_get_control_connection_host(self): # TODO: enable after https://github.com/scylladb/python-driver/issues/121 is fixed @unittest.skip('Fails on scylla due to the broadcast_rpc_port is None') - @notdse @greaterthanorequalcass40 def test_control_connection_port_discovery(self): """ diff --git a/tests/integration/standard/test_custom_protocol_handler.py b/tests/integration/standard/test_custom_protocol_handler.py index 35dba6c1b5..26d3f5fe35 100644 --- a/tests/integration/standard/test_custom_protocol_handler.py +++ b/tests/integration/standard/test_custom_protocol_handler.py @@ -21,8 +21,8 @@ from cassandra import ProtocolVersion, ConsistencyLevel from tests.integration import use_singledc, drop_keyspace_shutdown_cluster, \ - greaterthanorequalcass30, execute_with_long_wait_retry, greaterthanorequaldse51, greaterthanorequalcass3_10, \ - TestCluster, greaterthanorequalcass40, requirecassandra + greaterthanorequalcass30, execute_with_long_wait_retry, greaterthanorequalcass3_10, \ + TestCluster, greaterthanorequalcass40 from tests.integration.datatype_utils import update_datatypes, PRIMITIVE_DATATYPES from tests.integration.standard.utils import create_table_with_all_types, get_all_primitive_params @@ -121,7 +121,6 @@ def test_custom_raw_row_results_all_types(self): cluster.shutdown() @unittest.expectedFailure - @requirecassandra @greaterthanorequalcass40 def test_protocol_divergence_v5_fail_by_continuous_paging(self): """ @@ -169,7 +168,6 @@ def test_protocol_divergence_v4_fail_by_flag_uses_int(self): int_flag=True) @unittest.expectedFailure - @requirecassandra @greaterthanorequalcass40 def test_protocol_v5_uses_flag_int(self): """ @@ -183,21 +181,7 @@ def test_protocol_v5_uses_flag_int(self): self._protocol_divergence_fail_by_flag_uses_int(ProtocolVersion.V5, uses_int_query_flag=True, beta=True, int_flag=True) - @greaterthanorequaldse51 - def test_protocol_dsev1_uses_flag_int(self): - """ - Test to validate that the _PAGE_SIZE_FLAG is treated correctly using write_uint for DSE_V1 - - @jira_ticket PYTHON-694 - @expected_result the fetch_size=1 parameter will be honored - - @test_category connection - """ - self._protocol_divergence_fail_by_flag_uses_int(ProtocolVersion.DSE_V1, uses_int_query_flag=True, - int_flag=True) - @unittest.expectedFailure - @requirecassandra @greaterthanorequalcass40 def test_protocol_divergence_v5_fail_by_flag_uses_int(self): """ @@ -211,19 +195,6 @@ def test_protocol_divergence_v5_fail_by_flag_uses_int(self): self._protocol_divergence_fail_by_flag_uses_int(ProtocolVersion.V5, uses_int_query_flag=False, beta=True, int_flag=False) - @greaterthanorequaldse51 - def test_protocol_divergence_dsev1_fail_by_flag_uses_int(self): - """ - Test to validate that the _PAGE_SIZE_FLAG is treated correctly using write_uint for DSE_V1 - - @jira_ticket PYTHON-694 - @expected_result the fetch_size=1 parameter will be honored - - @test_category connection - """ - self._protocol_divergence_fail_by_flag_uses_int(ProtocolVersion.DSE_V1, uses_int_query_flag=False, - int_flag=False) - def _send_query_message(self, session, timeout, **kwargs): query = "SELECT * FROM test3rf.test" message = QueryMessage(query=query, **kwargs) diff --git a/tests/integration/standard/test_dse.py b/tests/integration/standard/test_dse.py deleted file mode 100644 index 7b96094b3f..0000000000 --- a/tests/integration/standard/test_dse.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os - -from packaging.version import Version - -from tests import notwindows -from tests.unit.cython.utils import notcython -from tests.integration import (execute_until_pass, - execute_with_long_wait_retry, use_cluster, TestCluster) - -import unittest - - -CCM_IS_DSE = (os.environ.get('CCM_IS_DSE', None) == 'true') - - -@unittest.skipIf(os.environ.get('CCM_ARGS', None), 'environment has custom CCM_ARGS; skipping') -@notwindows -@notcython # no need to double up on this test; also __default__ setting doesn't work -class DseCCMClusterTest(unittest.TestCase): - """ - This class can be executed setting the DSE_VERSION variable, for example: - DSE_VERSION=5.1.4 python2.7 -m nose tests/integration/standard/test_dse.py - If CASSANDRA_VERSION is set instead, it will be converted to the corresponding DSE_VERSION - """ - - def test_dse_5x(self): - self._test_basic(Version('5.1.10')) - - def test_dse_60(self): - self._test_basic(Version('6.0.2')) - - @unittest.skipUnless(CCM_IS_DSE, 'DSE version unavailable') - def test_dse_67(self): - self._test_basic(Version('6.7.0')) - - def _test_basic(self, dse_version): - """ - Test basic connection and usage - """ - cluster_name = '{}-{}'.format( - self.__class__.__name__, dse_version.base_version.replace('.', '_') - ) - use_cluster(cluster_name=cluster_name, nodes=[3], dse_options={}) - - cluster = TestCluster() - session = cluster.connect() - result = execute_until_pass( - session, - """ - CREATE KEYSPACE clustertests - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} - """) - self.assertFalse(result) - - result = execute_with_long_wait_retry( - session, - """ - CREATE TABLE clustertests.cf0 ( - a text, - b text, - c text, - PRIMARY KEY (a, b) - ) - """) - self.assertFalse(result) - - result = session.execute( - """ - INSERT INTO clustertests.cf0 (a, b, c) VALUES ('a', 'b', 'c') - """) - self.assertFalse(result) - - result = session.execute("SELECT * FROM clustertests.cf0") - self.assertEqual([('a', 'b', 'c')], result) - - execute_with_long_wait_retry(session, "DROP KEYSPACE clustertests") - - cluster.shutdown() diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index c76ffa22e9..8d677030f9 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -37,11 +37,11 @@ from tests.integration import (get_cluster, use_singledc, PROTOCOL_VERSION, execute_until_pass, BasicSegregatedKeyspaceUnitTestCase, BasicSharedKeyspaceUnitTestCase, BasicExistingKeyspaceUnitTestCase, drop_keyspace_shutdown_cluster, CASSANDRA_VERSION, - greaterthanorequaldse51, greaterthanorequalcass30, lessthancass30, local, + greaterthanorequalcass30, lessthancass30, local, get_supported_protocol_versions, greaterthancass20, greaterthancass21, assert_startswith, greaterthanorequalcass40, - greaterthanorequaldse67, lessthancass40, - TestCluster, DSE_VERSION, requires_java_udf, requires_composite_type, + lessthancass40, + TestCluster, requires_java_udf, requires_composite_type, requires_collection_indexes, SCYLLA_VERSION, xfail_scylla, xfail_scylla_version_lt) from tests.util import wait_until @@ -74,7 +74,7 @@ def test_host_addresses(self): self.assertIsNotNone(host.broadcast_rpc_address) self.assertIsNotNone(host.host_id) - if not DSE_VERSION and CASSANDRA_VERSION >= Version('4-a'): + if CASSANDRA_VERSION >= Version('4-a'): self.assertIsNotNone(host.broadcast_port) self.assertIsNotNone(host.broadcast_rpc_port) @@ -1066,7 +1066,7 @@ def test_metadata_pagination_keyspaces(self): test for covering https://github.com/scylladb/python-driver/issues/174 """ - + self.cluster.refresh_schema_metadata() keyspaces = [f"keyspace{idx}" for idx in range(15)] @@ -2509,21 +2509,6 @@ def test_metadata_with_quoted_identifiers(self): self.assertIsNotNone(value_column) self.assertEqual(value_column.name, 'the Value') - @greaterthanorequaldse51 - def test_dse_workloads(self): - """ - Test to ensure dse_workloads is populated appropriately. - Field added in DSE 5.1 - - @jira_ticket PYTHON-667 - @expected_result dse_workloads set is set on host model - - @test_category metadata - """ - for host in self.cluster.metadata.all_hosts(): - self.assertIsInstance(host.dse_workloads, SortedSet) - self.assertIn("Cassandra", host.dse_workloads) - class GroupPerHost(BasicSharedKeyspaceUnitTestCase): @classmethod @@ -2588,31 +2573,3 @@ def test_existing_keyspaces_have_correct_virtual_tags(self): ks.virtual, 'incorrect .virtual value for {}'.format(name) ) - - @greaterthanorequalcass40 - @greaterthanorequaldse67 - def test_expected_keyspaces_exist_and_are_virtual(self): - for name in self.virtual_ks_names: - self.assertTrue( - self.cluster.metadata.keyspaces[name].virtual, - 'incorrect .virtual value for {}'.format(name) - ) - - @greaterthanorequalcass40 - @greaterthanorequaldse67 - def test_virtual_keyspaces_have_expected_schema_structure(self): - self.maxDiff = None - - ingested_virtual_ks_structure = defaultdict(dict) - for ks_name, ks in self.cluster.metadata.keyspaces.items(): - if not ks.virtual: - continue - for tab_name, tab in ks.tables.items(): - ingested_virtual_ks_structure[ks_name][tab_name] = set( - tab.columns.keys() - ) - - # Identify a couple known values to verify we parsed the structure correctly - self.assertIn('table_name', ingested_virtual_ks_structure['system_virtual_schema']['tables']) - self.assertIn('type', ingested_virtual_ks_structure['system_virtual_schema']['columns']) - self.assertIn('total', ingested_virtual_ks_structure['system_views']['sstable_tasks']) diff --git a/tests/integration/standard/test_prepared_statements.py b/tests/integration/standard/test_prepared_statements.py index 5ccc0732fa..d413a4dc95 100644 --- a/tests/integration/standard/test_prepared_statements.py +++ b/tests/integration/standard/test_prepared_statements.py @@ -23,8 +23,8 @@ from cassandra import ConsistencyLevel, ProtocolVersion from cassandra.query import PreparedStatement, UNSET_VALUE -from tests.integration import (get_server_versions, greaterthanorequalcass40, greaterthanorequaldse50, - requirecassandra, BasicSharedKeyspaceUnitTestCase) +from tests.integration import (get_server_versions, greaterthanorequalcass40, + BasicSharedKeyspaceUnitTestCase) import logging @@ -563,7 +563,6 @@ def test_id_is_not_updated_conditional_v4(self): self.addCleanup(cluster.shutdown) self._test_updated_conditional(session, 9) - @requirecassandra def test_id_is_not_updated_conditional_v5(self): """ Test that verifies that the result_metadata and the @@ -577,36 +576,6 @@ def test_id_is_not_updated_conditional_v5(self): self.addCleanup(cluster.shutdown) self._test_updated_conditional(session, 10) - @greaterthanorequaldse50 - def test_id_is_not_updated_conditional_dsev1(self): - """ - Test that verifies that the result_metadata and the - result_metadata_id are udpated correctly in conditional statements - in protocol DSE V1 - - @since 3.13 - @jira_ticket PYTHON-847 - """ - cluster = TestCluster(protocol_version=ProtocolVersion.DSE_V1) - session = cluster.connect() - self.addCleanup(cluster.shutdown) - self._test_updated_conditional(session, 10) - - @greaterthanorequaldse50 - def test_id_is_not_updated_conditional_dsev2(self): - """ - Test that verifies that the result_metadata and the - result_metadata_id are udpated correctly in conditional statements - in protocol DSE V2 - - @since 3.13 - @jira_ticket PYTHON-847 - """ - cluster = TestCluster(protocol_version=ProtocolVersion.DSE_V2) - session = cluster.connect() - self.addCleanup(cluster.shutdown) - self._test_updated_conditional(session, 10) - def _test_updated_conditional(self, session, value): prepared_statement = session.prepare( "INSERT INTO {}(a, b, d) VALUES " diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 5c20c50a1a..a4d1b083bf 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -26,7 +26,7 @@ from cassandra.policies import HostDistance, RoundRobinPolicy, WhiteListRoundRobinPolicy from tests.integration import use_singledc, PROTOCOL_VERSION, BasicSharedKeyspaceUnitTestCase, \ greaterthanprotocolv3, MockLoggingHandler, get_supported_protocol_versions, local, get_cluster, setup_keyspace, \ - USE_CASS_EXTERNAL, greaterthanorequalcass40, DSE_VERSION, TestCluster, requirecassandra, xfail_scylla + USE_CASS_EXTERNAL, greaterthanorequalcass40, TestCluster, xfail_scylla from tests import notwindows from tests.integration import greaterthanorequalcass30, get_node @@ -1403,7 +1403,6 @@ def test_setting_keyspace(self): """ self._check_set_keyspace_in_statement(self.session) - @requirecassandra @greaterthanorequalcass40 def test_setting_keyspace_and_session(self): """ @@ -1572,7 +1571,7 @@ def test_reprepare_after_host_is_down(self): # We wait for cluster._prepare_all_queries to be called time.sleep(5) self.assertEqual(1, mock_handler.get_message_count('debug', 'Preparing all known prepared statements')) - + results = self.session.execute(prepared_statement, (1,), execution_profile="only_first") self.assertEqual(results.one(), (1, )) diff --git a/tests/integration/standard/test_single_interface.py b/tests/integration/standard/test_single_interface.py index e836b5f428..681e992477 100644 --- a/tests/integration/standard/test_single_interface.py +++ b/tests/integration/standard/test_single_interface.py @@ -19,12 +19,12 @@ from packaging.version import Version from tests.integration import use_singledc, PROTOCOL_VERSION, \ - remove_cluster, greaterthanorequalcass40, notdse, \ - CASSANDRA_VERSION, DSE_VERSION, TestCluster, DEFAULT_SINGLE_INTERFACE_PORT + remove_cluster, greaterthanorequalcass40, \ + CASSANDRA_VERSION, TestCluster, DEFAULT_SINGLE_INTERFACE_PORT def setup_module(): - if not DSE_VERSION and CASSANDRA_VERSION >= Version('4-a'): + if CASSANDRA_VERSION >= Version('4-a'): remove_cluster() use_singledc(use_single_interface=True) @@ -32,7 +32,6 @@ def teardown_module(): remove_cluster() -@notdse @greaterthanorequalcass40 class SingleInterfaceTest(unittest.TestCase): diff --git a/tests/integration/standard/test_types.py b/tests/integration/standard/test_types.py index 3d0dc0ed7c..eb50c7780a 100644 --- a/tests/integration/standard/test_types.py +++ b/tests/integration/standard/test_types.py @@ -38,8 +38,8 @@ from tests.unit.cython.utils import cythontest from tests.integration import use_singledc, execute_until_pass, notprotocolv1, \ - BasicSharedKeyspaceUnitTestCase, greaterthancass21, lessthancass30, greaterthanorequaldse51, \ - DSE_VERSION, greaterthanorequalcass3_10, requiredse, TestCluster, requires_composite_type, greaterthanorequalcass50 + BasicSharedKeyspaceUnitTestCase, greaterthancass21, lessthancass30, \ + greaterthanorequalcass3_10, TestCluster, requires_composite_type, greaterthanorequalcass50 from tests.integration.datatype_utils import update_datatypes, PRIMITIVE_DATATYPES, COLLECTION_TYPES, PRIMITIVE_DATATYPES_KEYS, \ get_sample, get_all_samples, get_collection_sample @@ -901,372 +901,6 @@ def test_smoke_duration_values(self): self.assertRaises(ValueError, self.session.execute, prepared, (1, Duration(int("8FFFFFFFFFFFFFF0", 16), 0, 0))) - -@requiredse -class AbstractDateRangeTest(): - - def test_single_value_daterange_round_trip(self): - self._daterange_round_trip( - util.DateRange( - value=util.DateRangeBound( - datetime(2014, 10, 1, 0), - util.DateRangePrecision.YEAR - ) - ), - util.DateRange( - value=util.DateRangeBound( - datetime(2014, 1, 1, 0), - util.DateRangePrecision.YEAR - ) - ) - ) - - def test_open_high_daterange_round_trip(self): - self._daterange_round_trip( - util.DateRange( - lower_bound=util.DateRangeBound( - datetime(2013, 10, 1, 6, 20, 39), - util.DateRangePrecision.SECOND - ) - ) - ) - - def test_open_low_daterange_round_trip(self): - self._daterange_round_trip( - util.DateRange( - upper_bound=util.DateRangeBound( - datetime(2013, 10, 28), - util.DateRangePrecision.DAY - ) - ) - ) - - def test_open_both_daterange_round_trip(self): - self._daterange_round_trip( - util.DateRange( - lower_bound=util.OPEN_BOUND, - upper_bound=util.OPEN_BOUND, - ) - ) - - def test_closed_daterange_round_trip(self): - insert = util.DateRange( - lower_bound=util.DateRangeBound( - datetime(2015, 3, 1, 10, 15, 30, 1000), - util.DateRangePrecision.MILLISECOND - ), - upper_bound=util.DateRangeBound( - datetime(2016, 1, 1, 10, 15, 30, 999000), - util.DateRangePrecision.MILLISECOND - ) - ) - self._daterange_round_trip(insert) - - def test_epoch_value_round_trip(self): - insert = util.DateRange( - value=util.DateRangeBound( - datetime(1970, 1, 1), - util.DateRangePrecision.YEAR - ) - ) - self._daterange_round_trip(insert) - - def test_double_bounded_daterange_round_trip_from_string(self): - self._daterange_round_trip( - '[2015-03-01T10:15:30.010Z TO 2016-01-01T10:15:30.999Z]', - util.DateRange( - lower_bound=util.DateRangeBound( - datetime(2015, 3, 1, 10, 15, 30, 10000), - util.DateRangePrecision.MILLISECOND - ), - upper_bound=util.DateRangeBound( - datetime(2016, 1, 1, 10, 15, 30, 999000), - util.DateRangePrecision.MILLISECOND - ), - ) - ) - - def test_open_high_daterange_round_trip_from_string(self): - self._daterange_round_trip( - '[2015-03 TO *]', - util.DateRange( - lower_bound=util.DateRangeBound( - datetime(2015, 3, 1, 0, 0), - util.DateRangePrecision.MONTH - ), - upper_bound=util.DateRangeBound(None, None) - ) - ) - - def test_open_low_daterange_round_trip_from_string(self): - self._daterange_round_trip( - '[* TO 2015-03]', - util.DateRange( - lower_bound=util.DateRangeBound(None, None), - upper_bound=util.DateRangeBound( - datetime(2015, 3, 1, 0, 0), - 'MONTH' - ) - ) - ) - - def test_no_bounds_daterange_round_trip_from_string(self): - self._daterange_round_trip( - '[* TO *]', - util.DateRange( - lower_bound=(None, None), - upper_bound=(None, None) - ) - ) - - def test_single_no_bounds_daterange_round_trip_from_string(self): - self._daterange_round_trip( - '*', - util.DateRange( - value=(None, None) - ) - ) - - def test_single_value_daterange_round_trip_from_string(self): - self._daterange_round_trip( - '2001-01-01T12:30:30.000Z', - util.DateRange( - value=util.DateRangeBound( - datetime(2001, 1, 1, 12, 30, 30), - 'MILLISECOND' - ) - ) - ) - - def test_daterange_with_negative_bound_round_trip_from_string(self): - self._daterange_round_trip( - '[-1991-01-01T00:00:00.001 TO 1990-02-03]', - util.DateRange( - lower_bound=(-124997039999999, 'MILLISECOND'), - upper_bound=util.DateRangeBound( - datetime(1990, 2, 3, 12, 30, 30), - 'DAY' - ) - ) - ) - - def test_epoch_value_round_trip_from_string(self): - self._daterange_round_trip( - '1970', - util.DateRange( - value=util.DateRangeBound( - datetime(1970, 1, 1), - util.DateRangePrecision.YEAR - ) - ) - ) - - -@greaterthanorequaldse51 -class TestDateRangePrepared(AbstractDateRangeTest, BasicSharedKeyspaceUnitTestCase): - """ - Tests various inserts and queries using Date-ranges and prepared queries - - @since 2.0.0 - @jira_ticket PYTHON-668 - @expected_result Date ranges will be inserted and retrieved succesfully - - @test_category data_types - """ - - @classmethod - def setUpClass(cls): - super(TestDateRangePrepared, cls).setUpClass() - cls.session.set_keyspace(cls.ks_name) - if DSE_VERSION and DSE_VERSION >= Version('5.1'): - cls.session.execute("CREATE TABLE tab (dr 'DateRangeType' PRIMARY KEY)") - - - def _daterange_round_trip(self, to_insert, expected=None): - if isinstance(to_insert, util.DateRange): - prep = self.session.prepare("INSERT INTO tab (dr) VALUES (?);") - self.session.execute(prep, (to_insert,)) - prep_sel = self.session.prepare("SELECT * FROM tab WHERE dr = ? ") - results = self.session.execute(prep_sel, (to_insert,)) - else: - prep = self.session.prepare("INSERT INTO tab (dr) VALUES ('%s');" % (to_insert,)) - self.session.execute(prep) - prep_sel = self.session.prepare("SELECT * FROM tab WHERE dr = '%s' " % (to_insert,)) - results = self.session.execute(prep_sel) - - dr = results.one().dr - # sometimes this is truncated in the assertEqual output on failure; - if isinstance(expected, str): - self.assertEqual(str(dr), expected) - else: - self.assertEqual(dr, expected or to_insert) - - # This can only be run as a prepared statement - def test_daterange_wide(self): - self._daterange_round_trip( - util.DateRange( - lower_bound=(-9223372036854775808, 'MILLISECOND'), - upper_bound=(9223372036854775807, 'MILLISECOND') - ), - '[-9223372036854775808ms TO 9223372036854775807ms]' - ) - # This can only be run as a prepared statement - def test_daterange_with_negative_bound_round_trip_to_string(self): - self._daterange_round_trip( - util.DateRange( - lower_bound=(-124997039999999, 'MILLISECOND'), - upper_bound=util.DateRangeBound( - datetime(1990, 2, 3, 12, 30, 30), - 'DAY' - ) - ), - '[-124997039999999ms TO 1990-02-03]' - ) - -@greaterthanorequaldse51 -class TestDateRangeSimple(AbstractDateRangeTest, BasicSharedKeyspaceUnitTestCase): - """ - Tests various inserts and queries using Date-ranges and simple queries - - @since 2.0.0 - @jira_ticket PYTHON-668 - @expected_result DateRanges will be inserted and retrieved successfully - @test_category data_types - """ - @classmethod - def setUpClass(cls): - super(TestDateRangeSimple, cls).setUpClass() - cls.session.set_keyspace(cls.ks_name) - if DSE_VERSION and DSE_VERSION >= Version('5.1'): - cls.session.execute("CREATE TABLE tab (dr 'DateRangeType' PRIMARY KEY)") - - - def _daterange_round_trip(self, to_insert, expected=None): - - query = "INSERT INTO tab (dr) VALUES ('{0}');".format(to_insert) - self.session.execute("INSERT INTO tab (dr) VALUES ('{0}');".format(to_insert)) - query = "SELECT * FROM tab WHERE dr = '{0}' ".format(to_insert) - results= self.session.execute("SELECT * FROM tab WHERE dr = '{0}' ".format(to_insert)) - - dr = results.one().dr - # sometimes this is truncated in the assertEqual output on failure; - if isinstance(expected, str): - self.assertEqual(str(dr), expected) - else: - self.assertEqual(dr, expected or to_insert) - - -@greaterthanorequaldse51 -class TestDateRangeCollection(BasicSharedKeyspaceUnitTestCase): - - - @classmethod - def setUpClass(cls): - super(TestDateRangeCollection, cls).setUpClass() - cls.session.set_keyspace(cls.ks_name) - - def test_date_range_collection(self): - """ - Tests DateRange type in collections - - @since 2.0.0 - @jira_ticket PYTHON-668 - @expected_result DateRanges will be inserted and retrieved successfully when part of a list or map - @test_category data_types - """ - self.session.execute("CREATE TABLE dateRangeIntegrationTest5 (k int PRIMARY KEY, l list<'DateRangeType'>, s set<'DateRangeType'>, dr2i map<'DateRangeType', int>, i2dr map)") - self.session.execute("INSERT INTO dateRangeIntegrationTest5 (k, l, s, i2dr, dr2i) VALUES (" + - "1, " + - "['[2000-01-01T10:15:30.001Z TO 2020]', '[2010-01-01T10:15:30.001Z TO 2020]', '2001-01-02'], " + - "{'[2000-01-01T10:15:30.001Z TO 2020]', '[2000-01-01T10:15:30.001Z TO 2020]', '[2010-01-01T10:15:30.001Z TO 2020]'}, " + - "{1: '[2000-01-01T10:15:30.001Z TO 2020]', 2: '[2010-01-01T10:15:30.001Z TO 2020]'}, " + - "{'[2000-01-01T10:15:30.001Z TO 2020]': 1, '[2010-01-01T10:15:30.001Z TO 2020]': 2})") - results = self.session.execute("SELECT * FROM dateRangeIntegrationTest5").all() - self.assertEqual(len(results),1) - - lower_bound_1 = util.DateRangeBound(datetime(2000, 1, 1, 10, 15, 30, 1000), 'MILLISECOND') - - lower_bound_2 = util.DateRangeBound(datetime(2010, 1, 1, 10, 15, 30, 1000), 'MILLISECOND') - - upper_bound_1 = util.DateRangeBound(datetime(2020, 1, 1), 'YEAR') - - value_1 = util.DateRangeBound(datetime(2001, 1, 2), 'DAY') - - dt = util.DateRange(lower_bound=lower_bound_1, upper_bound=upper_bound_1) - dt2 = util.DateRange(lower_bound=lower_bound_2, upper_bound=upper_bound_1) - dt3 = util.DateRange(value=value_1) - - - - list_result = results[0].l - self.assertEqual(3, len(list_result)) - self.assertEqual(list_result[0],dt) - self.assertEqual(list_result[1],dt2) - self.assertEqual(list_result[2],dt3) - - set_result = results[0].s - self.assertEqual(len(set_result), 2) - self.assertIn(dt, set_result) - self.assertIn(dt2, set_result) - - d2i = results[0].dr2i - self.assertEqual(len(d2i), 2) - self.assertEqual(d2i[dt],1) - self.assertEqual(d2i[dt2],2) - - i2r = results[0].i2dr - self.assertEqual(len(i2r), 2) - self.assertEqual(i2r[1],dt) - self.assertEqual(i2r[2],dt2) - - def test_allow_date_range_in_udt_tuple(self): - """ - Tests DateRanges in tuples and udts - - @since 2.0.0 - @jira_ticket PYTHON-668 - @expected_result DateRanges will be inserted and retrieved successfully in udt's and tuples - @test_category data_types - """ - self.session.execute("CREATE TYPE IF NOT EXISTS test_udt (i int, range 'DateRangeType')") - self.session.execute("CREATE TABLE dateRangeIntegrationTest4 (k int PRIMARY KEY, u test_udt, uf frozen, t tuple<'DateRangeType', int>, tf frozen>)") - self.session.execute("INSERT INTO dateRangeIntegrationTest4 (k, u, uf, t, tf) VALUES (" + - "1, " + - "{i: 10, range: '[2000-01-01T10:15:30.003Z TO 2020-01-01T10:15:30.001Z]'}, " + - "{i: 20, range: '[2000-01-01T10:15:30.003Z TO 2020-01-01T10:15:30.001Z]'}, " + - "('[2000-01-01T10:15:30.003Z TO 2020-01-01T10:15:30.001Z]', 30), " + - "('[2000-01-01T10:15:30.003Z TO 2020-01-01T10:15:30.001Z]', 40))") - - lower_bound = util.DateRangeBound( - datetime(2000, 1, 1, 10, 15, 30, 3000), - 'MILLISECOND') - - upper_bound = util.DateRangeBound( - datetime(2020, 1, 1, 10, 15, 30, 1000), - 'MILLISECOND') - - expected_dt = util.DateRange(lower_bound=lower_bound ,upper_bound=upper_bound) - - results_list = list(self.session.execute("SELECT * FROM dateRangeIntegrationTest4")) - self.assertEqual(len(results_list), 1) - udt = results_list[0].u - self.assertEqual(udt.range, expected_dt) - self.assertEqual(udt.i, 10) - - - uf = results_list[0].uf - self.assertEqual(uf.range, expected_dt) - self.assertEqual(uf.i, 20) - - t = results_list[0].t - self.assertEqual(t[0], expected_dt) - self.assertEqual(t[1], 30) - - tf = results_list[0].tf - self.assertEqual(tf[0], expected_dt) - self.assertEqual(tf[1], 40) - - class TypeTestsProtocol(BasicSharedKeyspaceUnitTestCase): @greaterthancass21 diff --git a/tests/unit/advanced/test_auth.py b/tests/unit/advanced/test_auth.py deleted file mode 100644 index 840073e9e1..0000000000 --- a/tests/unit/advanced/test_auth.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -from puresasl import QOP - -import unittest - -from cassandra.auth import DSEGSSAPIAuthProvider - -# Cannot import requiredse from tests.integration -# This auth provider requires kerberos and puresals -DSE_VERSION = os.getenv('DSE_VERSION', None) -@unittest.skipUnless(DSE_VERSION, "DSE required") -class TestGSSAPI(unittest.TestCase): - - def test_host_resolution(self): - # resolves by default - provider = DSEGSSAPIAuthProvider(service='test', qops=QOP.all) - authenticator = provider.new_authenticator('127.0.0.1') - self.assertEqual(authenticator.sasl.host, 'localhost') - - # numeric fallback okay - authenticator = provider.new_authenticator('192.0.2.1') - self.assertEqual(authenticator.sasl.host, '192.0.2.1') - - # disable explicitly - provider = DSEGSSAPIAuthProvider(service='test', qops=QOP.all, resolve_host_name=False) - authenticator = provider.new_authenticator('127.0.0.1') - self.assertEqual(authenticator.sasl.host, '127.0.0.1') - From ffc6392d5dc397e4baf9a0929aed7506445fbad5 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 15 Jul 2025 18:51:11 -0400 Subject: [PATCH 036/298] Add unit-tests for ShardAwarePortGenerator 1. Make it testable 2. Add unit tests for it --- cassandra/connection.py | 18 ++++++++++----- tests/unit/test_connection.py | 43 ++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/cassandra/connection.py b/cassandra/connection.py index 2d998fb805..6d6e854315 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -667,17 +667,23 @@ def reset_cql_frame_buffer(self): self.reset_io_buffer() -class ShardawarePortGenerator: - @classmethod - def generate(cls, shard_id, total_shards): - start = random.randrange(DEFAULT_LOCAL_PORT_LOW, DEFAULT_LOCAL_PORT_HIGH) - available_ports = itertools.chain(range(start, DEFAULT_LOCAL_PORT_HIGH), range(DEFAULT_LOCAL_PORT_LOW, start)) +class ShardAwarePortGenerator: + def __init__(self, start_port: int, end_port: int): + self.start_port = start_port + self.end_port = end_port + + def generate(self, shard_id: int, total_shards: int): + start = random.randrange(self.start_port, self.end_port) + available_ports = itertools.chain(range(start, self.end_port), range(self.start_port, start)) for port in available_ports: if port % total_shards == shard_id: yield port +DefaultShardAwarePortGenerator = ShardAwarePortGenerator(DEFAULT_LOCAL_PORT_LOW, DEFAULT_LOCAL_PORT_HIGH) + + class Connection(object): CALLBACK_ERR_THREAD_THRESHOLD = 100 @@ -928,7 +934,7 @@ def _wrap_socket_from_context(self): def _initiate_connection(self, sockaddr): if self.features.shard_id is not None: - for port in ShardawarePortGenerator.generate(self.features.shard_id, self.total_shards): + for port in DefaultShardAwarePortGenerator.generate(self.features.shard_id, self.total_shards): try: self._socket.bind(('', port)) break diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 460179e9ff..17b23b5ed0 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import itertools import unittest from io import BytesIO import time @@ -21,7 +22,7 @@ from cassandra.cluster import Cluster from cassandra.connection import (Connection, HEADER_DIRECTION_TO_CLIENT, ProtocolError, locally_supported_compressions, ConnectionHeartbeat, _Frame, Timer, TimerManager, - ConnectionException, DefaultEndPoint) + ConnectionException, DefaultEndPoint, ShardAwarePortGenerator) from cassandra.marshal import uint8_pack, uint32_pack, int32_pack from cassandra.protocol import (write_stringmultimap, write_int, write_string, SupportedMessage, ProtocolHandler) @@ -478,3 +479,43 @@ def test_endpoint_resolve(self): DefaultEndPoint('10.0.0.1', 3232).resolve(), ('10.0.0.1', 3232) ) + + +class TestShardawarePortGenerator(unittest.TestCase): + @patch('random.randrange') + def test_generate_ports_basic(self, mock_randrange): + mock_randrange.return_value = 10005 + gen = ShardAwarePortGenerator(10000, 10020) + ports = list(itertools.islice(gen.generate(shard_id=1, total_shards=3), 5)) + + # Starting from aligned 10005 + shard_id (1), step by 3 + self.assertEqual(ports, [10006, 10009, 10012, 10015, 10018]) + + @patch('random.randrange') + def test_wraps_around_to_start(self, mock_randrange): + mock_randrange.return_value = 10008 + gen = ShardAwarePortGenerator(10000, 10020) + ports = list(itertools.islice(gen.generate(shard_id=2, total_shards=4), 5)) + + # Expected wrap-around from start_port after end_port is exceeded + self.assertEqual(ports, [10010, 10014, 10018, 10002, 10006]) + + @patch('random.randrange') + def test_all_ports_have_correct_modulo(self, mock_randrange): + mock_randrange.return_value = 10012 + total_shards = 5 + shard_id = 3 + gen = ShardAwarePortGenerator(10000, 10020) + + for port in gen.generate(shard_id=shard_id, total_shards=total_shards): + self.assertEqual(port % total_shards, shard_id) + + @patch('random.randrange') + def test_generate_is_repeatable_with_same_mock(self, mock_randrange): + mock_randrange.return_value = 10010 + gen = ShardAwarePortGenerator(10000, 10020) + + first_run = list(itertools.islice(gen.generate(0, 2), 5)) + second_run = list(itertools.islice(gen.generate(0, 2), 5)) + + self.assertEqual(first_run, second_run) \ No newline at end of file From c7ca1c6031f6ed6975e9a3eb83c62b0441876b37 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 15 Jul 2025 22:40:24 -0400 Subject: [PATCH 037/298] Optimise ShardAwarePortGenerator Make ShardAwarePortGenerator iterate only over matching ports. --- cassandra/connection.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/cassandra/connection.py b/cassandra/connection.py index 6d6e854315..6c5e3e2645 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -672,13 +672,21 @@ def __init__(self, start_port: int, end_port: int): self.start_port = start_port self.end_port = end_port + @staticmethod + def _align(value: int, total_shards: int): + shift = value % total_shards + if shift == 0: + return value + return value + total_shards - shift + def generate(self, shard_id: int, total_shards: int): - start = random.randrange(self.start_port, self.end_port) - available_ports = itertools.chain(range(start, self.end_port), range(self.start_port, start)) + start = self._align(random.randrange(self.start_port, self.end_port), total_shards) + shard_id + beginning = self._align(self.start_port, total_shards) + shard_id + available_ports = itertools.chain(range(start, self.end_port, total_shards), + range(beginning, start, total_shards)) for port in available_ports: - if port % total_shards == shard_id: - yield port + yield port DefaultShardAwarePortGenerator = ShardAwarePortGenerator(DEFAULT_LOCAL_PORT_LOW, DEFAULT_LOCAL_PORT_HIGH) From a3f37e86235c7aa5a6d8f0114d8159c8e998381f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 17 Jul 2025 19:50:56 +0200 Subject: [PATCH 038/298] MaterializedViewMetadataTestSimple: Explicitly set compaction strategy "test_materialized_view_metadata_alter" checks which strategy is used on a view. The test did not set the strategy explicitly, instead relying on defaults. This default has changed in Scylla, causing the test to fail. --- tests/integration/standard/test_metadata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 8d677030f9..6238e2d98c 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -2126,7 +2126,8 @@ def setUp(self): self.session.execute("CREATE TABLE {0}.{1} (pk int PRIMARY KEY, c int)".format(self.keyspace_name, self.function_table_name)) self.session.execute( "CREATE MATERIALIZED VIEW {0}.mv1 AS SELECT pk, c FROM {0}.{1} " - "WHERE pk IS NOT NULL AND c IS NOT NULL PRIMARY KEY (pk, c)".format( + "WHERE pk IS NOT NULL AND c IS NOT NULL PRIMARY KEY (pk, c) " + "WITH compaction = {{ 'class' : 'SizeTieredCompactionStrategy' }}".format( self.keyspace_name, self.function_table_name) ) From 1f97f6a2118fe94e3ebb81018511dd942e40c6ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 17 Jul 2025 19:51:48 +0200 Subject: [PATCH 039/298] xfail compact storage tests Scylla 2025.1 deprecated compact storage, and disabled in by default, which caused tests to fail. We should start expecting them to fail on version 2025.1 and later. --- tests/integration/__init__.py | 11 +++++++++-- tests/integration/standard/test_metadata.py | 7 ++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index 770b000fca..96aa8fdbee 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -14,6 +14,7 @@ import re import os +from typing import Callable from cassandra.cluster import Cluster from tests import connection_class, EVENT_LOOP_MANAGER @@ -258,6 +259,13 @@ def _id_and_mark(f): return _id_and_mark +def xfail_scylla_version(filter: Callable[[Version], bool], reason: str, *args, **kwargs): + if SCYLLA_VERSION is None: + return pytest.mark.skipif(False, reason="It is just a NoOP Decor, should not skip anything") + current_version = Version(get_scylla_version(SCYLLA_VERSION)) + + return pytest.mark.xfail(filter(current_version), reason=reason, *args, **kwargs) + local = local_decorator_creator() notprotocolv1 = unittest.skipUnless(PROTOCOL_VERSION > 1, 'Protocol v1 not supported') greaterthanprotocolv3 = unittest.skipUnless(PROTOCOL_VERSION >= 4, 'Protocol versions less than 4 are not supported') @@ -297,7 +305,7 @@ def _id_and_mark(f): requiresmallclockgranularity = unittest.skipIf("Windows" in platform.system() or "asyncore" in EVENT_LOOP_MANAGER, "This test is not suitible for environments with large clock granularity") requiressimulacron = unittest.skipIf(SIMULACRON_JAR is None or CASSANDRA_VERSION < Version("2.1"), "Simulacron jar hasn't been specified or C* version is 2.0") - +requirescompactstorage = xfail_scylla_version(lambda v: v >= Version('2025.1.0'), reason="ScyllaDB deprecated compact storage", raises=InvalidRequest) libevtest = unittest.skipUnless(EVENT_LOOP_MANAGER=="libev", "Test timing designed for libev loop") def wait_for_node_socket(node, timeout): @@ -703,7 +711,6 @@ def xfail_scylla_version_lt(reason, oss_scylla_version, ent_scylla_version, *arg return pytest.mark.xfail(current_version < Version(oss_scylla_version), reason=reason, *args, **kwargs) - class UpDownWaiter(object): def __init__(self, host): diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 6238e2d98c..73bc40878a 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -42,7 +42,8 @@ greaterthancass21, assert_startswith, greaterthanorequalcass40, lessthancass40, TestCluster, requires_java_udf, requires_composite_type, - requires_collection_indexes, SCYLLA_VERSION, xfail_scylla, xfail_scylla_version_lt) + requires_collection_indexes, SCYLLA_VERSION, xfail_scylla, xfail_scylla_version_lt, + requirescompactstorage) from tests.util import wait_until @@ -428,6 +429,7 @@ def test_composite_in_compound_primary_key_ordering(self): self.check_create_statement(tablemeta, create_statement) @lessthancass40 + @requirescompactstorage def test_compact_storage(self): create_statement = self.make_create_statement(["a"], [], ["b"]) create_statement += " WITH COMPACT STORAGE" @@ -437,6 +439,7 @@ def test_compact_storage(self): self.check_create_statement(tablemeta, create_statement) @lessthancass40 + @requirescompactstorage def test_dense_compact_storage(self): create_statement = self.make_create_statement(["a"], ["b"], ["c"]) create_statement += " WITH COMPACT STORAGE" @@ -456,6 +459,7 @@ def test_counter(self): self.check_create_statement(tablemeta, create_statement) @lessthancass40 + @requirescompactstorage def test_counter_with_compact_storage(self): """ PYTHON-1100 """ create_statement = ( @@ -468,6 +472,7 @@ def test_counter_with_compact_storage(self): self.check_create_statement(tablemeta, create_statement) @lessthancass40 + @requirescompactstorage def test_counter_with_dense_compact_storage(self): create_statement = ( "CREATE TABLE {keyspace}.{table} (" From 2b2fea29743ebcbb0ac57001322f7987c309e0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 17 Jul 2025 20:40:30 +0200 Subject: [PATCH 040/298] CI: Update Scylla to 2025.2 --- .github/workflows/integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index bccbdc63cc..b75ef57c42 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -68,6 +68,6 @@ jobs: - name: Test with pytest run: | export EVENT_LOOP_MANAGER=${{ matrix.event_loop_manager }} - export SCYLLA_VERSION='release:6.2' + export SCYLLA_VERSION='release:2025.2' export PROTOCOL_VERSION=4 uv run pytest tests/integration/standard/ tests/integration/cqlengine/ From 222e5bdab162669617b7bbf5d9edbba545c1d77a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Mon, 7 Jul 2025 16:37:44 +0200 Subject: [PATCH 041/298] test_tablets: Remove CCM_CLUSTER global No need for it - cluster is already stored in __init__.py --- tests/integration/standard/test_tablets.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/integration/standard/test_tablets.py b/tests/integration/standard/test_tablets.py index 79dd166603..0216f7843a 100644 --- a/tests/integration/standard/test_tablets.py +++ b/tests/integration/standard/test_tablets.py @@ -5,15 +5,12 @@ from cassandra.cluster import Cluster from cassandra.policies import ConstantReconnectionPolicy, RoundRobinPolicy, TokenAwarePolicy -from tests.integration import PROTOCOL_VERSION, use_cluster +from tests.integration import PROTOCOL_VERSION, use_cluster, get_cluster from tests.unit.test_host_connection_pool import LOGGER -CCM_CLUSTER = None def setup_module(): - global CCM_CLUSTER - - CCM_CLUSTER = use_cluster('tablets', [3], start=True) + use_cluster('tablets', [3], start=True) class TestTabletsIntegration: @@ -193,7 +190,7 @@ def drop_ks(_): def test_tablets_invalidation_decommission_non_cc_node(self): def decommission_non_cc_node(rec): # Drop and recreate ks and table to trigger tablets invalidation - for node in CCM_CLUSTER.nodes.values(): + for node in get_cluster().nodes.values(): if self.cluster.control_connection._connection.endpoint.address == node.network_interfaces["storage"][0]: # Ignore node that control connection is connected to continue From 0c00198ca7e0d99643ad924849504d8bffbefe5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 19:36:02 +0200 Subject: [PATCH 042/298] Replace self.assertEqual with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used command: ``` ast-grep run --pattern 'self.assertEqual($A, $B)' --rewrite 'assert $A == $B' --lang python ./tests -U ast-grep run --pattern 'self.assertEqual($A, $B, $C)' --rewrite 'assert $A == $B, $C' --lang python ./tests -U ``` Some manual fixes were required for edge cases. --- .../columns/test_container_columns.py | 106 ++--- .../cqlengine/columns/test_counter_column.py | 6 +- .../cqlengine/columns/test_validation.py | 40 +- .../cqlengine/connections/test_connection.py | 28 +- .../cqlengine/management/test_management.py | 17 +- .../model/test_class_construction.py | 30 +- .../integration/cqlengine/model/test_model.py | 28 +- .../cqlengine/model/test_model_io.py | 84 ++-- .../cqlengine/model/test_polymorphism.py | 4 +- .../integration/cqlengine/model/test_udts.py | 82 ++-- .../cqlengine/model/test_updates.py | 52 +-- .../cqlengine/model/test_value_lists.py | 4 +- .../operators/test_where_operators.py | 32 +- .../cqlengine/query/test_batch_query.py | 38 +- .../integration/cqlengine/query/test_named.py | 42 +- .../cqlengine/query/test_queryoperators.py | 14 +- .../cqlengine/query/test_queryset.py | 180 ++++---- .../cqlengine/query/test_updates.py | 74 ++-- .../statements/test_assignment_clauses.py | 156 +++---- .../statements/test_base_statement.py | 21 +- .../statements/test_delete_statement.py | 24 +- .../statements/test_insert_statement.py | 12 +- .../statements/test_select_statement.py | 22 +- .../statements/test_update_statement.py | 12 +- .../cqlengine/statements/test_where_clause.py | 4 +- .../integration/cqlengine/test_batch_query.py | 24 +- .../integration/cqlengine/test_connections.py | 8 +- .../integration/cqlengine/test_consistency.py | 14 +- .../cqlengine/test_context_query.py | 22 +- tests/integration/cqlengine/test_ifexists.py | 32 +- .../integration/cqlengine/test_ifnotexists.py | 22 +- .../cqlengine/test_lwt_conditional.py | 62 +-- tests/integration/cqlengine/test_ttl.py | 12 +- tests/integration/long/test_failure_types.py | 4 +- tests/integration/long/test_ipv6.py | 2 +- tests/integration/long/test_large_data.py | 10 +- .../long/test_loadbalancingpolicies.py | 22 +- tests/integration/long/test_policies.py | 6 +- tests/integration/long/test_schema.py | 2 +- .../simulacron/test_backpressure.py | 2 +- tests/integration/simulacron/test_cluster.py | 10 +- .../integration/simulacron/test_connection.py | 4 +- .../simulacron/test_empty_column.py | 27 +- tests/integration/simulacron/test_endpoint.py | 14 +- tests/integration/simulacron/test_policies.py | 36 +- .../column_encryption/test_policies.py | 28 +- .../standard/test_authentication.py | 4 +- .../test_authentication_misconfiguration.py | 2 +- .../standard/test_client_warnings.py | 8 +- tests/integration/standard/test_cluster.py | 125 +++--- tests/integration/standard/test_concurrent.py | 22 +- tests/integration/standard/test_connection.py | 2 +- .../standard/test_control_connection.py | 20 +- .../standard/test_custom_payload.py | 2 +- .../standard/test_custom_protocol_handler.py | 12 +- .../standard/test_cython_protocol_handlers.py | 14 +- tests/integration/standard/test_metadata.py | 277 +++++++------ tests/integration/standard/test_metrics.py | 24 +- tests/integration/standard/test_policies.py | 6 +- .../standard/test_prepared_statements.py | 43 +- tests/integration/standard/test_query.py | 241 ++++++----- .../integration/standard/test_query_paging.py | 42 +- .../standard/test_rack_aware_policy.py | 8 +- .../standard/test_rate_limit_exceeded.py | 2 +- tests/integration/standard/test_routing.py | 2 +- .../standard/test_row_factories.py | 32 +- .../integration/standard/test_shard_aware.py | 4 +- .../standard/test_single_interface.py | 8 +- tests/integration/standard/test_types.py | 101 +++-- tests/integration/standard/test_udts.py | 76 ++-- tests/integration/upgrade/test_upgrade.py | 26 +- tests/unit/advanced/test_execution_profile.py | 4 +- tests/unit/advanced/test_geometry.py | 69 ++-- tests/unit/advanced/test_graph.py | 94 ++--- tests/unit/advanced/test_insights.py | 275 ++++++------- tests/unit/advanced/test_metadata.py | 4 +- tests/unit/advanced/test_policies.py | 14 +- tests/unit/column_encryption/test_policies.py | 14 +- tests/unit/cqlengine/test_columns.py | 6 +- tests/unit/io/test_twistedreactor.py | 13 +- tests/unit/io/utils.py | 15 +- tests/unit/test_auth.py | 5 +- tests/unit/test_cluster.py | 82 ++-- tests/unit/test_concurrent.py | 2 +- tests/unit/test_connection.py | 94 ++--- tests/unit/test_control_connection.py | 108 ++--- tests/unit/test_endpoints.py | 15 +- tests/unit/test_exception.py | 8 +- tests/unit/test_host_connection_pool.py | 14 +- tests/unit/test_marshalling.py | 20 +- tests/unit/test_metadata.py | 210 +++++----- tests/unit/test_orderedmap.py | 54 ++- tests/unit/test_parameter_binding.py | 36 +- tests/unit/test_policies.py | 254 ++++++------ tests/unit/test_protocol.py | 23 +- tests/unit/test_protocol_features.py | 6 +- tests/unit/test_query.py | 10 +- tests/unit/test_response_future.py | 28 +- tests/unit/test_resultset.py | 34 +- tests/unit/test_row_factories.py | 6 +- tests/unit/test_segment.py | 80 ++-- tests/unit/test_shard_aware.py | 12 +- tests/unit/test_sortedset.py | 144 +++---- tests/unit/test_tablets.py | 6 +- tests/unit/test_time_util.py | 30 +- tests/unit/test_timestamps.py | 28 +- tests/unit/test_types.py | 384 ++++++++---------- tests/unit/test_util_types.py | 132 +++--- 108 files changed, 2337 insertions(+), 2574 deletions(-) diff --git a/tests/integration/cqlengine/columns/test_container_columns.py b/tests/integration/cqlengine/columns/test_container_columns.py index 4c21ff55d8..040d9125e4 100644 --- a/tests/integration/cqlengine/columns/test_container_columns.py +++ b/tests/integration/cqlengine/columns/test_container_columns.py @@ -151,12 +151,12 @@ def test_partial_updates(self): m1.int_set.add(5) m1.int_set.remove(1) - self.assertEqual(m1.int_set, set((2, 3, 4, 5))) + assert m1.int_set == set((2, 3, 4, 5)) m1.save() m2 = TestSetModel.get(partition=m1.partition) - self.assertEqual(m2.int_set, set((2, 3, 4, 5))) + assert m2.int_set == set((2, 3, 4, 5)) def test_instantiation_with_column_class(self): """ @@ -178,9 +178,9 @@ def test_to_python(self): column = columns.Set(JsonTestColumn) val = set((1, 2, 3)) db_val = column.to_database(val) - self.assertEqual(db_val, set(json.dumps(v) for v in val)) + assert db_val == set(json.dumps(v) for v in val) py_val = column.to_python(db_val) - self.assertEqual(py_val, val) + assert py_val == val def test_default_empty_container_saving(self): """ tests that the default empty container is not saved if it hasn't been updated """ @@ -191,7 +191,7 @@ def test_default_empty_container_saving(self): TestSetModel.create(partition=pkey) m = TestSetModel.get(partition=pkey) - self.assertEqual(m.int_set, set((3, 4))) + assert m.int_set == set((3, 4)) class TestListModel(Model): @@ -230,14 +230,14 @@ def test_io_success(self): self.assertIsInstance(m2.int_list, list) self.assertIsInstance(m2.text_list, list) - self.assertEqual(len(m2.int_list), 2) - self.assertEqual(len(m2.text_list), 2) + assert len(m2.int_list) == 2 + assert len(m2.text_list) == 2 - self.assertEqual(m2.int_list[0], 1) - self.assertEqual(m2.int_list[1], 2) + assert m2.int_list[0] == 1 + assert m2.int_list[1] == 2 - self.assertEqual(m2.text_list[0], 'kai') - self.assertEqual(m2.text_list[1], 'andreas') + assert m2.text_list[0] == 'kai' + assert m2.text_list[1] == 'andreas' def test_type_validation(self): """ @@ -275,7 +275,7 @@ def test_partial_updates(self): expected = full m2 = TestListModel.get(partition=m1.partition) - self.assertEqual(list(m2.int_list), expected) + assert list(m2.int_list) == expected def test_instantiation_with_column_class(self): """ @@ -297,9 +297,9 @@ def test_to_python(self): column = columns.List(JsonTestColumn) val = [1, 2, 3] db_val = column.to_database(val) - self.assertEqual(db_val, [json.dumps(v) for v in val]) + assert db_val == [json.dumps(v) for v in val] py_val = column.to_python(db_val) - self.assertEqual(py_val, val) + assert py_val == val def test_default_empty_container_saving(self): """ tests that the default empty container is not saved if it hasn't been updated """ @@ -310,7 +310,7 @@ def test_default_empty_container_saving(self): TestListModel.create(partition=pkey) m = TestListModel.get(partition=pkey) - self.assertEqual(m.int_list, [1, 2, 3, 4]) + assert m.int_list == [1, 2, 3, 4] def test_remove_entry_works(self): pkey = uuid4() @@ -318,7 +318,7 @@ def test_remove_entry_works(self): tmp.int_list.pop() tmp.update() tmp = TestListModel.get(partition=pkey) - self.assertEqual(tmp.int_list, [1]) + assert tmp.int_list == [1] def test_update_from_non_empty_to_empty(self): pkey = uuid4() @@ -327,7 +327,7 @@ def test_update_from_non_empty_to_empty(self): tmp.update() tmp = TestListModel.get(partition=pkey) - self.assertEqual(tmp.int_list, []) + assert tmp.int_list == [] def test_insert_none(self): pkey = uuid4() @@ -341,12 +341,12 @@ def test_blind_list_updates_from_none(self): m.save() m2 = TestListModel.get(partition=m.partition) - self.assertEqual(m2.int_list, expected) + assert m2.int_list == expected TestListModel.objects(partition=m.partition).update(int_list=[]) m3 = TestListModel.get(partition=m.partition) - self.assertEqual(m3.int_list, []) + assert m3.int_list == [] class TestMapModel(Model): @@ -405,8 +405,8 @@ def test_io_success(self): self.assertTrue(1 in m2.int_map) self.assertTrue(2 in m2.int_map) - self.assertEqual(m2.int_map[1], k1) - self.assertEqual(m2.int_map[2], k2) + assert m2.int_map[1] == k1 + assert m2.int_map[2] == k2 self.assertAlmostEqual(get_total_seconds(now - m2.text_map['now']), 0, 2) self.assertAlmostEqual(get_total_seconds(then - m2.text_map['then']), 0, 2) @@ -449,7 +449,7 @@ def test_partial_updates(self): m1.save() m2 = TestMapModel.get(partition=m1.partition) - self.assertEqual(m2.text_map, final) + assert m2.text_map == final def test_updates_from_none(self): """ Tests that updates from None work as expected """ @@ -459,7 +459,7 @@ def test_updates_from_none(self): m.save() m2 = TestMapModel.get(partition=m.partition) - self.assertEqual(m2.int_map, expected) + assert m2.int_map == expected m2.int_map = None m2.save() @@ -474,7 +474,7 @@ def test_blind_updates_from_none(self): m.save() m2 = TestMapModel.get(partition=m.partition) - self.assertEqual(m2.int_map, expected) + assert m2.int_map == expected TestMapModel.objects(partition=m.partition).update(int_map={}) @@ -488,7 +488,7 @@ def test_updates_to_none(self): m.save() m2 = TestMapModel.get(partition=m.partition) - self.assertEqual(m2.int_map, {}) + assert m2.int_map == {} def test_instantiation_with_column_class(self): """ @@ -512,9 +512,9 @@ def test_to_python(self): column = columns.Map(JsonTestColumn, JsonTestColumn) val = {1: 2, 3: 4, 5: 6} db_val = column.to_database(val) - self.assertEqual(db_val, dict((json.dumps(k), json.dumps(v)) for k, v in val.items())) + assert db_val == dict((json.dumps(k), json.dumps(v)) for k, v in val.items()) py_val = column.to_python(db_val) - self.assertEqual(py_val, val) + assert py_val == val def test_default_empty_container_saving(self): """ tests that the default empty container is not saved if it hasn't been updated """ @@ -526,7 +526,7 @@ def test_default_empty_container_saving(self): TestMapModel.create(partition=pkey) m = TestMapModel.get(partition=pkey) - self.assertEqual(m.int_map, tmap) + assert m.int_map == tmap class TestCamelMapModel(Model): @@ -621,9 +621,9 @@ def test_io_success(self): self.assertIsInstance(m2.text_tuple, tuple) self.assertIsInstance(m2.mixed_tuple, tuple) - self.assertEqual((1, 2, 3), m2.int_tuple) - self.assertEqual(('kai', 'andreas'), m2.text_tuple) - self.assertEqual(('first', 2, 'Third'), m2.mixed_tuple) + assert (1, 2, 3) == m2.int_tuple + assert ('kai', 'andreas') == m2.text_tuple + assert ('first', 2, 'Third') == m2.mixed_tuple def test_type_validation(self): """ @@ -654,7 +654,7 @@ def test_instantiation_with_column_class(self): self.assertIsInstance(mixed_tuple.types[0], columns.Text) self.assertIsInstance(mixed_tuple.types[1], columns.Integer) self.assertIsInstance(mixed_tuple.types[2], columns.Text) - self.assertEqual(len(mixed_tuple.types), 3) + assert len(mixed_tuple.types) == 3 def test_default_empty_container_saving(self): """ @@ -673,7 +673,7 @@ def test_default_empty_container_saving(self): TestTupleModel.create(partition=pkey) m = TestTupleModel.get(partition=pkey) - self.assertEqual(m.int_tuple, (1, 2, 3)) + assert m.int_tuple == (1, 2, 3) def test_updates(self): """ @@ -693,7 +693,7 @@ def test_updates(self): m1.save() m2 = TestTupleModel.get(partition=m1.partition) - self.assertEqual(tuple(m2.int_tuple), replacement) + assert tuple(m2.int_tuple) == replacement def test_update_from_non_empty_to_empty(self): """ @@ -711,7 +711,7 @@ def test_update_from_non_empty_to_empty(self): tmp.update() tmp = TestTupleModel.get(partition=pkey) - self.assertEqual(tmp.int_tuple, (None)) + assert tmp.int_tuple == (None) def test_insert_none(self): """ @@ -725,7 +725,7 @@ def test_insert_none(self): """ pkey = uuid4() tmp = TestTupleModel.create(partition=pkey, int_tuple=(None)) - self.assertEqual((None), tmp.int_tuple) + assert (None) == tmp.int_tuple def test_blind_tuple_updates_from_none(self): """ @@ -744,12 +744,12 @@ def test_blind_tuple_updates_from_none(self): m.save() m2 = TestTupleModel.get(partition=m.partition) - self.assertEqual(m2.int_tuple, expected) + assert m2.int_tuple == expected TestTupleModel.objects(partition=m.partition).update(int_tuple=None) m3 = TestTupleModel.get(partition=m.partition) - self.assertEqual(m3.int_tuple, None) + assert m3.int_tuple == None class TestNestedModel(Model): @@ -829,9 +829,9 @@ def test_io_success(self): self.assertIsInstance(m2.map_list, dict) self.assertIsInstance(m2.map_list.get("key2"), list) - self.assertEqual(list_list_master, m2.list_list) - self.assertEqual(map_list_master, m2.map_list) - self.assertEqual(set_tuple_master, m2.set_tuple) + assert list_list_master == m2.list_list + assert map_list_master == m2.map_list + assert set_tuple_master == m2.set_tuple self.assertIsInstance(m2.set_tuple.pop(), tuple) def test_type_validation(self): @@ -902,9 +902,9 @@ def test_default_empty_container_saving(self): TestNestedModel.create(partition=pkey) m = TestNestedModel.get(partition=pkey) - self.assertEqual(m.list_list, list_list_master) - self.assertEqual(m.map_list, map_list_master) - self.assertEqual(m.set_tuple, set_tuple_master) + assert m.list_list == list_list_master + assert m.map_list == map_list_master + assert m.set_tuple == set_tuple_master def test_updates(self): """ @@ -931,9 +931,9 @@ def test_updates(self): m1.save() m2 = TestNestedModel.get(partition=m1.partition) - self.assertEqual(m2.list_list, list_list_replacement) - self.assertEqual(m2.map_list, map_list_replacement) - self.assertEqual(m2.set_tuple, set_tuple_replacement) + assert m2.list_list == list_list_replacement + assert m2.map_list == map_list_replacement + assert m2.set_tuple == set_tuple_replacement def test_update_from_non_empty_to_empty(self): """ @@ -955,9 +955,9 @@ def test_update_from_non_empty_to_empty(self): tmp.update() tmp = TestNestedModel.get(partition=tmp.partition) - self.assertEqual(tmp.list_list, []) - self.assertEqual(tmp.map_list, {}) - self.assertEqual(tmp.set_tuple, set()) + assert tmp.list_list == [] + assert tmp.map_list == {} + assert tmp.set_tuple == set() def test_insert_none(self): """ @@ -971,8 +971,8 @@ def test_insert_none(self): """ pkey = uuid4() tmp = TestNestedModel.create(partition=pkey, list_list=(None), map_list=(None), set_tuple=(None)) - self.assertEqual([], tmp.list_list) - self.assertEqual({}, tmp.map_list) - self.assertEqual(set(), tmp.set_tuple) + assert [] == tmp.list_list + assert {} == tmp.map_list + assert set() == tmp.set_tuple diff --git a/tests/integration/cqlengine/columns/test_counter_column.py b/tests/integration/cqlengine/columns/test_counter_column.py index 160b98d7c2..338a0944dc 100644 --- a/tests/integration/cqlengine/columns/test_counter_column.py +++ b/tests/integration/cqlengine/columns/test_counter_column.py @@ -120,12 +120,12 @@ def test_save_after_no_update(self): # read back instance = TestCounterModel.get(partition=instance.partition) - self.assertEqual(instance.counter, expected_value) + assert instance.counter == expected_value # save after doing nothing instance.save() - self.assertEqual(instance.counter, expected_value) + assert instance.counter == expected_value # make sure there was no increment instance = TestCounterModel.get(partition=instance.partition) - self.assertEqual(instance.counter, expected_value) + assert instance.counter == expected_value diff --git a/tests/integration/cqlengine/columns/test_validation.py b/tests/integration/cqlengine/columns/test_validation.py index 32f20d52ff..bd755fa69d 100644 --- a/tests/integration/cqlengine/columns/test_validation.py +++ b/tests/integration/cqlengine/columns/test_validation.py @@ -53,7 +53,7 @@ def test_datetime_io(self): now = datetime.now() self.DatetimeTest.objects.create(test_id=0, created_at=now) dt2 = self.DatetimeTest.objects(test_id=0).first() - self.assertEqual(dt2.created_at.timetuple()[:6], now.timetuple()[:6]) + assert dt2.created_at.timetuple()[:6] == now.timetuple()[:6] def test_datetime_tzinfo_io(self): class TZ(tzinfo): @@ -65,20 +65,20 @@ def dst(self, date_time): now = datetime(1982, 1, 1, tzinfo=TZ()) dt = self.DatetimeTest.objects.create(test_id=1, created_at=now) dt2 = self.DatetimeTest.objects(test_id=1).first() - self.assertEqual(dt2.created_at.timetuple()[:6], (now + timedelta(hours=1)).timetuple()[:6]) + assert dt2.created_at.timetuple()[:6] == (now + timedelta(hours=1)).timetuple()[:6] @greaterthanorequalcass30 def test_datetime_date_support(self): today = date.today() self.DatetimeTest.objects.create(test_id=2, created_at=today) dt2 = self.DatetimeTest.objects(test_id=2).first() - self.assertEqual(dt2.created_at.isoformat(), datetime(today.year, today.month, today.day).isoformat()) + assert dt2.created_at.isoformat() == datetime(today.year, today.month, today.day).isoformat() result = self.DatetimeTest.objects.all().allow_filtering().filter(test_id=2).first() - self.assertEqual(result.created_at, datetime.combine(today, datetime.min.time())) + assert result.created_at == datetime.combine(today, datetime.min.time()) result = self.DatetimeTest.objects.all().allow_filtering().filter(test_id=2, created_at=today).first() - self.assertEqual(result.created_at, datetime.combine(today, datetime.min.time())) + assert result.created_at == datetime.combine(today, datetime.min.time()) def test_datetime_none(self): dt = self.DatetimeTest.objects.create(test_id=3, created_at=None) @@ -97,13 +97,13 @@ def test_datetime_timestamp(self): dt_value = 1454520554 self.DatetimeTest.objects.create(test_id=5, created_at=dt_value) dt2 = self.DatetimeTest.objects(test_id=5).first() - self.assertEqual(dt2.created_at, datetime.fromtimestamp(dt_value, tz=timezone.utc).replace(tzinfo=None)) + assert dt2.created_at == datetime.fromtimestamp(dt_value, tz=timezone.utc).replace(tzinfo=None) def test_datetime_large(self): dt_value = datetime(2038, 12, 31, 10, 10, 10, 123000) self.DatetimeTest.objects.create(test_id=6, created_at=dt_value) dt2 = self.DatetimeTest.objects(test_id=6).first() - self.assertEqual(dt2.created_at, dt_value) + assert dt2.created_at == dt_value def test_datetime_truncate_microseconds(self): """ @@ -123,7 +123,7 @@ def test_datetime_truncate_microseconds(self): dt_truncated = datetime(2024, 12, 31, 10, 10, 10, 923000) self.DatetimeTest.objects.create(test_id=6, created_at=dt_value) dt2 = self.DatetimeTest.objects(test_id=6).first() - self.assertEqual(dt2.created_at,dt_truncated) + assert dt2.created_at == dt_truncated finally: # We need to always return behavior to default DateTime.truncate_microseconds = False @@ -141,9 +141,9 @@ def setUpClass(cls): def test_default_is_set(self): tmp = self.BoolDefaultValueTest.create(test_id=1) - self.assertEqual(True, tmp.stuff) + assert True == tmp.stuff tmp2 = self.BoolDefaultValueTest.get(test_id=1) - self.assertEqual(True, tmp2.stuff) + assert True == tmp2.stuff class TestBoolValidation(BaseCassEngTestCase): @@ -183,7 +183,7 @@ def test_varint_io(self): long_int = 92834902384092834092384028340283048239048203480234823048230482304820348239 int1 = self.VarIntTest.objects.create(test_id=0, bignum=long_int) int2 = self.VarIntTest.objects(test_id=0).first() - self.assertEqual(int1.bignum, int2.bignum) + assert int1.bignum == int2.bignum with self.assertRaises(ValidationError): self.VarIntTest.objects.create(test_id=0, bignum="not_a_number") @@ -235,15 +235,15 @@ def _check_value_is_correct_in_db(self, value): result = self.model_class.objects(test_id=0).first() self.assertIsInstance(result.class_param, self.python_klass) - self.assertEqual(result.class_param, value_to_compare) + assert result.class_param == value_to_compare result = self.model_class.objects.all().allow_filtering().filter(test_id=0).first() self.assertIsInstance(result.class_param, self.python_klass) - self.assertEqual(result.class_param, value_to_compare) + assert result.class_param == value_to_compare result = self.model_class.objects.all().allow_filtering().filter(test_id=0, class_param=value).first() self.assertIsInstance(result.class_param, self.python_klass) - self.assertEqual(result.class_param, value_to_compare) + assert result.class_param == value_to_compare return result @@ -616,9 +616,9 @@ def test_type_checking(self): def test_unaltering_validation(self): """ Test the validation step doesn't re-interpret values. """ - self.assertEqual(Ascii().validate(''), '') - self.assertEqual(Ascii().validate(None), None) - self.assertEqual(Ascii().validate('yo'), 'yo') + assert Ascii().validate('') == '' + assert Ascii().validate(None) == None + assert Ascii().validate('yo') == 'yo' def test_non_required_validation(self): """ Tests that validation is ok on none and blank values if required is False. """ @@ -739,9 +739,9 @@ def test_type_checking(self): def test_unaltering_validation(self): """ Test the validation step doesn't re-interpret values. """ - self.assertEqual(Text().validate(''), '') - self.assertEqual(Text().validate(None), None) - self.assertEqual(Text().validate('yo'), 'yo') + assert Text().validate('') == '' + assert Text().validate(None) == None + assert Text().validate('yo') == 'yo' def test_non_required_validation(self): """ Tests that validation is ok on none and blank values if required is False """ diff --git a/tests/integration/cqlengine/connections/test_connection.py b/tests/integration/cqlengine/connections/test_connection.py index c63836785e..e5dea115ed 100644 --- a/tests/integration/cqlengine/connections/test_connection.py +++ b/tests/integration/cqlengine/connections/test_connection.py @@ -63,7 +63,7 @@ def test_only_one_connection_is_created(self): number_of_clusters_before = len(_clusters_for_shutdown) connection.default() number_of_clusters_after = len(_clusters_for_shutdown) - self.assertEqual(number_of_clusters_after - number_of_clusters_before, 1) + assert number_of_clusters_after - number_of_clusters_before == 1 class SeveralConnectionsTest(BaseCassEngTestCase): @@ -119,11 +119,11 @@ def test_connection_session_switch(self): sync_table(TestConnectModel) TCM2 = TestConnectModel.create(id=1, keyspace=self.keyspace2) connection.set_session(self.session1) - self.assertEqual(1, TestConnectModel.objects.count()) - self.assertEqual(TestConnectModel.objects.first(), TCM1) + assert 1 == TestConnectModel.objects.count() + assert TestConnectModel.objects.first() == TCM1 connection.set_session(self.session2) - self.assertEqual(1, TestConnectModel.objects.count()) - self.assertEqual(TestConnectModel.objects.first(), TCM2) + assert 1 == TestConnectModel.objects.count() + assert TestConnectModel.objects.first() == TCM2 class ConnectionModel(Model): @@ -135,7 +135,7 @@ class ConnectionInitTest(unittest.TestCase): def test_default_connection_uses_legacy(self): connection.default() conn = connection.get_connection() - self.assertEqual(conn.cluster._config_mode, _ConfigMode.LEGACY) + assert conn.cluster._config_mode == _ConfigMode.LEGACY def test_connection_with_legacy_settings(self): connection.setup( @@ -144,7 +144,7 @@ def test_connection_with_legacy_settings(self): consistency=ConsistencyLevel.LOCAL_ONE ) conn = connection.get_connection() - self.assertEqual(conn.cluster._config_mode, _ConfigMode.LEGACY) + assert conn.cluster._config_mode == _ConfigMode.LEGACY def test_connection_from_session_with_execution_profile(self): cluster = TestCluster(execution_profiles={EXEC_PROFILE_DEFAULT: ExecutionProfile(row_factory=dict_factory)}) @@ -152,7 +152,7 @@ def test_connection_from_session_with_execution_profile(self): connection.default() connection.set_session(session) conn = connection.get_connection() - self.assertEqual(conn.cluster._config_mode, _ConfigMode.PROFILES) + assert conn.cluster._config_mode == _ConfigMode.PROFILES def test_connection_from_session_with_legacy_settings(self): cluster = TestCluster(load_balancing_policy=RoundRobinPolicy()) @@ -160,7 +160,7 @@ def test_connection_from_session_with_legacy_settings(self): session.row_factory = dict_factory connection.set_session(session) conn = connection.get_connection() - self.assertEqual(conn.cluster._config_mode, _ConfigMode.LEGACY) + assert conn.cluster._config_mode == _ConfigMode.LEGACY def test_uncommitted_session_uses_legacy(self): cluster = TestCluster() @@ -168,7 +168,7 @@ def test_uncommitted_session_uses_legacy(self): session.row_factory = dict_factory connection.set_session(session) conn = connection.get_connection() - self.assertEqual(conn.cluster._config_mode, _ConfigMode.LEGACY) + assert conn.cluster._config_mode == _ConfigMode.LEGACY def test_legacy_insert_query(self): connection.setup( @@ -176,21 +176,21 @@ def test_legacy_insert_query(self): default_keyspace=DEFAULT_KEYSPACE, consistency=ConsistencyLevel.LOCAL_ONE ) - self.assertEqual(connection.get_connection().cluster._config_mode, _ConfigMode.LEGACY) + assert connection.get_connection().cluster._config_mode == _ConfigMode.LEGACY sync_table(ConnectionModel) ConnectionModel.objects.create(key=0, some_data='text0') ConnectionModel.objects.create(key=1, some_data='text1') - self.assertEqual(ConnectionModel.objects(key=0)[0].some_data, 'text0') + assert ConnectionModel.objects(key=0)[0].some_data == 'text0' def test_execution_profile_insert_query(self): cluster = TestCluster(execution_profiles={EXEC_PROFILE_DEFAULT: ExecutionProfile(row_factory=dict_factory)}) session = cluster.connect() connection.default() connection.set_session(session) - self.assertEqual(connection.get_connection().cluster._config_mode, _ConfigMode.PROFILES) + assert connection.get_connection().cluster._config_mode == _ConfigMode.PROFILES sync_table(ConnectionModel) ConnectionModel.objects.create(key=0, some_data='text0') ConnectionModel.objects.create(key=1, some_data='text1') - self.assertEqual(ConnectionModel.objects(key=0)[0].some_data, 'text0') + assert ConnectionModel.objects(key=0)[0].some_data == 'text0' diff --git a/tests/integration/cqlengine/management/test_management.py b/tests/integration/cqlengine/management/test_management.py index 55fb62f22c..5bfb5e38ba 100644 --- a/tests/integration/cqlengine/management/test_management.py +++ b/tests/integration/cqlengine/management/test_management.py @@ -177,16 +177,16 @@ def setUp(self): def test_add_column(self): sync_table(FirstModel) meta_columns = _get_table_metadata(FirstModel).columns - self.assertEqual(set(meta_columns), set(FirstModel._columns)) + assert set(meta_columns) == set(FirstModel._columns) sync_table(SecondModel) meta_columns = _get_table_metadata(FirstModel).columns - self.assertEqual(set(meta_columns), set(SecondModel._columns)) + assert set(meta_columns) == set(SecondModel._columns) sync_table(ThirdModel) meta_columns = _get_table_metadata(FirstModel).columns - self.assertEqual(len(meta_columns), 5) - self.assertEqual(len(ThirdModel._columns), 4) + assert len(meta_columns) == 5 + assert len(ThirdModel._columns) == 4 self.assertIn('fourth_key', meta_columns) self.assertNotIn('fourth_key', ThirdModel._columns) self.assertIn('blah', ThirdModel._columns) @@ -194,8 +194,8 @@ def test_add_column(self): sync_table(FourthModel) meta_columns = _get_table_metadata(FirstModel).columns - self.assertEqual(len(meta_columns), 5) - self.assertEqual(len(ThirdModel._columns), 4) + assert len(meta_columns) == 5 + assert len(ThirdModel._columns) == 4 self.assertIn('fourth_key', meta_columns) self.assertNotIn('fourth_key', FourthModel._columns) self.assertIn('renamed', FourthModel._columns) @@ -239,8 +239,7 @@ def test_set_table_properties(self): expected.update({'read_repair_chance': 0.17985}) options = management._get_table_metadata(ModelWithTableProperties).options - self.assertEqual(dict([(k, options.get(k)) for k in expected.keys()]), - expected) + assert dict([(k, options.get(k)) for k in expected.keys()]) == expected def test_table_property_update(self): ModelWithTableProperties.__options__['bloom_filter_fp_chance'] = 0.66778 @@ -485,4 +484,4 @@ class StaticModel(Model): with mock.patch.object(session, "execute", wraps=session.execute) as m2: sync_table(StaticModel) - self.assertEqual(len(m2.call_args_list), 0) + assert len(m2.call_args_list) == 0 diff --git a/tests/integration/cqlengine/model/test_class_construction.py b/tests/integration/cqlengine/model/test_class_construction.py index dae97c4438..1488a6fd11 100644 --- a/tests/integration/cqlengine/model/test_class_construction.py +++ b/tests/integration/cqlengine/model/test_class_construction.py @@ -63,13 +63,13 @@ class TestPerson(Model): inst1 = TestPerson() self.assertHasAttr(inst1, 'first_name') self.assertHasAttr(inst1, 'last_name') - self.assertEqual(inst1.first_name, 'kevin') - self.assertEqual(inst1.last_name, 'deldycke') + assert inst1.first_name == 'kevin' + assert inst1.last_name == 'deldycke' # Check that values on instantiation overrides defaults. inst2 = TestPerson(first_name='bob', last_name='joe') - self.assertEqual(inst2.first_name, 'bob') - self.assertEqual(inst2.last_name, 'joe') + assert inst2.first_name == 'bob' + assert inst2.last_name == 'joe' def test_db_map(self): """ @@ -83,8 +83,8 @@ class WildDBNames(Model): numbers = columns.Integer(db_field='integers_etc') db_map = WildDBNames._db_map - self.assertEqual(db_map['words_and_whatnot'], 'content') - self.assertEqual(db_map['integers_etc'], 'numbers') + assert db_map['words_and_whatnot'] == 'content' + assert db_map['integers_etc'] == 'numbers' def test_attempting_to_make_duplicate_column_names_fails(self): """ @@ -108,7 +108,7 @@ class Stuff(Model): content = columns.Text() numbers = columns.Integer() - self.assertEqual([x for x in Stuff._columns.keys()], ['id', 'words', 'content', 'numbers']) + assert [x for x in Stuff._columns.keys()] == ['id', 'words', 'content', 'numbers'] def test_exception_raised_when_creating_class_without_pk(self): with self.assertRaises(ModelDefinitionException): @@ -130,8 +130,8 @@ class Stuff(Model): inst2 = Stuff(num=7) self.assertNotEqual(inst1.num, inst2.num) - self.assertEqual(inst1.num, 5) - self.assertEqual(inst2.num, 7) + assert inst1.num == 5 + assert inst2.num == 7 def test_superclass_fields_are_inherited(self): """ @@ -179,7 +179,7 @@ class ModelWithPartitionKeys(Model): self.assertTrue(cols['p2'].partition_key) obj = ModelWithPartitionKeys(p1='a', p2='b') - self.assertEqual(obj.pk, ('a', 'b')) + assert obj.pk == ('a', 'b') def test_del_attribute_is_assigned_properly(self): """ Tests that columns that can be deleted have the del attribute """ @@ -236,7 +236,7 @@ class NoKeyspace(Model): __abstract__ = True key = columns.UUID(primary_key=True) - self.assertEqual(len(warn), 0) + assert len(warn) == 0 class TestManualTableNaming(BaseCassEngTestCase): @@ -278,8 +278,8 @@ def test_proper_table_naming_case_insensitive(self): @test_category object_mapper """ - self.assertEqual(self.RenamedCaseInsensitiveTest.column_family_name(include_keyspace=False), 'manual_name') - self.assertEqual(self.RenamedCaseInsensitiveTest.column_family_name(include_keyspace=True), 'whatever.manual_name') + assert self.RenamedCaseInsensitiveTest.column_family_name(include_keyspace=False) == 'manual_name' + assert self.RenamedCaseInsensitiveTest.column_family_name(include_keyspace=True) == 'whatever.manual_name' def test_proper_table_naming_case_sensitive(self): """ @@ -292,8 +292,8 @@ def test_proper_table_naming_case_sensitive(self): @test_category object_mapper """ - self.assertEqual(self.RenamedCaseSensitiveTest.column_family_name(include_keyspace=False), '"Manual_Name"') - self.assertEqual(self.RenamedCaseSensitiveTest.column_family_name(include_keyspace=True), 'whatever."Manual_Name"') + assert self.RenamedCaseSensitiveTest.column_family_name(include_keyspace=False) == '"Manual_Name"' + assert self.RenamedCaseSensitiveTest.column_family_name(include_keyspace=True) == 'whatever."Manual_Name"' class AbstractModel(Model): diff --git a/tests/integration/cqlengine/model/test_model.py b/tests/integration/cqlengine/model/test_model.py index d5153843f5..6868fc7fac 100644 --- a/tests/integration/cqlengine/model/test_model.py +++ b/tests/integration/cqlengine/model/test_model.py @@ -35,7 +35,7 @@ class EqualityModel(Model): m0 = EqualityModel(pk=0) m1 = EqualityModel(pk=1) - self.assertEqual(m0, m0) + assert m0 == m0 self.assertNotEqual(m0, m1) def test_model_equality(self): @@ -51,7 +51,7 @@ class EqualityModel1(Model): m0 = EqualityModel0(pk=0) m1 = EqualityModel1(kk=1) - self.assertEqual(m0, m0) + assert m0 == m0 self.assertNotEqual(m0, m1) def test_keywords_as_names(self): @@ -87,8 +87,8 @@ class table(Model): created = table.create(select=0, table='table') selected = table.objects(select=0)[0] - self.assertEqual(created.select, selected.select) - self.assertEqual(created.table, selected.table) + assert created.select == selected.select + assert created.table == selected.table # Alter should work class table(Model): @@ -101,9 +101,9 @@ class table(Model): created = table.create(select=1, table='table') selected = table.objects(select=1)[0] - self.assertEqual(created.select, selected.select) - self.assertEqual(created.table, selected.table) - self.assertEqual(created.where, selected.where) + assert created.select == selected.select + assert created.table == selected.table + assert created.where == selected.where drop_keyspace('keyspace') @@ -112,18 +112,18 @@ class TestModel(Model): k = columns.Integer(primary_key=True) # no model keyspace uses default - self.assertEqual(TestModel.column_family_name(), "%s.test_model" % (models.DEFAULT_KEYSPACE,)) + assert TestModel.column_family_name() == "%s.test_model" % (models.DEFAULT_KEYSPACE,) # model keyspace overrides TestModel.__keyspace__ = "my_test_keyspace" - self.assertEqual(TestModel.column_family_name(), "%s.test_model" % (TestModel.__keyspace__,)) + assert TestModel.column_family_name() == "%s.test_model" % (TestModel.__keyspace__,) # neither set should raise CQLEngineException before failing or formatting an invalid name del TestModel.__keyspace__ with patch('cassandra.cqlengine.models.DEFAULT_KEYSPACE', None): self.assertRaises(CQLEngineException, TestModel.column_family_name) # .. but we can still get the bare CF name - self.assertEqual(TestModel.column_family_name(include_keyspace=False), "test_model") + assert TestModel.column_family_name(include_keyspace=False) == "test_model" def test_column_family_case_sensitive(self): """ @@ -141,15 +141,15 @@ class TestModel(Model): k = columns.Integer(primary_key=True) - self.assertEqual(TestModel.column_family_name(), '%s."TestModel"' % (models.DEFAULT_KEYSPACE,)) + assert TestModel.column_family_name() == '%s."TestModel"' % (models.DEFAULT_KEYSPACE,) TestModel.__keyspace__ = "my_test_keyspace" - self.assertEqual(TestModel.column_family_name(), '%s."TestModel"' % (TestModel.__keyspace__,)) + assert TestModel.column_family_name() == '%s."TestModel"' % (TestModel.__keyspace__,) del TestModel.__keyspace__ with patch('cassandra.cqlengine.models.DEFAULT_KEYSPACE', None): self.assertRaises(CQLEngineException, TestModel.column_family_name) - self.assertEqual(TestModel.column_family_name(include_keyspace=False), '"TestModel"') + assert TestModel.column_family_name(include_keyspace=False) == '"TestModel"' class BuiltInAttributeConflictTest(unittest.TestCase): @@ -216,7 +216,7 @@ def test_comparison(self): TestQueryUpdateModel.text_list.column, TestQueryUpdateModel.text_map.column] - self.assertEqual(l, sorted(l)) + assert l == sorted(l) self.assertNotEqual(TestQueryUpdateModel.partition.column, TestQueryUpdateModel.cluster.column) self.assertLessEqual(TestQueryUpdateModel.partition.column, TestQueryUpdateModel.cluster.column) self.assertGreater(TestQueryUpdateModel.cluster.column, TestQueryUpdateModel.partition.column) diff --git a/tests/integration/cqlengine/model/test_model_io.py b/tests/integration/cqlengine/model/test_model_io.py index 81240e90c5..7fe845190d 100644 --- a/tests/integration/cqlengine/model/test_model_io.py +++ b/tests/integration/cqlengine/model/test_model_io.py @@ -79,7 +79,7 @@ def test_model_save_and_load(self): self.assertIsInstance(tm2, TestModel) for cname in tm._columns.keys(): - self.assertEqual(getattr(tm, cname), getattr(tm2, cname)) + assert getattr(tm, cname) == getattr(tm2, cname) def test_model_instantiation_save_and_load(self): """ @@ -89,13 +89,13 @@ def test_model_instantiation_save_and_load(self): tm = TestModel(count=8, text='123456789') # Tests that values are available on instantiation. self.assertIsNotNone(tm['id']) - self.assertEqual(tm.count, 8) - self.assertEqual(tm.text, '123456789') + assert tm.count == 8 + assert tm.text == '123456789' tm.save() tm2 = TestModel.objects(id=tm.id).first() for cname in tm._columns.keys(): - self.assertEqual(getattr(tm, cname), getattr(tm2, cname)) + assert getattr(tm, cname) == getattr(tm2, cname) def test_model_read_as_dict(self): """ @@ -108,18 +108,16 @@ def test_model_read_as_dict(self): 'text': tm.text, 'a_bool': tm.a_bool, } - self.assertEqual(sorted(tm.keys()), sorted(column_dict.keys())) + assert sorted(tm.keys()) == sorted(column_dict.keys()) self.assertSetEqual(set(tm.values()), set(column_dict.values())) - self.assertEqual( - sorted(tm.items(), key=itemgetter(0)), - sorted(column_dict.items(), key=itemgetter(0))) - self.assertEqual(len(tm), len(column_dict)) + assert sorted(tm.items(), key=itemgetter(0)) == sorted(column_dict.items(), key=itemgetter(0)) + assert len(tm) == len(column_dict) for column_id in column_dict.keys(): - self.assertEqual(tm[column_id], column_dict[column_id]) + assert tm[column_id] == column_dict[column_id] tm['count'] = 6 - self.assertEqual(tm.count, 6) + assert tm.count == 6 def test_model_updating_works_properly(self): """ @@ -132,8 +130,8 @@ def test_model_updating_works_properly(self): tm.save() tm2 = TestModel.objects(id=tm.pk).first() - self.assertEqual(tm.count, tm2.count) - self.assertEqual(tm.a_bool, tm2.a_bool) + assert tm.count == tm2.count + assert tm.a_bool == tm2.a_bool def test_model_deleting_works_properly(self): """ @@ -212,11 +210,11 @@ class AllDatatypesModel(Model): m=UUID('067e6162-3b6f-4ae2-a171-2470b63dff00'), n=int(str(2147483647) + '000'), o=Duration(2, 3, 4)) - self.assertEqual(1, AllDatatypesModel.objects.count()) + assert 1 == AllDatatypesModel.objects.count() output = AllDatatypesModel.objects.first() for i, i_char in enumerate(range(ord('a'), ord('a') + 14)): - self.assertEqual(input[i], output[chr(i_char)]) + assert input[i] == output[chr(i_char)] def test_can_specify_none_instead_of_default(self): self.assertIsNotNone(TestModel.a_bool.column.default) @@ -229,9 +227,9 @@ def test_can_specify_none_instead_of_default(self): # letting default be set inst = TestModel.create() - self.assertEqual(inst.a_bool, TestModel.a_bool.column.default) + assert inst.a_bool == TestModel.a_bool.column.default queried = TestModel.objects(id=inst.id).first() - self.assertEqual(queried.a_bool, TestModel.a_bool.column.default) + assert queried.a_bool == TestModel.a_bool.column.default def test_can_insert_model_with_all_protocol_v4_column_types(self): """ @@ -265,11 +263,11 @@ class v4DatatypesModel(Model): v4DatatypesModel.create(id=0, a=date(1970, 1, 1), b=32523, c=time(16, 47, 25, 7), d=123) - self.assertEqual(1, v4DatatypesModel.objects.count()) + assert 1 == v4DatatypesModel.objects.count() output = v4DatatypesModel.objects.first() for i, i_char in enumerate(range(ord('a'), ord('a') + 3)): - self.assertEqual(input[i], output[chr(i_char)]) + assert input[i] == output[chr(i_char)] def test_can_insert_double_and_float(self): """ @@ -292,16 +290,16 @@ class FloatingPointModel(Model): FloatingPointModel.create(id=0, f=2.39) output = FloatingPointModel.objects.first() - self.assertEqual(2.390000104904175, output.f) # float loses precision + assert 2.390000104904175 == output.f # float loses precision FloatingPointModel.create(id=0, f=3.4028234663852886e+38, d=2.39) output = FloatingPointModel.objects.first() - self.assertEqual(3.4028234663852886e+38, output.f) - self.assertEqual(2.39, output.d) # double retains precision + assert 3.4028234663852886e+38 == output.f + assert 2.39 == output.d # double retains precision FloatingPointModel.create(id=0, d=3.4028234663852886e+38) output = FloatingPointModel.objects.first() - self.assertEqual(3.4028234663852886e+38, output.d) + assert 3.4028234663852886e+38 == output.d class TestMultiKeyModel(Model): @@ -503,15 +501,15 @@ class TestDefaultValueTracking(Model): int3=7777, int5=5555) - self.assertEqual(instance.id, 1) - self.assertEqual(instance.int1, 9999) - self.assertEqual(instance.int2, 456) - self.assertEqual(instance.int3, 7777) + assert instance.id == 1 + assert instance.int1 == 9999 + assert instance.int2 == 456 + assert instance.int3 == 7777 self.assertIsNotNone(instance.int4) self.assertIsInstance(instance.int4, int) self.assertGreaterEqual(instance.int4, 0) self.assertLessEqual(instance.int4, 1000) - self.assertEqual(instance.int5, 5555) + assert instance.int5 == 5555 self.assertTrue(instance.int6 is None) # All previous values are unset as the object hasn't been persisted @@ -554,20 +552,20 @@ def test_save_to_none(self): text_set=text_set, text_map=text_map) initial.save() current = TestModelSave.objects.get(partition=partition, cluster=cluster) - self.assertEqual(current.text, text) - self.assertEqual(current.text_list, text_list) - self.assertEqual(current.text_set, text_set) - self.assertEqual(current.text_map, text_map) + assert current.text == text + assert current.text_list == text_list + assert current.text_set == text_set + assert current.text_map == text_map next = TestModelSave(partition=partition, cluster=cluster, text=None, text_list=None, text_set=None, text_map=None) next.save() current = TestModelSave.objects.get(partition=partition, cluster=cluster) - self.assertEqual(current.text, None) - self.assertEqual(current.text_list, []) - self.assertEqual(current.text_set, set()) - self.assertEqual(current.text_map, {}) + assert current.text == None + assert current.text_list == [] + assert current.text_set == set() + assert current.text_map == {} def test_none_filter_fails(): @@ -697,7 +695,7 @@ def test_query_with_date(self): day = date(2013, 11, 26) obj = TestQueryModel.create(test_id=uid, date=day, description=u'foo') - self.assertEqual(obj.description, u'foo') + assert obj.description == u'foo' inst = TestQueryModel.filter( TestQueryModel.test_id == uid, @@ -780,10 +778,10 @@ def test_routing_key_is_ignored(self): t = BasicModelNoRouting.create(k=2, v=3) t.update(v=4).save() f = BasicModelNoRouting.objects.filter(k=2).first() - self.assertEqual(t, f) + assert t == f t.delete() - self.assertEqual(BasicModelNoRouting.objects.count(), 0) + assert BasicModelNoRouting.objects.count() == 0 def test_routing_key_generation_basic(self): @@ -806,7 +804,7 @@ def test_routing_key_generation_basic(self): mrk = BasicModel._routing_key_from_values([1], self.session.cluster.protocol_version) simple = SimpleStatement("") simple.routing_key = mrk - self.assertEqual(bound.routing_key, simple.routing_key) + assert bound.routing_key == simple.routing_key def test_routing_key_generation_multi(self): """ @@ -827,7 +825,7 @@ def test_routing_key_generation_multi(self): mrk = BasicModelMulti._routing_key_from_values([1, 2], self.session.cluster.protocol_version) simple = SimpleStatement("") simple.routing_key = mrk - self.assertEqual(bound.routing_key, simple.routing_key) + assert bound.routing_key == simple.routing_key def test_routing_key_generation_complex(self): """ @@ -853,7 +851,7 @@ def test_routing_key_generation_complex(self): mrk = ComplexModelRouting._routing_key_from_values([partition, cluster, text, float], self.session.cluster.protocol_version) simple = SimpleStatement("") simple.routing_key = mrk - self.assertEqual(bound.routing_key, simple.routing_key) + assert bound.routing_key == simple.routing_key def test_partition_key_index(self): """ @@ -899,7 +897,7 @@ def _check_partition_value_generation(self, model, state, reverse=False): # Those specified in the models partition field for indx, value in enumerate(state.partition_key_values(model._partition_key_index)): name = res.get(value) - self.assertEqual(indx, model._partition_key_index.get(name)) + assert indx == model._partition_key_index.get(name) def test_none_filter_fails(): diff --git a/tests/integration/cqlengine/model/test_polymorphism.py b/tests/integration/cqlengine/model/test_polymorphism.py index f27703367d..ddee177172 100644 --- a/tests/integration/cqlengine/model/test_polymorphism.py +++ b/tests/integration/cqlengine/model/test_polymorphism.py @@ -251,5 +251,5 @@ def tearDownClass(cls): management.drop_table(IndexedInherit2) def test_success_case(self): - self.assertEqual(len(list(IndexedInherit1.objects(partition=self.p1.partition))), 1) - self.assertEqual(len(list(IndexedInherit2.objects(partition=self.p1.partition))), 1) + assert len(list(IndexedInherit1.objects(partition=self.p1.partition))) == 1 + assert len(list(IndexedInherit2.objects(partition=self.p1.partition))) == 1 diff --git a/tests/integration/cqlengine/model/test_udts.py b/tests/integration/cqlengine/model/test_udts.py index 7063df8caa..f53e694f4a 100644 --- a/tests/integration/cqlengine/model/test_udts.py +++ b/tests/integration/cqlengine/model/test_udts.py @@ -75,8 +75,8 @@ class User(UserType): sync_type(DEFAULT_KEYSPACE, User) user = User(age=42, name="John") - self.assertEqual(42, user.age) - self.assertEqual("John", user.name) + assert 42 == user.age + assert "John" == user.name # Add a field class User(UserType): @@ -88,9 +88,9 @@ class User(UserType): user = User(age=42) user["name"] = "John" user["gender"] = "male" - self.assertEqual(42, user.age) - self.assertEqual("John", user.name) - self.assertEqual("male", user.gender) + assert 42 == user.age + assert "John" == user.name + assert "male" == user.gender # Remove a field class User(UserType): @@ -110,13 +110,13 @@ def test_can_insert_udts(self): user = User(age=42, name="John") UserModel.create(id=0, info=user) - self.assertEqual(1, UserModel.objects.count()) + assert 1 == UserModel.objects.count() john = UserModel.objects.first() - self.assertEqual(0, john.id) + assert 0 == john.id self.assertTrue(type(john.info) is User) - self.assertEqual(42, john.info.age) - self.assertEqual("John", john.info.name) + assert 42 == john.info.age + assert "John" == john.info.name def test_can_update_udts(self): sync_table(UserModel) @@ -126,15 +126,15 @@ def test_can_update_udts(self): created_user = UserModel.create(id=0, info=user) john_info = UserModel.objects.first().info - self.assertEqual(42, john_info.age) - self.assertEqual("John", john_info.name) + assert 42 == john_info.age + assert "John" == john_info.name created_user.info = User(age=22, name="Mary") created_user.update() mary_info = UserModel.objects.first().info - self.assertEqual(22, mary_info["age"]) - self.assertEqual("Mary", mary_info["name"]) + assert 22 == mary_info["age"] + assert "Mary" == mary_info["name"] def test_can_update_udts_with_nones(self): sync_table(UserModel) @@ -144,8 +144,8 @@ def test_can_update_udts_with_nones(self): created_user = UserModel.create(id=0, info=user) john_info = UserModel.objects.first().info - self.assertEqual(42, john_info.age) - self.assertEqual("John", john_info.name) + assert 42 == john_info.age + assert "John" == john_info.name created_user.info = None created_user.update() @@ -177,15 +177,15 @@ class UserModelGender(Model): UserModelGender.create(id=0, info=user) john_info = UserModelGender.objects.first().info - self.assertEqual(42, john_info.age) - self.assertEqual("John", john_info.name) + assert 42 == john_info.age + assert "John" == john_info.name self.assertIsNone(john_info.gender) user = UserGender(age=42) UserModelGender.create(id=0, info=user) john_info = UserModelGender.objects.first().info - self.assertEqual(42, john_info.age) + assert 42 == john_info.age self.assertIsNone(john_info.name) self.assertIsNone(john_info.gender) @@ -221,10 +221,10 @@ class DepthModel(Model): DepthModel.create(id=0, v_0=udts[0], v_1=udts[1], v_2=udts[2], v_3=udts[3]) output = DepthModel.objects.first() - self.assertEqual(udts[0], output.v_0) - self.assertEqual(udts[1], output.v_1) - self.assertEqual(udts[2], output.v_2) - self.assertEqual(udts[3], output.v_3) + assert udts[0] == output.v_0 + assert udts[1] == output.v_1 + assert udts[2] == output.v_2 + assert udts[3] == output.v_3 def test_can_insert_udts_with_nones(self): """ @@ -248,10 +248,10 @@ def test_can_insert_udts_with_nones(self): l=None, m=None, n=None) AllDatatypesModel.create(id=0, data=input) - self.assertEqual(1, AllDatatypesModel.objects.count()) + assert 1 == AllDatatypesModel.objects.count() output = AllDatatypesModel.objects.first().data - self.assertEqual(input, output) + assert input == output def test_can_insert_udts_with_all_datatypes(self): """ @@ -278,11 +278,11 @@ def test_can_insert_udts_with_all_datatypes(self): m=UUID('067e6162-3b6f-4ae2-a171-2470b63dff00'), n=int(str(2147483647) + '000')) AllDatatypesModel.create(id=0, data=input) - self.assertEqual(1, AllDatatypesModel.objects.count()) + assert 1 == AllDatatypesModel.objects.count() output = AllDatatypesModel.objects.first().data for i in range(ord('a'), ord('a') + 14): - self.assertEqual(input[chr(i)], output[chr(i)]) + assert input[chr(i)] == output[chr(i)] def test_can_insert_udts_protocol_v4_datatypes(self): """ @@ -320,11 +320,11 @@ class Allv4DatatypesModel(Model): input = Allv4Datatypes(a=Date(date(1970, 1, 1)), b=32523, c=Time(time(16, 47, 25, 7)), d=123) Allv4DatatypesModel.create(id=0, data=input) - self.assertEqual(1, Allv4DatatypesModel.objects.count()) + assert 1 == Allv4DatatypesModel.objects.count() output = Allv4DatatypesModel.objects.first().data for i in range(ord('a'), ord('a') + 3): - self.assertEqual(input[chr(i)], output[chr(i)]) + assert input[chr(i)] == output[chr(i)] def test_nested_udts_inserts(self): """ @@ -364,9 +364,9 @@ class Container(Model): Container.create(id=UUID('FE2B4360-28C6-11E2-81C1-0800200C9A66'), names=names) # Validate input and output matches - self.assertEqual(1, Container.objects.count()) + assert 1 == Container.objects.count() names_output = Container.objects.first().names - self.assertEqual(names_output, names) + assert names_output == names def test_udts_with_unicode(self): """ @@ -407,7 +407,7 @@ def test_register_default_keyspace(self): # None emulating no model and no default keyspace before connecting connection.udt_by_keyspace.clear() User.register_for_keyspace(None) - self.assertEqual(len(connection.udt_by_keyspace), 1) + assert len(connection.udt_by_keyspace) == 1 self.assertIn(None, connection.udt_by_keyspace) # register should be with default keyspace, not None @@ -443,7 +443,7 @@ class TheModel(Model): type_fields = (db_field_different.age.column, db_field_different.name.column) - self.assertEqual(len(type_meta.field_names), len(type_fields)) + assert len(type_meta.field_names) == len(type_fields) for f in type_fields: self.assertIn(f.db_field_name, type_meta.field_names) @@ -453,17 +453,17 @@ class TheModel(Model): info = db_field_different(age=age, name=name) TheModel.create(id=id, info=info) - self.assertEqual(1, TheModel.objects.count()) + assert 1 == TheModel.objects.count() john = TheModel.objects.first() - self.assertEqual(john.id, id) + assert john.id == id info = john.info self.assertIsInstance(info, db_field_different) - self.assertEqual(info.age, age) - self.assertEqual(info.name, name) + assert info.age == age + assert info.name == name # also excercise the db_Field mapping - self.assertEqual(info.a, age) - self.assertEqual(info.n, name) + assert info.a == age + assert info.n == name def test_db_field_overload(self): """ @@ -493,7 +493,7 @@ def test_set_udt_fields(self): u = User() u.age = 20 - self.assertEqual(20, u.age) + assert 20 == u.age def test_default_values(self): """ @@ -527,9 +527,9 @@ class OuterModel(Model): t.simple = NestedUdt(something="") t.save() self.assertIsNotNone(t.nested[0].test_id) - self.assertEqual(t.nested[0].default_text, "default text") + assert t.nested[0].default_text == "default text" self.assertIsNotNone(t.simple.test_id) - self.assertEqual(t.simple.default_text, "default text") + assert t.simple.default_text == "default text" def test_udt_validate(self): """ diff --git a/tests/integration/cqlengine/model/test_updates.py b/tests/integration/cqlengine/model/test_updates.py index 718c651880..836e0eef99 100644 --- a/tests/integration/cqlengine/model/test_updates.py +++ b/tests/integration/cqlengine/model/test_updates.py @@ -60,8 +60,8 @@ def test_update_model(self): # database should reflect both updates m2 = TestUpdateModel.get(partition=m0.partition, cluster=m0.cluster) - self.assertEqual(m2.count, m1.count) - self.assertEqual(m2.text, m0.text) + assert m2.count == m1.count + assert m2.text == m0.text #This shouldn't raise a Validation error as the PR is not changing m0.update(partition=m0.partition, cluster=m0.cluster) @@ -85,12 +85,12 @@ def test_update_values(self): # update the text, and call update m0.update(text='monkey land') - self.assertEqual(m0.text, 'monkey land') + assert m0.text == 'monkey land' # database should reflect both updates m2 = TestUpdateModel.get(partition=m0.partition, cluster=m0.cluster) - self.assertEqual(m2.count, m1.count) - self.assertEqual(m2.text, m0.text) + assert m2.count == m1.count + assert m2.text == m0.text def test_noop_model_direct_update(self): """ Tests that calling update on a model with no changes will do nothing. """ @@ -206,15 +206,13 @@ def test_value_override_with_default(self): initial = ModelWithDefault(id=1, mf={0: 0}, dummy=0, udt=first_udt, udt_default=first_udt) initial.save() - self.assertEqual(ModelWithDefault.get()._as_dict(), - {'id': 1, 'dummy': 0, 'mf': {0: 0}, "udt": first_udt, "udt_default": first_udt}) + assert ModelWithDefault.get()._as_dict() == {'id': 1, 'dummy': 0, 'mf': {0: 0}, "udt": first_udt, "udt_default": first_udt} second_udt = UDT(age=1, mf={3: 3}, dummy_udt=12) second = ModelWithDefault(id=1) second.update(mf={0: 1}, udt=second_udt) - self.assertEqual(ModelWithDefault.get()._as_dict(), - {'id': 1, 'dummy': 0, 'mf': {0: 1}, "udt": second_udt, "udt_default": first_udt}) + assert ModelWithDefault.get()._as_dict() == {'id': 1, 'dummy': 0, 'mf': {0: 1}, "udt": second_udt, "udt_default": first_udt} def test_value_is_written_if_is_default(self): """ @@ -231,8 +229,7 @@ def test_value_is_written_if_is_default(self): initial.udt_default = self.udt_default initial.update() - self.assertEqual(ModelWithDefault.get()._as_dict(), - {'id': 1, 'dummy': 42, 'mf': {0: 0}, "udt": None, "udt_default": self.udt_default}) + assert ModelWithDefault.get()._as_dict() == {'id': 1, 'dummy': 42, 'mf': {0: 0}, "udt": None, "udt_default": self.udt_default} def test_null_update_is_respected(self): """ @@ -253,8 +250,7 @@ def test_null_update_is_respected(self): updated_udt = UDT(age=1, mf={2:2}, dummy_udt=None) obj.update(dummy=None, udt_default=updated_udt) - self.assertEqual(ModelWithDefault.get()._as_dict(), - {'id': 1, 'dummy': None, 'mf': {0: 0}, "udt": None, "udt_default": updated_udt}) + assert ModelWithDefault.get()._as_dict() == {'id': 1, 'dummy': None, 'mf': {0: 0}, "udt": None, "udt_default": updated_udt} def test_only_set_values_is_updated(self): """ @@ -276,8 +272,7 @@ def test_only_set_values_is_updated(self): item.udt, item.udt_default = udt, udt_default item.save() - self.assertEqual(ModelWithDefault.get()._as_dict(), - {'id': 1, 'dummy': None, 'mf': {1: 2}, "udt": udt, "udt_default": udt_default}) + assert ModelWithDefault.get()._as_dict() == {'id': 1, 'dummy': None, 'mf': {1: 2}, "udt": udt, "udt_default": udt_default} def test_collections(self): """ @@ -296,8 +291,7 @@ def test_collections(self): udt, udt_default = UDT(age=1, mf={2: 1}), UDT(age=1, mf={2: 1}) item.update(mf={2:1}, udt=udt, udt_default=udt_default) - self.assertEqual(ModelWithDefault.get()._as_dict(), - {'id': 1, 'dummy': 1, 'mf': {2: 1}, "udt": udt, "udt_default": udt_default}) + assert ModelWithDefault.get()._as_dict() == {'id': 1, 'dummy': 1, 'mf': {2: 1}, "udt": udt, "udt_default": udt_default} def test_collection_with_default(self): """ @@ -314,38 +308,31 @@ def test_collection_with_default(self): udt, udt_default = UDT(age=1, mf={6: 6}), UDT(age=1, mf={6: 6}) item = ModelWithDefaultCollection.create(id=1, mf={1: 1}, dummy=1, udt=udt, udt_default=udt_default).save() - self.assertEqual(ModelWithDefaultCollection.objects.get(id=1)._as_dict(), - {'id': 1, 'dummy': 1, 'mf': {1: 1}, "udt": udt, "udt_default": udt_default}) + assert ModelWithDefaultCollection.objects.get(id=1)._as_dict() == {'id': 1, 'dummy': 1, 'mf': {1: 1}, "udt": udt, "udt_default": udt_default} udt, udt_default = UDT(age=1, mf={5: 5}), UDT(age=1, mf={5: 5}) item.update(mf={2: 2}, udt=udt, udt_default=udt_default) - self.assertEqual(ModelWithDefaultCollection.objects.get(id=1)._as_dict(), - {'id': 1, 'dummy': 1, 'mf': {2: 2}, "udt": udt, "udt_default": udt_default}) + assert ModelWithDefaultCollection.objects.get(id=1)._as_dict() == {'id': 1, 'dummy': 1, 'mf': {2: 2}, "udt": udt, "udt_default": udt_default} udt, udt_default = UDT(age=1, mf=None), UDT(age=1, mf=None) expected_udt, expected_udt_default = UDT(age=1, mf={}), UDT(age=1, mf={}) item.update(mf=None, udt=udt, udt_default=udt_default) - self.assertEqual(ModelWithDefaultCollection.objects.get(id=1)._as_dict(), - {'id': 1, 'dummy': 1, 'mf': {}, "udt": expected_udt, "udt_default": expected_udt_default}) + assert ModelWithDefaultCollection.objects.get(id=1)._as_dict() == {'id': 1, 'dummy': 1, 'mf': {}, "udt": expected_udt, "udt_default": expected_udt_default} udt_default = UDT(age=1, mf={2:2}, dummy_udt=42) item = ModelWithDefaultCollection.create(id=2, dummy=2) - self.assertEqual(ModelWithDefaultCollection.objects.get(id=2)._as_dict(), - {'id': 2, 'dummy': 2, 'mf': {2: 2}, "udt": None, "udt_default": udt_default}) + assert ModelWithDefaultCollection.objects.get(id=2)._as_dict() == {'id': 2, 'dummy': 2, 'mf': {2: 2}, "udt": None, "udt_default": udt_default} udt, udt_default = UDT(age=1, mf={1: 1, 6: 6}), UDT(age=1, mf={1: 1, 6: 6}) item.update(mf={1: 1, 4: 4}, udt=udt, udt_default=udt_default) - self.assertEqual(ModelWithDefaultCollection.objects.get(id=2)._as_dict(), - {'id': 2, 'dummy': 2, 'mf': {1: 1, 4: 4}, "udt": udt, "udt_default": udt_default}) + assert ModelWithDefaultCollection.objects.get(id=2)._as_dict() == {'id': 2, 'dummy': 2, 'mf': {1: 1, 4: 4}, "udt": udt, "udt_default": udt_default} item.update(udt_default=None) - self.assertEqual(ModelWithDefaultCollection.objects.get(id=2)._as_dict(), - {'id': 2, 'dummy': 2, 'mf': {1: 1, 4: 4}, "udt": udt, "udt_default": None}) + assert ModelWithDefaultCollection.objects.get(id=2)._as_dict() == {'id': 2, 'dummy': 2, 'mf': {1: 1, 4: 4}, "udt": udt, "udt_default": None} udt_default = UDT(age=1, mf={2:2}) item.update(udt_default=udt_default) - self.assertEqual(ModelWithDefaultCollection.objects.get(id=2)._as_dict(), - {'id': 2, 'dummy': 2, 'mf': {1: 1, 4: 4}, "udt": udt, "udt_default": udt_default}) + assert ModelWithDefaultCollection.objects.get(id=2)._as_dict() == {'id': 2, 'dummy': 2, 'mf': {1: 1, 4: 4}, "udt": udt, "udt_default": udt_default} def test_udt_to_python(self): @@ -370,5 +357,4 @@ def test_udt_to_python(self): item.update(udt=user_to_update) udt, udt_default = UDT(time_col=10), UDT(age=1, mf={2:2}) - self.assertEqual(ModelWithDefault.objects.get(id=1)._as_dict(), - {'id': 1, 'dummy': 42, 'mf': {}, "udt": udt, "udt_default": udt_default}) + assert ModelWithDefault.objects.get(id=1)._as_dict() == {'id': 1, 'dummy': 42, 'mf': {}, "udt": udt, "udt_default": udt_default} diff --git a/tests/integration/cqlengine/model/test_value_lists.py b/tests/integration/cqlengine/model/test_value_lists.py index 8fd7f4b392..cdab57ea38 100644 --- a/tests/integration/cqlengine/model/test_value_lists.py +++ b/tests/integration/cqlengine/model/test_value_lists.py @@ -57,7 +57,7 @@ def test_clustering_order(self): values = list(TestModel.objects.values_list('clustering_key', flat=True)) # [19L, 18L, 17L, 16L, 15L, 14L, 13L, 12L, 11L, 10L, 9L, 8L, 7L, 6L, 5L, 4L, 3L, 2L, 1L, 0L] - self.assertEqual(values, sorted(items, reverse=True)) + assert values == sorted(items, reverse=True) def test_clustering_order_more_complex(self): """ @@ -72,6 +72,6 @@ def test_clustering_order_more_complex(self): values = list(TestClusteringComplexModel.objects.values_list('some_value', flat=True)) - self.assertEqual([2] * 20, values) + assert [2] * 20 == values drop_table(TestClusteringComplexModel) diff --git a/tests/integration/cqlengine/operators/test_where_operators.py b/tests/integration/cqlengine/operators/test_where_operators.py index e04a377c88..4b789377ed 100644 --- a/tests/integration/cqlengine/operators/test_where_operators.py +++ b/tests/integration/cqlengine/operators/test_where_operators.py @@ -45,15 +45,15 @@ def test_symbol_lookup(self): def test_operator_rendering(self): """ tests symbols are rendered properly """ - self.assertEqual("=", str(EqualsOperator())) - self.assertEqual("!=", str(NotEqualsOperator())) - self.assertEqual("IN", str(InOperator())) - self.assertEqual(">", str(GreaterThanOperator())) - self.assertEqual(">=", str(GreaterThanOrEqualOperator())) - self.assertEqual("<", str(LessThanOperator())) - self.assertEqual("<=", str(LessThanOrEqualOperator())) - self.assertEqual("CONTAINS", str(ContainsOperator())) - self.assertEqual("LIKE", str(LikeOperator())) + assert "=" == str(EqualsOperator()) + assert "!=" == str(NotEqualsOperator()) + assert "IN" == str(InOperator()) + assert ">" == str(GreaterThanOperator()) + assert ">=" == str(GreaterThanOrEqualOperator()) + assert "<" == str(LessThanOperator()) + assert "<=" == str(LessThanOrEqualOperator()) + assert "CONTAINS" == str(ContainsOperator()) + assert "LIKE" == str(LikeOperator()) class TestIsNotNull(BaseCassEngTestCase): @@ -71,18 +71,12 @@ def test_is_not_null_to_cql(self): check_lookup(self, 'IS NOT NULL', IsNotNullOperator) # The * is not expanded because there are no referred fields - self.assertEqual( - str(TestQueryUpdateModel.filter(IsNotNull("text")).limit(2)), - 'SELECT * FROM cqlengine_test.test_query_update_model WHERE "text" IS NOT NULL LIMIT 2' - ) + assert str(TestQueryUpdateModel.filter(IsNotNull("text")).limit(2)) == 'SELECT * FROM cqlengine_test.test_query_update_model WHERE "text" IS NOT NULL LIMIT 2' # We already know partition so cqlengine doesn't query for it - self.assertEqual( - str(TestQueryUpdateModel.filter(IsNotNull("text"), partition=uuid4())), - ('SELECT "cluster", "count", "text", "text_set", ' - '"text_list", "text_map", "bin_map" FROM cqlengine_test.test_query_update_model ' - 'WHERE "text" IS NOT NULL AND "partition" = %(0)s LIMIT 10000') - ) + assert str(TestQueryUpdateModel.filter(IsNotNull("text"), partition=uuid4())) == ('SELECT "cluster", "count", "text", "text_set", ' + '"text_list", "text_map", "bin_map" FROM cqlengine_test.test_query_update_model ' + 'WHERE "text" IS NOT NULL AND "partition" = %(0)s LIMIT 10000') @greaterthanorequalcass30 def test_is_not_null_execution(self): diff --git a/tests/integration/cqlengine/query/test_batch_query.py b/tests/integration/cqlengine/query/test_batch_query.py index a69f19d811..1a1deab77b 100644 --- a/tests/integration/cqlengine/query/test_batch_query.py +++ b/tests/integration/cqlengine/query/test_batch_query.py @@ -88,12 +88,12 @@ def test_update_success_case(self): inst.batch(b).save() inst2 = TestMultiKeyModel.get(partition=self.pkey, cluster=2) - self.assertEqual(inst2.count, 3) + assert inst2.count == 3 b.execute() inst3 = TestMultiKeyModel.get(partition=self.pkey, cluster=2) - self.assertEqual(inst3.count, 4) + assert inst3.count == 4 @execute_count(4) def test_delete_success_case(self): @@ -134,9 +134,9 @@ def test_bulk_delete_success_case(self): with BatchQuery() as b: TestMultiKeyModel.objects.batch(b).filter(partition=0).delete() - self.assertEqual(TestMultiKeyModel.filter(partition=0).count(), 5) + assert TestMultiKeyModel.filter(partition=0).count() == 5 - self.assertEqual(TestMultiKeyModel.filter(partition=0).count(), 0) + assert TestMultiKeyModel.filter(partition=0).count() == 0 #cleanup for m in TestMultiKeyModel.all(): m.delete() @@ -147,7 +147,7 @@ def test_none_success_case(self): b = BatchQuery() q = TestMultiKeyModel.objects.batch(b) - self.assertEqual(q._batch, b) + assert q._batch == b q = q.batch(None) self.assertIsNone(q._batch) @@ -158,7 +158,7 @@ def test_dml_none_success_case(self): b = BatchQuery() q = DMLQuery(TestMultiKeyModel, batch=b) - self.assertEqual(q._batch, b) + assert q._batch == b q.batch(None) self.assertIsNone(q._batch) @@ -170,7 +170,7 @@ def test_batch_execute_on_exception_succeeds(self): sync_table(BatchQueryLogModel) obj = BatchQueryLogModel.objects(k=1) - self.assertEqual(0, len(obj)) + assert 0 == len(obj) try: with BatchQuery(execute_on_exception=True) as b: @@ -181,7 +181,7 @@ def test_batch_execute_on_exception_succeeds(self): obj = BatchQueryLogModel.objects(k=1) # should be 1 because the batch should execute - self.assertEqual(1, len(obj)) + assert 1 == len(obj) @execute_count(2) def test_batch_execute_on_exception_skips_if_not_specified(self): @@ -190,7 +190,7 @@ def test_batch_execute_on_exception_skips_if_not_specified(self): sync_table(BatchQueryLogModel) obj = BatchQueryLogModel.objects(k=2) - self.assertEqual(0, len(obj)) + assert 0 == len(obj) try: with BatchQuery() as b: @@ -202,21 +202,21 @@ def test_batch_execute_on_exception_skips_if_not_specified(self): obj = BatchQueryLogModel.objects(k=2) # should be 0 because the batch should not execute - self.assertEqual(0, len(obj)) + assert 0 == len(obj) @execute_count(1) def test_batch_execute_timeout(self): with mock.patch.object(Session, 'execute') as mock_execute: with BatchQuery(timeout=1) as b: BatchQueryLogModel.batch(b).create(k=2, v=2) - self.assertEqual(mock_execute.call_args[-1]['timeout'], 1) + assert mock_execute.call_args[-1]['timeout'] == 1 @execute_count(1) def test_batch_execute_no_timeout(self): with mock.patch.object(Session, 'execute') as mock_execute: with BatchQuery() as b: BatchQueryLogModel.batch(b).create(k=2, v=2) - self.assertEqual(mock_execute.call_args[-1]['timeout'], NOT_SET) + assert mock_execute.call_args[-1]['timeout'] == NOT_SET class BatchTypeQueryTests(BaseCassEngTestCase): @@ -245,7 +245,7 @@ def test_cassandra_batch_type(self): TestMultiKeyModel.batch(b).create(partition=1, cluster=2) obj = TestMultiKeyModel.objects(partition=1) - self.assertEqual(2, len(obj)) + assert 2 == len(obj) with BatchQuery(batch_type=cassandra_BatchType.COUNTER) as b: CounterBatchQueryModel.batch(b).create(k=1, v=1) @@ -253,15 +253,15 @@ def test_cassandra_batch_type(self): CounterBatchQueryModel.batch(b).create(k=1, v=10) obj = CounterBatchQueryModel.objects(k=1) - self.assertEqual(1, len(obj)) - self.assertEqual(obj[0].v, 13) + assert 1 == len(obj) + assert obj[0].v == 13 with BatchQuery(batch_type=cassandra_BatchType.LOGGED) as b: TestMultiKeyModel.batch(b).create(partition=1, cluster=1) TestMultiKeyModel.batch(b).create(partition=1, cluster=2) obj = TestMultiKeyModel.objects(partition=1) - self.assertEqual(2, len(obj)) + assert 2 == len(obj) @execute_count(4) def test_cqlengine_batch_type(self): @@ -280,7 +280,7 @@ def test_cqlengine_batch_type(self): TestMultiKeyModel.batch(b).create(partition=1, cluster=2) obj = TestMultiKeyModel.objects(partition=1) - self.assertEqual(2, len(obj)) + assert 2 == len(obj) with BatchQuery(batch_type=cqlengine_BatchType.Counter) as b: CounterBatchQueryModel.batch(b).create(k=1, v=1) @@ -288,5 +288,5 @@ def test_cqlengine_batch_type(self): CounterBatchQueryModel.batch(b).create(k=1, v=10) obj = CounterBatchQueryModel.objects(k=1) - self.assertEqual(1, len(obj)) - self.assertEqual(obj[0].v, 13) + assert 1 == len(obj) + assert obj[0].v == 13 diff --git a/tests/integration/cqlengine/query/test_named.py b/tests/integration/cqlengine/query/test_named.py index 0d5ba38200..09550e607e 100644 --- a/tests/integration/cqlengine/query/test_named.py +++ b/tests/integration/cqlengine/query/test_named.py @@ -77,46 +77,46 @@ def test_filter_method_where_clause_generation(self): Tests the where clause creation """ query1 = self.table.objects(test_id=5) - self.assertEqual(len(query1._where), 1) + assert len(query1._where) == 1 where = query1._where[0] - self.assertEqual(where.field, 'test_id') - self.assertEqual(where.value, 5) + assert where.field == 'test_id' + assert where.value == 5 query2 = query1.filter(expected_result__gte=1) - self.assertEqual(len(query2._where), 2) + assert len(query2._where) == 2 where = query2._where[0] - self.assertEqual(where.field, 'test_id') + assert where.field == 'test_id' self.assertIsInstance(where.operator, EqualsOperator) - self.assertEqual(where.value, 5) + assert where.value == 5 where = query2._where[1] - self.assertEqual(where.field, 'expected_result') + assert where.field == 'expected_result' self.assertIsInstance(where.operator, GreaterThanOrEqualOperator) - self.assertEqual(where.value, 1) + assert where.value == 1 def test_query_expression_where_clause_generation(self): """ Tests the where clause creation """ query1 = self.table.objects(self.table.column('test_id') == 5) - self.assertEqual(len(query1._where), 1) + assert len(query1._where) == 1 where = query1._where[0] - self.assertEqual(where.field, 'test_id') - self.assertEqual(where.value, 5) + assert where.field == 'test_id' + assert where.value == 5 query2 = query1.filter(self.table.column('expected_result') >= 1) - self.assertEqual(len(query2._where), 2) + assert len(query2._where) == 2 where = query2._where[0] - self.assertEqual(where.field, 'test_id') + assert where.field == 'test_id' self.assertIsInstance(where.operator, EqualsOperator) - self.assertEqual(where.value, 5) + assert where.value == 5 where = query2._where[1] - self.assertEqual(where.field, 'expected_result') + assert where.field == 'expected_result' self.assertIsInstance(where.operator, GreaterThanOrEqualOperator) - self.assertEqual(where.value, 1) + assert where.value == 1 @requires_collection_indexes class TestQuerySetCountSelectionAndIteration(BaseQuerySetUsage): @@ -363,12 +363,12 @@ def test_named_table_with_mv(self): self.assertTrue(self.check_table_size("alltimehigh", key_space, len(parameters))) filtered_mv_monthly_objects = mv_monthly.objects.filter(game='Chess', year=2015, month=6) - self.assertEqual(len(filtered_mv_monthly_objects), 1) - self.assertEqual(filtered_mv_monthly_objects[0]['score'], 3500) - self.assertEqual(filtered_mv_monthly_objects[0]['user'], 'jbellis') + assert len(filtered_mv_monthly_objects) == 1 + assert filtered_mv_monthly_objects[0]['score'] == 3500 + assert filtered_mv_monthly_objects[0]['user'] == 'jbellis' filtered_mv_alltime_objects = mv_all_time.objects.filter(game='Chess') - self.assertEqual(len(filtered_mv_alltime_objects), 2) - self.assertEqual(filtered_mv_alltime_objects[0]['score'], 3500) + assert len(filtered_mv_alltime_objects) == 2 + assert filtered_mv_alltime_objects[0]['score'] == 3500 def check_table_size(self, table_name, key_space, expected_size): table = key_space.table(table_name) diff --git a/tests/integration/cqlengine/query/test_queryoperators.py b/tests/integration/cqlengine/query/test_queryoperators.py index fbf666cf21..f829a6c74a 100644 --- a/tests/integration/cqlengine/query/test_queryoperators.py +++ b/tests/integration/cqlengine/query/test_queryoperators.py @@ -37,10 +37,10 @@ def test_maxtimeuuid_function(self): where = WhereClause('time', EqualsOperator(), functions.MaxTimeUUID(now)) where.set_context_id(5) - self.assertEqual(str(where), '"time" = MaxTimeUUID(%(5)s)') + assert str(where) == '"time" = MaxTimeUUID(%(5)s)' ctx = {} where.update_context(ctx) - self.assertEqual(ctx, {'5': columns.DateTime().to_database(now)}) + assert ctx == {'5': columns.DateTime().to_database(now)} def test_mintimeuuid_function(self): """ @@ -50,10 +50,10 @@ def test_mintimeuuid_function(self): where = WhereClause('time', EqualsOperator(), functions.MinTimeUUID(now)) where.set_context_id(5) - self.assertEqual(str(where), '"time" = MinTimeUUID(%(5)s)') + assert str(where) == '"time" = MinTimeUUID(%(5)s)' ctx = {} where.update_context(ctx) - self.assertEqual(ctx, {'5': columns.DateTime().to_database(now)}) + assert ctx == {'5': columns.DateTime().to_database(now)} class TokenTestModel(Model): @@ -93,7 +93,7 @@ def test_token_function(self): # pk__token equality r = TokenTestModel.objects(pk__token=functions.Token(last_token)) - self.assertEqual(len(r), 1) + assert len(r) == 1 r.all() # Attempt to obtain queryset for results. This has thrown an exception in the past def test_compound_pk_token_function(self): @@ -108,7 +108,7 @@ class TestModel(Model): q = TestModel.objects.filter(pk__token__gt=func) where = q._where[0] where.set_context_id(1) - self.assertEqual(str(where), 'token("p1", "p2") > token(%({0})s, %({1})s)'.format(1, 2)) + assert str(where) == 'token("p1", "p2") > token(%({0})s, %({1})s)'.format(1, 2) # Verify that a SELECT query can be successfully generated str(q._select_query()) @@ -120,7 +120,7 @@ class TestModel(Model): q = TestModel.objects.filter(pk__token__gt=func) where = q._where[0] where.set_context_id(1) - self.assertEqual(str(where), 'token("p1", "p2") > token(%({0})s, %({1})s)'.format(1, 2)) + assert str(where) == 'token("p1", "p2") > token(%({0})s, %({1})s)'.format(1, 2) str(q._select_query()) # The 'pk__token' virtual column may only be compared to a Token diff --git a/tests/integration/cqlengine/query/test_queryset.py b/tests/integration/cqlengine/query/test_queryset.py index d15390827f..d8aad8cf79 100644 --- a/tests/integration/cqlengine/query/test_queryset.py +++ b/tests/integration/cqlengine/query/test_queryset.py @@ -218,13 +218,13 @@ def test_queryset_with_distinct(self): """ query1 = TestModel.objects.distinct() - self.assertEqual(len(query1._distinct_fields), 1) + assert len(query1._distinct_fields) == 1 query2 = TestModel.objects.distinct(['test_id']) - self.assertEqual(len(query2._distinct_fields), 1) + assert len(query2._distinct_fields) == 1 query3 = TestModel.objects.distinct(['test_id', 'attempt_id']) - self.assertEqual(len(query3._distinct_fields), 2) + assert len(query3._distinct_fields) == 2 def test_defining_only_fields(self): """ @@ -238,7 +238,7 @@ def test_defining_only_fields(self): """ # simple only definition q = TestModel.objects.only(['attempt_id', 'description']) - self.assertEqual(q._select_fields(), ['attempt_id', 'description']) + assert q._select_fields() == ['attempt_id', 'description'] with self.assertRaises(query.QueryException): TestModel.objects.only(['nonexistent_field']) @@ -250,7 +250,7 @@ def test_defining_only_fields(self): # only with defer fields q = TestModel.objects.only(['attempt_id', 'description']) q = q.defer(['description']) - self.assertEqual(q._select_fields(), ['attempt_id']) + assert q._select_fields() == ['attempt_id'] # Eliminate all results confirm exception is thrown q = TestModel.objects.only(['description']) @@ -259,7 +259,7 @@ def test_defining_only_fields(self): q._select_fields() q = TestModel.objects.filter(test_id=0).only(['test_id', 'attempt_id', 'description']) - self.assertEqual(q._select_fields(), ['attempt_id', 'description']) + assert q._select_fields() == ['attempt_id', 'description'] # no fields to select with self.assertRaises(query.QueryException): @@ -284,7 +284,7 @@ def test_defining_defer_fields(self): # simple defer definition q = TestModel.objects.defer(['attempt_id', 'description']) - self.assertEqual(q._select_fields(), ['test_id', 'expected_result', 'test_result']) + assert q._select_fields() == ['test_id', 'expected_result', 'test_result'] with self.assertRaises(query.QueryException): TestModel.objects.defer(['nonexistent_field']) @@ -292,12 +292,12 @@ def test_defining_defer_fields(self): # defer more than one q = TestModel.objects.defer(['attempt_id', 'description']) q = q.defer(['expected_result']) - self.assertEqual(q._select_fields(), ['test_id', 'test_result']) + assert q._select_fields() == ['test_id', 'test_result'] # defer with only q = TestModel.objects.defer(['description', 'attempt_id']) q = q.only(['description', 'test_id']) - self.assertEqual(q._select_fields(), ['test_id']) + assert q._select_fields() == ['test_id'] # Eliminate all results confirm exception is thrown q = TestModel.objects.defer(['description', 'attempt_id']) @@ -307,11 +307,11 @@ def test_defining_defer_fields(self): # implicit defer q = TestModel.objects.filter(test_id=0) - self.assertEqual(q._select_fields(), ['attempt_id', 'description', 'expected_result', 'test_result']) + assert q._select_fields() == ['attempt_id', 'description', 'expected_result', 'test_result'] # when all fields are defered, it fallbacks select the partition keys q = TestModel.objects.defer(['test_id', 'attempt_id', 'description', 'expected_result', 'test_result']) - self.assertEqual(q._select_fields(), ['test_id']) + assert q._select_fields() == ['test_id'] class BaseQuerySetUsage(BaseCassEngTestCase): @@ -566,17 +566,17 @@ class TestQuerySetDistinct(BaseQuerySetUsage): @execute_count(1) def test_distinct_without_parameter(self): q = TestModel.objects.distinct() - self.assertEqual(len(q), 3) + assert len(q) == 3 @execute_count(1) def test_distinct_with_parameter(self): q = TestModel.objects.distinct(['test_id']) - self.assertEqual(len(q), 3) + assert len(q) == 3 @execute_count(1) def test_distinct_with_filter(self): q = TestModel.objects.distinct(['test_id']).filter(test_id__in=[1, 2]) - self.assertEqual(len(q), 2) + assert len(q) == 2 @execute_count(1) def test_distinct_with_non_partition(self): @@ -587,16 +587,16 @@ def test_distinct_with_non_partition(self): @execute_count(1) def test_zero_result(self): q = TestModel.objects.distinct(['test_id']).filter(test_id__in=[52]) - self.assertEqual(len(q), 0) + assert len(q) == 0 @greaterthancass21 @execute_count(2) def test_distinct_with_explicit_count(self): q = TestModel.objects.distinct(['test_id']) - self.assertEqual(q.count(), 3) + assert q.count() == 3 q = TestModel.objects.distinct(['test_id']).filter(test_id__in=[1, 2]) - self.assertEqual(q.count(), 2) + assert q.count() == 2 @requires_collection_indexes @@ -677,10 +677,10 @@ def test_slicing_works_properly(self): expected_order = [0, 1, 2, 3] for model, expect in zip(q[1:3], expected_order[1:3]): - self.assertEqual(model.attempt_id, expect) + assert model.attempt_id == expect for model, expect in zip(q[0:3:2], expected_order[0:3:2]): - self.assertEqual(model.attempt_id, expect) + assert model.attempt_id == expect @execute_count(1) def test_negative_slicing(self): @@ -688,19 +688,19 @@ def test_negative_slicing(self): expected_order = [0, 1, 2, 3] for model, expect in zip(q[-3:], expected_order[-3:]): - self.assertEqual(model.attempt_id, expect) + assert model.attempt_id == expect for model, expect in zip(q[:-1], expected_order[:-1]): - self.assertEqual(model.attempt_id, expect) + assert model.attempt_id == expect for model, expect in zip(q[1:-1], expected_order[1:-1]): - self.assertEqual(model.attempt_id, expect) + assert model.attempt_id == expect for model, expect in zip(q[-3:-1], expected_order[-3:-1]): - self.assertEqual(model.attempt_id, expect) + assert model.attempt_id == expect for model, expect in zip(q[-3:-1:2], expected_order[-3:-1:2]): - self.assertEqual(model.attempt_id, expect) + assert model.attempt_id == expect @requires_collection_indexes @@ -729,25 +729,25 @@ def test_indexed_field_can_be_queried(self): Tests that queries on an indexed field will work without any primary key relations specified """ q = IndexedTestModel.objects(test_result=25) - self.assertEqual(q.count(), 4) + assert q.count() == 4 q = IndexedCollectionsTestModel.objects.filter(test_list__contains=42) - self.assertEqual(q.count(), 1) + assert q.count() == 1 q = IndexedCollectionsTestModel.objects.filter(test_list__contains=13) - self.assertEqual(q.count(), 0) + assert q.count() == 0 q = IndexedCollectionsTestModel.objects.filter(test_set__contains=42) - self.assertEqual(q.count(), 1) + assert q.count() == 1 q = IndexedCollectionsTestModel.objects.filter(test_set__contains=13) - self.assertEqual(q.count(), 0) + assert q.count() == 0 q = IndexedCollectionsTestModel.objects.filter(test_map__contains=42) - self.assertEqual(q.count(), 1) + assert q.count() == 1 q = IndexedCollectionsTestModel.objects.filter(test_map__contains=13) - self.assertEqual(q.count(), 0) + assert q.count() == 0 def test_custom_indexed_field_can_be_queried(self): """ @@ -824,16 +824,16 @@ def test_range_deletion(self): TestMultiClusteringModel.objects().create(one=1, two=i, three=i) TestMultiClusteringModel.objects(one=1, two__gte=0, two__lte=3).delete() - self.assertEqual(6, len(TestMultiClusteringModel.objects.all())) + assert 6 == len(TestMultiClusteringModel.objects.all()) TestMultiClusteringModel.objects(one=1, two__gt=3, two__lt=5).delete() - self.assertEqual(5, len(TestMultiClusteringModel.objects.all())) + assert 5 == len(TestMultiClusteringModel.objects.all()) TestMultiClusteringModel.objects(one=1, two__in=[8, 9]).delete() - self.assertEqual(3, len(TestMultiClusteringModel.objects.all())) + assert 3 == len(TestMultiClusteringModel.objects.all()) TestMultiClusteringModel.objects(one__in=[1], two__gte=0).delete() - self.assertEqual(0, len(TestMultiClusteringModel.objects.all())) + assert 0 == len(TestMultiClusteringModel.objects.all()) class TimeUUIDQueryModel(Model): @@ -912,7 +912,7 @@ def test_success_case(self): # test kwarg filtering q = TimeUUIDQueryModel.filter(partition=pk, time__lte=functions.MaxTimeUUID(midpoint)) q = [d for d in q] - self.assertEqual(len(q), 2, msg="Got: %s" % q) + assert len(q) == 2, "Got: %s" % q datas = [d.data for d in q] assert '1' in datas assert '2' in datas @@ -977,9 +977,9 @@ class bool_model(Model): bool_model.create(k=0, b=True) bool_model.create(k=0, b=False) - self.assertEqual(len(bool_model.objects.all()), 2) - self.assertEqual(len(bool_model.objects.filter(k=0, b=True)), 1) - self.assertEqual(len(bool_model.objects.filter(k=0, b=False)), 1) + assert len(bool_model.objects.all()) == 2 + assert len(bool_model.objects.filter(k=0, b=True)) == 1 + assert len(bool_model.objects.filter(k=0, b=False)) == 1 @execute_count(3) def test_bool_filter(self): @@ -1001,7 +1001,7 @@ class bool_model2(Model): bool_model2.create(k=True, b=1, v='a') bool_model2.create(k=False, b=1, v='b') - self.assertEqual(len(list(bool_model2.objects(k__in=(True, False)))), 2) + assert len(list(bool_model2.objects(k__in=(True, False)))) == 2 @greaterthancass20 @@ -1012,63 +1012,63 @@ class TestContainsOperator(BaseQuerySetUsage): def test_kwarg_success_case(self): """ Tests the CONTAINS operator works with the kwarg query method """ q = IndexedCollectionsTestModel.filter(test_list__contains=1) - self.assertEqual(q.count(), 2) + assert q.count() == 2 q = IndexedCollectionsTestModel.filter(test_list__contains=13) - self.assertEqual(q.count(), 0) + assert q.count() == 0 q = IndexedCollectionsTestModel.filter(test_set__contains=3) - self.assertEqual(q.count(), 2) + assert q.count() == 2 q = IndexedCollectionsTestModel.filter(test_set__contains=13) - self.assertEqual(q.count(), 0) + assert q.count() == 0 q = IndexedCollectionsTestModel.filter(test_map__contains=42) - self.assertEqual(q.count(), 1) + assert q.count() == 1 q = IndexedCollectionsTestModel.filter(test_map__contains=13) - self.assertEqual(q.count(), 0) + assert q.count() == 0 with self.assertRaises(QueryException): q = IndexedCollectionsTestModel.filter(test_list_no_index__contains=1) - self.assertEqual(q.count(), 0) + assert q.count() == 0 with self.assertRaises(QueryException): q = IndexedCollectionsTestModel.filter(test_set_no_index__contains=1) - self.assertEqual(q.count(), 0) + assert q.count() == 0 with self.assertRaises(QueryException): q = IndexedCollectionsTestModel.filter(test_map_no_index__contains=1) - self.assertEqual(q.count(), 0) + assert q.count() == 0 @execute_count(6) def test_query_expression_success_case(self): """ Tests the CONTAINS operator works with the query expression query method """ q = IndexedCollectionsTestModel.filter(IndexedCollectionsTestModel.test_list.contains_(1)) - self.assertEqual(q.count(), 2) + assert q.count() == 2 q = IndexedCollectionsTestModel.filter(IndexedCollectionsTestModel.test_list.contains_(13)) - self.assertEqual(q.count(), 0) + assert q.count() == 0 q = IndexedCollectionsTestModel.filter(IndexedCollectionsTestModel.test_set.contains_(3)) - self.assertEqual(q.count(), 2) + assert q.count() == 2 q = IndexedCollectionsTestModel.filter(IndexedCollectionsTestModel.test_set.contains_(13)) - self.assertEqual(q.count(), 0) + assert q.count() == 0 q = IndexedCollectionsTestModel.filter(IndexedCollectionsTestModel.test_map.contains_(42)) - self.assertEqual(q.count(), 1) + assert q.count() == 1 q = IndexedCollectionsTestModel.filter(IndexedCollectionsTestModel.test_map.contains_(13)) - self.assertEqual(q.count(), 0) + assert q.count() == 0 with self.assertRaises(QueryException): q = IndexedCollectionsTestModel.filter(IndexedCollectionsTestModel.test_map_no_index.contains_(1)) - self.assertEqual(q.count(), 0) + assert q.count() == 0 with self.assertRaises(QueryException): q = IndexedCollectionsTestModel.filter(IndexedCollectionsTestModel.test_map_no_index.contains_(1)) - self.assertEqual(q.count(), 0) + assert q.count() == 0 with self.assertRaises(QueryException): q = IndexedCollectionsTestModel.filter(IndexedCollectionsTestModel.test_map_no_index.contains_(1)) - self.assertEqual(q.count(), 0) + assert q.count() == 0 @requires_collection_indexes @@ -1120,17 +1120,17 @@ class ModelQuerySetTimeoutTestCase(BaseQuerySetUsage): def test_default_timeout(self): with mock.patch.object(Session, 'execute') as mock_execute: list(TestModel.objects()) - self.assertEqual(mock_execute.call_args[-1]['timeout'], NOT_SET) + assert mock_execute.call_args[-1]['timeout'] == NOT_SET def test_float_timeout(self): with mock.patch.object(Session, 'execute') as mock_execute: list(TestModel.objects().timeout(0.5)) - self.assertEqual(mock_execute.call_args[-1]['timeout'], 0.5) + assert mock_execute.call_args[-1]['timeout'] == 0.5 def test_none_timeout(self): with mock.patch.object(Session, 'execute') as mock_execute: list(TestModel.objects().timeout(None)) - self.assertEqual(mock_execute.call_args[-1]['timeout'], None) + assert mock_execute.call_args[-1]['timeout'] == None @requires_collection_indexes @@ -1142,17 +1142,17 @@ def setUp(self): def test_default_timeout(self): with mock.patch.object(Session, 'execute') as mock_execute: self.model.save() - self.assertEqual(mock_execute.call_args[-1]['timeout'], NOT_SET) + assert mock_execute.call_args[-1]['timeout'] == NOT_SET def test_float_timeout(self): with mock.patch.object(Session, 'execute') as mock_execute: self.model.timeout(0.5).save() - self.assertEqual(mock_execute.call_args[-1]['timeout'], 0.5) + assert mock_execute.call_args[-1]['timeout'] == 0.5 def test_none_timeout(self): with mock.patch.object(Session, 'execute') as mock_execute: self.model.timeout(None).save() - self.assertEqual(mock_execute.call_args[-1]['timeout'], None) + assert mock_execute.call_args[-1]['timeout'] == None def test_timeout_then_batch(self): b = query.BatchQuery() @@ -1220,13 +1220,13 @@ def test_basic_crud(self): # create i = model.create(**values) i = model.objects(k0=i.k0, k1=i.k1).first() - self.assertEqual(i, model(**values)) + assert i == model(**values) # create values['v0'] = 101 i.update(v0=values['v0']) i = model.objects(k0=i.k0, k1=i.k1).first() - self.assertEqual(i, model(**values)) + assert i == model(**values) # delete model.objects(k0=i.k0, k1=i.k1).delete() @@ -1235,7 +1235,7 @@ def test_basic_crud(self): i = model.create(**values) i = model.objects(k0=i.k0, k1=i.k1).first() - self.assertEqual(i, model(**values)) + assert i == model(**values) i.delete() model.objects(k0=i.k0, k1=i.k1).delete() i = model.objects(k0=i.k0, k1=i.k1).first() @@ -1259,10 +1259,10 @@ def test_slice(self): values['c0'] = c i = model.create(**values) - self.assertEqual(model.objects(k0=i.k0, k1=i.k1).count(), len(clustering_values)) - self.assertEqual(model.objects(k0=i.k0, k1=i.k1, c0=i.c0).count(), 1) - self.assertEqual(model.objects(k0=i.k0, k1=i.k1, c0__lt=i.c0).count(), len(clustering_values[:-1])) - self.assertEqual(model.objects(k0=i.k0, k1=i.k1, c0__gt=0).count(), len(clustering_values[1:])) + assert model.objects(k0=i.k0, k1=i.k1).count() == len(clustering_values) + assert model.objects(k0=i.k0, k1=i.k1, c0=i.c0).count() == 1 + assert model.objects(k0=i.k0, k1=i.k1, c0__lt=i.c0).count() == len(clustering_values[:-1]) + assert model.objects(k0=i.k0, k1=i.k1, c0__gt=0).count() == len(clustering_values[1:]) @execute_count(15) def test_order(self): @@ -1281,8 +1281,8 @@ def test_order(self): for c in clustering_values: values['c0'] = c i = model.create(**values) - self.assertEqual(model.objects(k0=i.k0, k1=i.k1).order_by('c0').first().c0, clustering_values[0]) - self.assertEqual(model.objects(k0=i.k0, k1=i.k1).order_by('-c0').first().c0, clustering_values[-1]) + assert model.objects(k0=i.k0, k1=i.k1).order_by('c0').first().c0 == clustering_values[0] + assert model.objects(k0=i.k0, k1=i.k1).order_by('-c0').first().c0 == clustering_values[-1] @execute_count(15) def test_index(self): @@ -1302,8 +1302,8 @@ def test_index(self): values['c0'] = c values['v1'] = c i = model.create(**values) - self.assertEqual(model.objects(k0=i.k0, k1=i.k1).count(), len(clustering_values)) - self.assertEqual(model.objects(k0=i.k0, k1=i.k1, v1=0).count(), 1) + assert model.objects(k0=i.k0, k1=i.k1).count() == len(clustering_values) + assert model.objects(k0=i.k0, k1=i.k1, v1=0).count() == 1 @execute_count(1) def test_db_field_names_used(self): @@ -1340,10 +1340,8 @@ def test_db_field_names_used(self): def test_db_field_value_list(self): DBFieldModel.create(k0=0, k1=0, c0=0, v0=4, v1=5) - self.assertEqual(DBFieldModel.objects.filter(c0=0, k0=0, k1=0).values_list('c0', 'v0')._defer_fields, - {'a', 'c', 'b'}) - self.assertEqual(DBFieldModel.objects.filter(c0=0, k0=0, k1=0).values_list('c0', 'v0')._only_fields, - ['c', 'd']) + assert DBFieldModel.objects.filter(c0=0, k0=0, k1=0).values_list('c0', 'v0')._defer_fields == {'a', 'c', 'b'} + assert DBFieldModel.objects.filter(c0=0, k0=0, k1=0).values_list('c0', 'v0')._only_fields == ['c', 'd'] list(DBFieldModel.objects.filter(c0=0, k0=0, k1=0).values_list('c0', 'v0')) @@ -1390,14 +1388,14 @@ def test_defaultFetchSize(self): for i in range(5000, 5100): TestModelSmall.batch(b).create(test_id=i) - self.assertEqual(len(TestModelSmall.objects.fetch_size(1)), 5100) - self.assertEqual(len(TestModelSmall.objects.fetch_size(500)), 5100) - self.assertEqual(len(TestModelSmall.objects.fetch_size(4999)), 5100) - self.assertEqual(len(TestModelSmall.objects.fetch_size(5000)), 5100) - self.assertEqual(len(TestModelSmall.objects.fetch_size(5001)), 5100) - self.assertEqual(len(TestModelSmall.objects.fetch_size(5100)), 5100) - self.assertEqual(len(TestModelSmall.objects.fetch_size(5101)), 5100) - self.assertEqual(len(TestModelSmall.objects.fetch_size(1)), 5100) + assert len(TestModelSmall.objects.fetch_size(1)) == 5100 + assert len(TestModelSmall.objects.fetch_size(500)) == 5100 + assert len(TestModelSmall.objects.fetch_size(4999)) == 5100 + assert len(TestModelSmall.objects.fetch_size(5000)) == 5100 + assert len(TestModelSmall.objects.fetch_size(5001)) == 5100 + assert len(TestModelSmall.objects.fetch_size(5100)) == 5100 + assert len(TestModelSmall.objects.fetch_size(5101)) == 5100 + assert len(TestModelSmall.objects.fetch_size(1)) == 5100 with self.assertRaises(QueryException): TestModelSmall.objects.fetch_size(0) @@ -1453,10 +1451,10 @@ def test_defaultFetchSize(self): # Check query constructions expected_fields = ['first_name', 'birthday'] - self.assertEqual(People.filter(last_name="Smith")._select_fields(), expected_fields) + assert People.filter(last_name="Smith")._select_fields() == expected_fields # Validate correct fields are fetched smiths = list(People.filter(last_name="Smith")) - self.assertEqual(len(smiths), 3) + assert len(smiths) == 3 self.assertTrue(smiths[0].last_name is not None) # Modify table with new value @@ -1468,9 +1466,9 @@ def test_defaultFetchSize(self): # validate query construction expected_fields = ['first_name', 'middle_name', 'birthday'] - self.assertEqual(People2.filter(last_name="Smith")._select_fields(), expected_fields) + assert People2.filter(last_name="Smith")._select_fields() == expected_fields # validate correct items are returneds smiths = list(People2.filter(last_name="Smith")) - self.assertEqual(len(smiths), 5) + assert len(smiths) == 5 self.assertTrue(smiths[0].last_name is not None) diff --git a/tests/integration/cqlengine/query/test_updates.py b/tests/integration/cqlengine/query/test_updates.py index f92e4fc53f..1189ed0f5f 100644 --- a/tests/integration/cqlengine/query/test_updates.py +++ b/tests/integration/cqlengine/query/test_updates.py @@ -46,17 +46,17 @@ def test_update_values(self): # sanity check for i, row in enumerate(TestQueryUpdateModel.objects(partition=partition)): - self.assertEqual(row.cluster, i) - self.assertEqual(row.count, i) - self.assertEqual(row.text, str(i)) + assert row.cluster == i + assert row.count == i + assert row.text == str(i) # perform update TestQueryUpdateModel.objects(partition=partition, cluster=3).update(count=6) for i, row in enumerate(TestQueryUpdateModel.objects(partition=partition)): - self.assertEqual(row.cluster, i) - self.assertEqual(row.count, 6 if i == 3 else i) - self.assertEqual(row.text, str(i)) + assert row.cluster == i + assert row.count == (6 if i == 3 else i) + assert row.text == str(i) @execute_count(6) def test_update_values_validation(self): @@ -67,9 +67,9 @@ def test_update_values_validation(self): # sanity check for i, row in enumerate(TestQueryUpdateModel.objects(partition=partition)): - self.assertEqual(row.cluster, i) - self.assertEqual(row.count, i) - self.assertEqual(row.text, str(i)) + assert row.cluster == i + assert row.count == i + assert row.text == str(i) # perform update with self.assertRaises(ValidationError): @@ -94,17 +94,17 @@ def test_null_update_deletes_column(self): # sanity check for i, row in enumerate(TestQueryUpdateModel.objects(partition=partition)): - self.assertEqual(row.cluster, i) - self.assertEqual(row.count, i) - self.assertEqual(row.text, str(i)) + assert row.cluster == i + assert row.count == i + assert row.text == str(i) # perform update TestQueryUpdateModel.objects(partition=partition, cluster=3).update(text=None) for i, row in enumerate(TestQueryUpdateModel.objects(partition=partition)): - self.assertEqual(row.cluster, i) - self.assertEqual(row.count, i) - self.assertEqual(row.text, None if i == 3 else str(i)) + assert row.cluster == i + assert row.count == i + assert row.text == None if i == 3 else str(i) @execute_count(9) def test_mixed_value_and_null_update(self): @@ -115,17 +115,17 @@ def test_mixed_value_and_null_update(self): # sanity check for i, row in enumerate(TestQueryUpdateModel.objects(partition=partition)): - self.assertEqual(row.cluster, i) - self.assertEqual(row.count, i) - self.assertEqual(row.text, str(i)) + assert row.cluster == i + assert row.count == i + assert row.text == str(i) # perform update TestQueryUpdateModel.objects(partition=partition, cluster=3).update(count=6, text=None) for i, row in enumerate(TestQueryUpdateModel.objects(partition=partition)): - self.assertEqual(row.cluster, i) - self.assertEqual(row.count, 6 if i == 3 else i) - self.assertEqual(row.text, None if i == 3 else str(i)) + assert row.cluster == i + assert row.count == (6 if i == 3 else i) + assert row.text == (None if i == 3 else str(i)) @execute_count(3) def test_set_add_updates(self): @@ -136,7 +136,7 @@ def test_set_add_updates(self): TestQueryUpdateModel.objects( partition=partition, cluster=cluster).update(text_set__add=set(('bar',))) obj = TestQueryUpdateModel.objects.get(partition=partition, cluster=cluster) - self.assertEqual(obj.text_set, set(("foo", "bar"))) + assert obj.text_set == set(("foo", "bar")) @execute_count(2) def test_set_add_updates_new_record(self): @@ -147,7 +147,7 @@ def test_set_add_updates_new_record(self): TestQueryUpdateModel.objects( partition=partition, cluster=cluster).update(text_set__add=set(('bar',))) obj = TestQueryUpdateModel.objects.get(partition=partition, cluster=cluster) - self.assertEqual(obj.text_set, set(("bar",))) + assert obj.text_set == set(("bar",)) @execute_count(3) def test_set_remove_updates(self): @@ -159,7 +159,7 @@ def test_set_remove_updates(self): partition=partition, cluster=cluster).update( text_set__remove=set(('foo',))) obj = TestQueryUpdateModel.objects.get(partition=partition, cluster=cluster) - self.assertEqual(obj.text_set, set(("baz",))) + assert obj.text_set == set(("baz",)) @execute_count(3) def test_set_remove_new_record(self): @@ -173,7 +173,7 @@ def test_set_remove_new_record(self): partition=partition, cluster=cluster).update( text_set__remove=set(('afsd',))) obj = TestQueryUpdateModel.objects.get(partition=partition, cluster=cluster) - self.assertEqual(obj.text_set, set(("foo",))) + assert obj.text_set == set(("foo",)) @execute_count(3) def test_list_append_updates(self): @@ -185,7 +185,7 @@ def test_list_append_updates(self): partition=partition, cluster=cluster).update( text_list__append=['bar']) obj = TestQueryUpdateModel.objects.get(partition=partition, cluster=cluster) - self.assertEqual(obj.text_list, ["foo", "bar"]) + assert obj.text_list == ["foo", "bar"] @execute_count(3) def test_list_prepend_updates(self): @@ -201,7 +201,7 @@ def test_list_prepend_updates(self): text_list__prepend=prepended) obj = TestQueryUpdateModel.objects.get(partition=partition, cluster=cluster) expected = (prepended[::-1] if is_prepend_reversed() else prepended) + original - self.assertEqual(obj.text_list, expected) + assert obj.text_list == expected @execute_count(3) def test_map_update_updates(self): @@ -215,7 +215,7 @@ def test_map_update_updates(self): partition=partition, cluster=cluster).update( text_map__update={"bar": '3', "baz": '4'}) obj = TestQueryUpdateModel.objects.get(partition=partition, cluster=cluster) - self.assertEqual(obj.text_map, {"foo": '1', "bar": '3', "baz": '4'}) + assert obj.text_map == {"foo": '1', "bar": '3', "baz": '4'} @execute_count(3) def test_map_update_none_deletes_key(self): @@ -231,7 +231,7 @@ def test_map_update_none_deletes_key(self): partition=partition, cluster=cluster).update( text_map__update={"bar": None}) obj = TestQueryUpdateModel.objects.get(partition=partition, cluster=cluster) - self.assertEqual(obj.text_map, {"foo": '1'}) + assert obj.text_map == {"foo": '1'} @greaterthancass20 @execute_count(5) @@ -256,22 +256,16 @@ def test_map_update_remove(self): bin_map__update={456: b'4', 123: b'2'} ) obj = TestQueryUpdateModel.objects.get(partition=partition, cluster=cluster) - self.assertEqual(obj.text_map, {"foo": '2', "foz": '4'}) - self.assertEqual(obj.bin_map, {123: b'2', 456: b'4'}) + assert obj.text_map == {"foo": '2', "foz": '4'} + assert obj.bin_map == {123: b'2', 456: b'4'} TestQueryUpdateModel.objects(partition=partition, cluster=cluster).update( text_map__remove={"foo", "foz"}, bin_map__remove={123, 456} ) rec = TestQueryUpdateModel.objects.get(partition=partition, cluster=cluster) - self.assertEqual( - rec.text_map, - {} - ) - self.assertEqual( - rec.bin_map, - {} - ) + assert rec.text_map == {} + assert rec.bin_map == {} def test_map_remove_rejects_non_sets(self): """ @@ -352,6 +346,6 @@ def test_static_deletion(self): """ StaticDeleteModel.create(example_id=5, example_clust=5, example_static2=1) sdm = StaticDeleteModel.filter(example_id=5).first() - self.assertEqual(1, sdm.example_static2) + assert 1 == sdm.example_static2 sdm.update(example_static2=None) self.assertIsNone(sdm.example_static2) diff --git a/tests/integration/cqlengine/statements/test_assignment_clauses.py b/tests/integration/cqlengine/statements/test_assignment_clauses.py index 82bf067cb4..5a7c89aacd 100644 --- a/tests/integration/cqlengine/statements/test_assignment_clauses.py +++ b/tests/integration/cqlengine/statements/test_assignment_clauses.py @@ -24,7 +24,7 @@ def test_rendering(self): def test_insert_tuple(self): ac = AssignmentClause('a', 'b') ac.set_context_id(10) - self.assertEqual(ac.insert_tuple(), ('a', 10)) + assert ac.insert_tuple() == ('a', 10) class SetUpdateClauseTests(unittest.TestCase): @@ -34,16 +34,16 @@ def test_update_from_none(self): c._analyze() c.set_context_id(0) - self.assertEqual(c._assignments, set((1, 2))) + assert c._assignments == set((1, 2)) self.assertIsNone(c._additions) self.assertIsNone(c._removals) - self.assertEqual(c.get_context_size(), 1) - self.assertEqual(str(c), '"s" = %(0)s') + assert c.get_context_size() == 1 + assert str(c) == '"s" = %(0)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': set((1, 2))}) + assert ctx == {'0': set((1, 2))} def test_null_update(self): """ tests setting a set to None creates an empty update statement """ @@ -55,12 +55,12 @@ def test_null_update(self): self.assertIsNone(c._additions) self.assertIsNone(c._removals) - self.assertEqual(c.get_context_size(), 0) - self.assertEqual(str(c), '') + assert c.get_context_size() == 0 + assert str(c) == '' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {}) + assert ctx == {} def test_no_update(self): """ tests an unchanged value creates an empty update statement """ @@ -72,12 +72,12 @@ def test_no_update(self): self.assertIsNone(c._additions) self.assertIsNone(c._removals) - self.assertEqual(c.get_context_size(), 0) - self.assertEqual(str(c), '') + assert c.get_context_size() == 0 + assert str(c) == '' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {}) + assert ctx == {} def test_update_empty_set(self): """tests assigning a set to an empty set creates a nonempty @@ -86,16 +86,16 @@ def test_update_empty_set(self): c._analyze() c.set_context_id(0) - self.assertEqual(c._assignments, set()) + assert c._assignments == set() self.assertIsNone(c._additions) self.assertIsNone(c._removals) - self.assertEqual(c.get_context_size(), 1) - self.assertEqual(str(c), '"s" = %(0)s') + assert c.get_context_size() == 1 + assert str(c) == '"s" = %(0)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': set()}) + assert ctx == {'0': set()} def test_additions(self): c = SetUpdateClause('s', set((1, 2, 3)), previous=set((1, 2))) @@ -103,15 +103,15 @@ def test_additions(self): c.set_context_id(0) self.assertIsNone(c._assignments) - self.assertEqual(c._additions, set((3,))) + assert c._additions == set((3,)) self.assertIsNone(c._removals) - self.assertEqual(c.get_context_size(), 1) - self.assertEqual(str(c), '"s" = "s" + %(0)s') + assert c.get_context_size() == 1 + assert str(c) == '"s" = "s" + %(0)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': set((3,))}) + assert ctx == {'0': set((3,))} def test_removals(self): c = SetUpdateClause('s', set((1, 2)), previous=set((1, 2, 3))) @@ -120,14 +120,14 @@ def test_removals(self): self.assertIsNone(c._assignments) self.assertIsNone(c._additions) - self.assertEqual(c._removals, set((3,))) + assert c._removals == set((3,)) - self.assertEqual(c.get_context_size(), 1) - self.assertEqual(str(c), '"s" = "s" - %(0)s') + assert c.get_context_size() == 1 + assert str(c) == '"s" = "s" - %(0)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': set((3,))}) + assert ctx == {'0': set((3,))} def test_additions_and_removals(self): c = SetUpdateClause('s', set((2, 3)), previous=set((1, 2))) @@ -135,15 +135,15 @@ def test_additions_and_removals(self): c.set_context_id(0) self.assertIsNone(c._assignments) - self.assertEqual(c._additions, set((3,))) - self.assertEqual(c._removals, set((1,))) + assert c._additions == set((3,)) + assert c._removals == set((1,)) - self.assertEqual(c.get_context_size(), 2) - self.assertEqual(str(c), '"s" = "s" + %(0)s, "s" = "s" - %(1)s') + assert c.get_context_size() == 2 + assert str(c) == '"s" = "s" + %(0)s, "s" = "s" - %(1)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': set((3,)), '1': set((1,))}) + assert ctx == {'0': set((3,)), '1': set((1,))} class ListUpdateClauseTests(unittest.TestCase): @@ -153,48 +153,48 @@ def test_update_from_none(self): c._analyze() c.set_context_id(0) - self.assertEqual(c._assignments, [1, 2, 3]) + assert c._assignments == [1, 2, 3] self.assertIsNone(c._append) self.assertIsNone(c._prepend) - self.assertEqual(c.get_context_size(), 1) - self.assertEqual(str(c), '"s" = %(0)s') + assert c.get_context_size() == 1 + assert str(c) == '"s" = %(0)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': [1, 2, 3]}) + assert ctx == {'0': [1, 2, 3]} def test_update_from_empty(self): c = ListUpdateClause('s', [1, 2, 3], previous=[]) c._analyze() c.set_context_id(0) - self.assertEqual(c._assignments, [1, 2, 3]) + assert c._assignments == [1, 2, 3] self.assertIsNone(c._append) self.assertIsNone(c._prepend) - self.assertEqual(c.get_context_size(), 1) - self.assertEqual(str(c), '"s" = %(0)s') + assert c.get_context_size() == 1 + assert str(c) == '"s" = %(0)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': [1, 2, 3]}) + assert ctx == {'0': [1, 2, 3]} def test_update_from_different_list(self): c = ListUpdateClause('s', [1, 2, 3], previous=[3, 2, 1]) c._analyze() c.set_context_id(0) - self.assertEqual(c._assignments, [1, 2, 3]) + assert c._assignments == [1, 2, 3] self.assertIsNone(c._append) self.assertIsNone(c._prepend) - self.assertEqual(c.get_context_size(), 1) - self.assertEqual(str(c), '"s" = %(0)s') + assert c.get_context_size() == 1 + assert str(c) == '"s" = %(0)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': [1, 2, 3]}) + assert ctx == {'0': [1, 2, 3]} def test_append(self): c = ListUpdateClause('s', [1, 2, 3, 4], previous=[1, 2]) @@ -202,15 +202,15 @@ def test_append(self): c.set_context_id(0) self.assertIsNone(c._assignments) - self.assertEqual(c._append, [3, 4]) + assert c._append == [3, 4] self.assertIsNone(c._prepend) - self.assertEqual(c.get_context_size(), 1) - self.assertEqual(str(c), '"s" = "s" + %(0)s') + assert c.get_context_size() == 1 + assert str(c) == '"s" = "s" + %(0)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': [3, 4]}) + assert ctx == {'0': [3, 4]} def test_prepend(self): c = ListUpdateClause('s', [1, 2, 3, 4], previous=[3, 4]) @@ -219,14 +219,14 @@ def test_prepend(self): self.assertIsNone(c._assignments) self.assertIsNone(c._append) - self.assertEqual(c._prepend, [1, 2]) + assert c._prepend == [1, 2] - self.assertEqual(c.get_context_size(), 1) - self.assertEqual(str(c), '"s" = %(0)s + "s"') + assert c.get_context_size() == 1 + assert str(c) == '"s" = %(0)s + "s"' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': [1, 2]}) + assert ctx == {'0': [1, 2]} def test_append_and_prepend(self): c = ListUpdateClause('s', [1, 2, 3, 4, 5, 6], previous=[3, 4]) @@ -234,15 +234,15 @@ def test_append_and_prepend(self): c.set_context_id(0) self.assertIsNone(c._assignments) - self.assertEqual(c._append, [5, 6]) - self.assertEqual(c._prepend, [1, 2]) + assert c._append == [5, 6] + assert c._prepend == [1, 2] - self.assertEqual(c.get_context_size(), 2) - self.assertEqual(str(c), '"s" = %(0)s + "s", "s" = "s" + %(1)s') + assert c.get_context_size() == 2 + assert str(c) == '"s" = %(0)s + "s", "s" = "s" + %(1)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': [1, 2], '1': [5, 6]}) + assert ctx == {'0': [1, 2], '1': [5, 6]} def test_shrinking_list_update(self): """ tests that updating to a smaller list results in an insert statement """ @@ -250,16 +250,16 @@ def test_shrinking_list_update(self): c._analyze() c.set_context_id(0) - self.assertEqual(c._assignments, [1, 2, 3]) + assert c._assignments == [1, 2, 3] self.assertIsNone(c._append) self.assertIsNone(c._prepend) - self.assertEqual(c.get_context_size(), 1) - self.assertEqual(str(c), '"s" = %(0)s') + assert c.get_context_size() == 1 + assert str(c) == '"s" = %(0)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': [1, 2, 3]}) + assert ctx == {'0': [1, 2, 3]} class MapUpdateTests(unittest.TestCase): @@ -269,26 +269,26 @@ def test_update(self): c._analyze() c.set_context_id(0) - self.assertEqual(c._updates, [3, 5]) - self.assertEqual(c.get_context_size(), 4) - self.assertEqual(str(c), '"s"[%(0)s] = %(1)s, "s"[%(2)s] = %(3)s') + assert c._updates == [3, 5] + assert c.get_context_size() == 4 + assert str(c) == '"s"[%(0)s] = %(1)s, "s"[%(2)s] = %(3)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': 3, "1": 0, '2': 5, '3': 6}) + assert ctx == {'0': 3, "1": 0, '2': 5, '3': 6} def test_update_from_null(self): c = MapUpdateClause('s', {3: 0, 5: 6}) c._analyze() c.set_context_id(0) - self.assertEqual(c._updates, [3, 5]) - self.assertEqual(c.get_context_size(), 4) - self.assertEqual(str(c), '"s"[%(0)s] = %(1)s, "s"[%(2)s] = %(3)s') + assert c._updates == [3, 5] + assert c.get_context_size() == 4 + assert str(c) == '"s"[%(0)s] = %(1)s, "s"[%(2)s] = %(3)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': 3, "1": 0, '2': 5, '3': 6}) + assert ctx == {'0': 3, "1": 0, '2': 5, '3': 6} def test_nulled_columns_arent_included(self): c = MapUpdateClause('s', {3: 0}, {1: 2, 3: 4}) @@ -304,34 +304,34 @@ def test_positive_update(self): c = CounterUpdateClause('a', 5, 3) c.set_context_id(5) - self.assertEqual(c.get_context_size(), 1) - self.assertEqual(str(c), '"a" = "a" + %(5)s') + assert c.get_context_size() == 1 + assert str(c) == '"a" = "a" + %(5)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'5': 2}) + assert ctx == {'5': 2} def test_negative_update(self): c = CounterUpdateClause('a', 4, 7) c.set_context_id(3) - self.assertEqual(c.get_context_size(), 1) - self.assertEqual(str(c), '"a" = "a" - %(3)s') + assert c.get_context_size() == 1 + assert str(c) == '"a" = "a" - %(3)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'3': 3}) + assert ctx == {'3': 3} def noop_update(self): c = CounterUpdateClause('a', 5, 5) c.set_context_id(5) - self.assertEqual(c.get_context_size(), 1) - self.assertEqual(str(c), '"a" = "a" + %(0)s') + assert c.get_context_size() == 1 + assert str(c) == '"a" = "a" + %(0)s' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'5': 0}) + assert ctx == {'5': 0} class MapDeleteTests(unittest.TestCase): @@ -341,13 +341,13 @@ def test_update(self): c._analyze() c.set_context_id(0) - self.assertEqual(c._removals, [1, 5]) - self.assertEqual(c.get_context_size(), 2) - self.assertEqual(str(c), '"s"[%(0)s], "s"[%(1)s]') + assert c._removals == [1, 5] + assert c.get_context_size() == 2 + assert str(c) == '"s"[%(0)s], "s"[%(1)s]' ctx = {} c.update_context(ctx) - self.assertEqual(ctx, {'0': 1, '1': 5}) + assert ctx == {'0': 1, '1': 5} class FieldDeleteTests(unittest.TestCase): diff --git a/tests/integration/cqlengine/statements/test_base_statement.py b/tests/integration/cqlengine/statements/test_base_statement.py index bbcdc700e1..a2af184235 100644 --- a/tests/integration/cqlengine/statements/test_base_statement.py +++ b/tests/integration/cqlengine/statements/test_base_statement.py @@ -35,13 +35,13 @@ class BaseStatementTest(unittest.TestCase): def test_fetch_size(self): """ tests that fetch_size is correctly set """ stmt = BaseCQLStatement('table', None, fetch_size=1000) - self.assertEqual(stmt.fetch_size, 1000) + assert stmt.fetch_size == 1000 stmt = BaseCQLStatement('table', None, fetch_size=None) - self.assertEqual(stmt.fetch_size, FETCH_SIZE_UNSET) + assert stmt.fetch_size == FETCH_SIZE_UNSET stmt = BaseCQLStatement('table', None) - self.assertEqual(stmt.fetch_size, FETCH_SIZE_UNSET) + assert stmt.fetch_size == FETCH_SIZE_UNSET class ExecuteStatementTest(BaseCassEngTestCase): @@ -64,8 +64,8 @@ def _verify_statement(self, original): response = result.one() for assignment in original.assignments: - self.assertEqual(response[assignment.field], assignment.value) - self.assertEqual(len(response), 8) + assert response[assignment.field] == assignment.value + assert len(response) == 8 def test_insert_statement_execute(self): """ @@ -99,7 +99,7 @@ def test_insert_statement_execute(self): # Verifying delete statement execute(DeleteStatement(self.table_name, where=where)) - self.assertEqual(TestQueryUpdateModel.objects.count(), 0) + assert TestQueryUpdateModel.objects.count() == 0 @greaterthanorequalcass3_10 @requires_custom_indexes @@ -128,17 +128,16 @@ def test_like_operator(self): ss = SelectStatement(self.table_name) like_clause = "text_for_%" ss.add_where(Column(db_field='text'), LikeOperator(), like_clause) - self.assertEqual(str(ss), - 'SELECT * FROM {} WHERE "text" LIKE %(0)s'.format(self.table_name)) + assert str(ss) == 'SELECT * FROM {} WHERE "text" LIKE %(0)s'.format(self.table_name) result = execute(ss) - self.assertEqual(result[0]["text"], self.text) + assert result[0]["text"] == self.text q = TestQueryUpdateModel.objects.filter(text__like=like_clause).allow_filtering() - self.assertEqual(q[0].text, self.text) + assert q[0].text == self.text q = TestQueryUpdateModel.objects.filter(text__like=like_clause) - self.assertEqual(q[0].text, self.text) + assert q[0].text == self.text def _insert_statement(self, partition, cluster): # Verifying insert statement diff --git a/tests/integration/cqlengine/statements/test_delete_statement.py b/tests/integration/cqlengine/statements/test_delete_statement.py index 745881f42f..64852f1021 100644 --- a/tests/integration/cqlengine/statements/test_delete_statement.py +++ b/tests/integration/cqlengine/statements/test_delete_statement.py @@ -24,8 +24,8 @@ class DeleteStatementTests(TestCase): def test_single_field_is_listified(self): """ tests that passing a string field into the constructor puts it into a list """ ds = DeleteStatement('table', 'field') - self.assertEqual(len(ds.fields), 1) - self.assertEqual(ds.fields[0].field, 'field') + assert len(ds.fields) == 1 + assert ds.fields[0].field == 'field' def test_field_rendering(self): """ tests that fields are properly added to the select statement """ @@ -47,7 +47,7 @@ def test_table_rendering(self): def test_where_clause_rendering(self): ds = DeleteStatement('table', None) ds.add_where(Column(db_field='a'), EqualsOperator(), 'b') - self.assertEqual(str(ds), 'DELETE FROM table WHERE "a" = %(0)s', str(ds)) + assert str(ds) == 'DELETE FROM table WHERE "a" = %(0)s', str(ds) def test_context_update(self): ds = DeleteStatement('table', None) @@ -55,36 +55,36 @@ def test_context_update(self): ds.add_where(Column(db_field='a'), EqualsOperator(), 'b') ds.update_context_id(7) - self.assertEqual(str(ds), 'DELETE "d"[%(8)s] FROM table WHERE "a" = %(7)s') - self.assertEqual(ds.get_context(), {'7': 'b', '8': 3}) + assert str(ds) == 'DELETE "d"[%(8)s] FROM table WHERE "a" = %(7)s' + assert ds.get_context() == {'7': 'b', '8': 3} def test_context(self): ds = DeleteStatement('table', None) ds.add_where(Column(db_field='a'), EqualsOperator(), 'b') - self.assertEqual(ds.get_context(), {'0': 'b'}) + assert ds.get_context() == {'0': 'b'} def test_range_deletion_rendering(self): ds = DeleteStatement('table', None) ds.add_where(Column(db_field='a'), EqualsOperator(), 'b') ds.add_where(Column(db_field='created_at'), GreaterThanOrEqualOperator(), '0') ds.add_where(Column(db_field='created_at'), LessThanOrEqualOperator(), '10') - self.assertEqual(str(ds), 'DELETE FROM table WHERE "a" = %(0)s AND "created_at" >= %(1)s AND "created_at" <= %(2)s', str(ds)) + assert str(ds) == 'DELETE FROM table WHERE "a" = %(0)s AND "created_at" >= %(1)s AND "created_at" <= %(2)s', str(ds) ds = DeleteStatement('table', None) ds.add_where(Column(db_field='a'), EqualsOperator(), 'b') ds.add_where(Column(db_field='created_at'), InOperator(), ['0', '10', '20']) - self.assertEqual(str(ds), 'DELETE FROM table WHERE "a" = %(0)s AND "created_at" IN %(1)s', str(ds)) + assert str(ds) == 'DELETE FROM table WHERE "a" = %(0)s AND "created_at" IN %(1)s', str(ds) ds = DeleteStatement('table', None) ds.add_where(Column(db_field='a'), NotEqualsOperator(), 'b') - self.assertEqual(str(ds), 'DELETE FROM table WHERE "a" != %(0)s', str(ds)) + assert str(ds) == 'DELETE FROM table WHERE "a" != %(0)s', str(ds) def test_delete_conditional(self): where = [WhereClause('id', EqualsOperator(), 1)] conditionals = [ConditionalClause('f0', 'value0'), ConditionalClause('f1', 'value1')] ds = DeleteStatement('table', where=where, conditionals=conditionals) - self.assertEqual(len(ds.conditionals), len(conditionals)) - self.assertEqual(str(ds), 'DELETE FROM table WHERE "id" = %(0)s IF "f0" = %(1)s AND "f1" = %(2)s', str(ds)) + assert len(ds.conditionals) == len(conditionals) + assert str(ds) == 'DELETE FROM table WHERE "id" = %(0)s IF "f0" = %(1)s AND "f1" = %(2)s', str(ds) fields = ['one', 'two'] ds = DeleteStatement('table', fields=fields, where=where, conditionals=conditionals) - self.assertEqual(str(ds), 'DELETE "one", "two" FROM table WHERE "id" = %(0)s IF "f0" = %(1)s AND "f1" = %(2)s', str(ds)) + assert str(ds) == 'DELETE "one", "two" FROM table WHERE "id" = %(0)s IF "f0" = %(1)s AND "f1" = %(2)s', str(ds) diff --git a/tests/integration/cqlengine/statements/test_insert_statement.py b/tests/integration/cqlengine/statements/test_insert_statement.py index 45485af912..149cfa93a2 100644 --- a/tests/integration/cqlengine/statements/test_insert_statement.py +++ b/tests/integration/cqlengine/statements/test_insert_statement.py @@ -24,10 +24,7 @@ def test_statement(self): ist.add_assignment(Column(db_field='a'), 'b') ist.add_assignment(Column(db_field='c'), 'd') - self.assertEqual( - str(ist), - 'INSERT INTO table ("a", "c") VALUES (%(0)s, %(1)s)' - ) + assert str(ist) == 'INSERT INTO table ("a", "c") VALUES (%(0)s, %(1)s)' def test_context_update(self): ist = InsertStatement('table', None) @@ -35,12 +32,9 @@ def test_context_update(self): ist.add_assignment(Column(db_field='c'), 'd') ist.update_context_id(4) - self.assertEqual( - str(ist), - 'INSERT INTO table ("a", "c") VALUES (%(4)s, %(5)s)' - ) + assert str(ist) == 'INSERT INTO table ("a", "c") VALUES (%(4)s, %(5)s)' ctx = ist.get_context() - self.assertEqual(ctx, {'4': 'b', '5': 'd'}) + assert ctx == {'4': 'b', '5': 'd'} def test_additional_rendering(self): ist = InsertStatement('table', ttl=60) diff --git a/tests/integration/cqlengine/statements/test_select_statement.py b/tests/integration/cqlengine/statements/test_select_statement.py index 26c9c804cb..40516fea3c 100644 --- a/tests/integration/cqlengine/statements/test_select_statement.py +++ b/tests/integration/cqlengine/statements/test_select_statement.py @@ -22,7 +22,7 @@ class SelectStatementTests(unittest.TestCase): def test_single_field_is_listified(self): """ tests that passing a string field into the constructor puts it into a list """ ss = SelectStatement('table', 'field') - self.assertEqual(ss.fields, ['field']) + assert ss.fields == ['field'] def test_field_rendering(self): """ tests that fields are properly added to the select statement """ @@ -44,41 +44,41 @@ def test_table_rendering(self): def test_where_clause_rendering(self): ss = SelectStatement('table') ss.add_where(Column(db_field='a'), EqualsOperator(), 'b') - self.assertEqual(str(ss), 'SELECT * FROM table WHERE "a" = %(0)s', str(ss)) + assert str(ss) == 'SELECT * FROM table WHERE "a" = %(0)s', str(ss) def test_count(self): ss = SelectStatement('table', count=True, limit=10, order_by='d') ss.add_where(Column(db_field='a'), EqualsOperator(), 'b') - self.assertEqual(str(ss), 'SELECT COUNT(*) FROM table WHERE "a" = %(0)s LIMIT 10', str(ss)) + assert str(ss) == 'SELECT COUNT(*) FROM table WHERE "a" = %(0)s LIMIT 10', str(ss) self.assertIn('LIMIT', str(ss)) self.assertNotIn('ORDER', str(ss)) def test_distinct(self): ss = SelectStatement('table', distinct_fields=['field2']) ss.add_where(Column(db_field='field1'), EqualsOperator(), 'b') - self.assertEqual(str(ss), 'SELECT DISTINCT "field2" FROM table WHERE "field1" = %(0)s', str(ss)) + assert str(ss) == 'SELECT DISTINCT "field2" FROM table WHERE "field1" = %(0)s', str(ss) ss = SelectStatement('table', distinct_fields=['field1', 'field2']) - self.assertEqual(str(ss), 'SELECT DISTINCT "field1", "field2" FROM table') + assert str(ss) == 'SELECT DISTINCT "field1", "field2" FROM table' ss = SelectStatement('table', distinct_fields=['field1'], count=True) - self.assertEqual(str(ss), 'SELECT DISTINCT COUNT("field1") FROM table') + assert str(ss) == 'SELECT DISTINCT COUNT("field1") FROM table' def test_context(self): ss = SelectStatement('table') ss.add_where(Column(db_field='a'), EqualsOperator(), 'b') - self.assertEqual(ss.get_context(), {'0': 'b'}) + assert ss.get_context() == {'0': 'b'} def test_context_id_update(self): """ tests that the right things happen the the context id """ ss = SelectStatement('table') ss.add_where(Column(db_field='a'), EqualsOperator(), 'b') - self.assertEqual(ss.get_context(), {'0': 'b'}) - self.assertEqual(str(ss), 'SELECT * FROM table WHERE "a" = %(0)s') + assert ss.get_context() == {'0': 'b'} + assert str(ss) == 'SELECT * FROM table WHERE "a" = %(0)s' ss.update_context_id(5) - self.assertEqual(ss.get_context(), {'5': 'b'}) - self.assertEqual(str(ss), 'SELECT * FROM table WHERE "a" = %(5)s') + assert ss.get_context() == {'5': 'b'} + assert str(ss) == 'SELECT * FROM table WHERE "a" = %(5)s' def test_additional_rendering(self): ss = SelectStatement( diff --git a/tests/integration/cqlengine/statements/test_update_statement.py b/tests/integration/cqlengine/statements/test_update_statement.py index 4429625bf4..149f43e1bb 100644 --- a/tests/integration/cqlengine/statements/test_update_statement.py +++ b/tests/integration/cqlengine/statements/test_update_statement.py @@ -33,17 +33,17 @@ def test_rendering(self): us.add_assignment(Column(db_field='a'), 'b') us.add_assignment(Column(db_field='c'), 'd') us.add_where(Column(db_field='a'), EqualsOperator(), 'x') - self.assertEqual(str(us), 'UPDATE table SET "a" = %(0)s, "c" = %(1)s WHERE "a" = %(2)s', str(us)) + assert str(us) == 'UPDATE table SET "a" = %(0)s, "c" = %(1)s WHERE "a" = %(2)s', str(us) us.add_where(Column(db_field='a'), NotEqualsOperator(), 'y') - self.assertEqual(str(us), 'UPDATE table SET "a" = %(0)s, "c" = %(1)s WHERE "a" = %(2)s AND "a" != %(3)s', str(us)) + assert str(us) == 'UPDATE table SET "a" = %(0)s, "c" = %(1)s WHERE "a" = %(2)s AND "a" != %(3)s', str(us) def test_context(self): us = UpdateStatement('table') us.add_assignment(Column(db_field='a'), 'b') us.add_assignment(Column(db_field='c'), 'd') us.add_where(Column(db_field='a'), EqualsOperator(), 'x') - self.assertEqual(us.get_context(), {'0': 'b', '1': 'd', '2': 'x'}) + assert us.get_context() == {'0': 'b', '1': 'd', '2': 'x'} def test_context_update(self): us = UpdateStatement('table') @@ -51,8 +51,8 @@ def test_context_update(self): us.add_assignment(Column(db_field='c'), 'd') us.add_where(Column(db_field='a'), EqualsOperator(), 'x') us.update_context_id(3) - self.assertEqual(str(us), 'UPDATE table SET "a" = %(4)s, "c" = %(5)s WHERE "a" = %(3)s') - self.assertEqual(us.get_context(), {'4': 'b', '5': 'd', '3': 'x'}) + assert str(us) == 'UPDATE table SET "a" = %(4)s, "c" = %(5)s WHERE "a" = %(3)s' + assert us.get_context() == {'4': 'b', '5': 'd', '3': 'x'} def test_additional_rendering(self): us = UpdateStatement('table', ttl=60) @@ -63,7 +63,7 @@ def test_additional_rendering(self): def test_update_set_add(self): us = UpdateStatement('table') us.add_update(Set(Text, db_field='a'), set((1,)), 'add') - self.assertEqual(str(us), 'UPDATE table SET "a" = "a" + %(0)s') + assert str(us) == 'UPDATE table SET "a" = "a" + %(0)s' def test_update_empty_set_add_does_not_assign(self): us = UpdateStatement('table') diff --git a/tests/integration/cqlengine/statements/test_where_clause.py b/tests/integration/cqlengine/statements/test_where_clause.py index 0090fa0123..2f2f3d9222 100644 --- a/tests/integration/cqlengine/statements/test_where_clause.py +++ b/tests/integration/cqlengine/statements/test_where_clause.py @@ -29,8 +29,8 @@ def test_where_clause_rendering(self): wc = WhereClause('a', EqualsOperator(), 'c') wc.set_context_id(5) - self.assertEqual('"a" = %(5)s', str(wc), str(wc)) - self.assertEqual('"a" = %(5)s', str(wc), type(wc)) + assert '"a" = %(5)s' == str(wc), str(wc) + assert '"a" = %(5)s' == str(wc), type(wc) def test_equality_method(self): """ tests that 2 identical where clauses evaluate as == """ diff --git a/tests/integration/cqlengine/test_batch_query.py b/tests/integration/cqlengine/test_batch_query.py index 399bee6202..9647643a1b 100644 --- a/tests/integration/cqlengine/test_batch_query.py +++ b/tests/integration/cqlengine/test_batch_query.py @@ -73,12 +73,12 @@ def test_update_success_case(self): inst.batch(b).save() inst2 = TestMultiKeyModel.get(partition=self.pkey, cluster=2) - self.assertEqual(inst2.count, 3) + assert inst2.count == 3 b.execute() inst3 = TestMultiKeyModel.get(partition=self.pkey, cluster=2) - self.assertEqual(inst3.count, 4) + assert inst3.count == 4 def test_delete_success_case(self): @@ -116,9 +116,9 @@ def test_bulk_delete_success_case(self): with BatchQuery() as b: TestMultiKeyModel.objects.batch(b).filter(partition=0).delete() - self.assertEqual(TestMultiKeyModel.filter(partition=0).count(), 5) + assert TestMultiKeyModel.filter(partition=0).count() == 5 - self.assertEqual(TestMultiKeyModel.filter(partition=0).count(), 0) + assert TestMultiKeyModel.filter(partition=0).count() == 0 #cleanup for m in TestMultiKeyModel.all(): m.delete() @@ -146,11 +146,11 @@ def my_callback(*args, **kwargs): batch.add_callback(my_callback, 2, named_arg='value') batch.add_callback(my_callback, 1, 3) - self.assertEqual(batch._callbacks, [ + assert batch._callbacks == [ (my_callback, (), {}), (my_callback, (2,), {'named_arg':'value'}), (my_callback, (1, 3), {}) - ]) + ] def test_callbacks_properly_execute_callables_and_tuples(self): @@ -166,8 +166,8 @@ def my_callback(*args, **kwargs): batch.execute() - self.assertEqual(len(call_history), 2) - self.assertEqual([(), ('more', 'args')], call_history) + assert len(call_history) == 2 + assert [(), ('more', 'args')] == call_history def test_callbacks_tied_to_execute(self): """Batch callbacks should NOT fire if batch is not executed in context manager mode""" @@ -179,7 +179,7 @@ def my_callback(*args, **kwargs): with BatchQuery() as batch: batch.add_callback(my_callback) - self.assertEqual(len(call_history), 1) + assert len(call_history) == 1 class SomeError(Exception): pass @@ -192,7 +192,7 @@ class SomeError(Exception): raise SomeError # still same call history. Nothing added - self.assertEqual(len(call_history), 1) + assert len(call_history) == 1 # but if execute ran, even with an error bubbling through # the callbacks also would have fired @@ -202,7 +202,7 @@ class SomeError(Exception): raise SomeError # updated call history - self.assertEqual(len(call_history), 2) + assert len(call_history) == 2 def test_callbacks_work_multiple_times(self): """ @@ -224,7 +224,7 @@ def my_callback(*args, **kwargs): batch.add_callback(my_callback) batch.execute() batch.execute() - self.assertEqual(len(w), 2) # package filter setup to warn always + assert len(w) == 2 # package filter setup to warn always self.assertRegex(str(w[0].message), r"^Batch.*multiple.*") def test_disable_multiple_callback_warning(self): diff --git a/tests/integration/cqlengine/test_connections.py b/tests/integration/cqlengine/test_connections.py index 32db143088..839b180e2c 100644 --- a/tests/integration/cqlengine/test_connections.py +++ b/tests/integration/cqlengine/test_connections.py @@ -481,12 +481,12 @@ def test_keyspace(self): TestModel.objects.using(keyspace='ks2').get(partition=1, cluster=1) obj2 = TestModel.objects.using(connection='cluster', keyspace='ks2').get(partition=1, cluster=1) - self.assertEqual(obj2.count, 2) + assert obj2.count == 2 # Update test TestModel.objects(partition=2, cluster=2).using(connection='cluster', keyspace='ks2').update(count=5) obj3 = TestModel.objects.using(connection='cluster', keyspace='ks2').get(partition=2, cluster=2) - self.assertEqual(obj3.count, 5) + assert obj3.count == 5 TestModel.objects(partition=2, cluster=2).using(connection='cluster', keyspace='ks2').delete() with self.assertRaises(TestModel.DoesNotExist): @@ -511,11 +511,11 @@ def test_connection(self): TestModel.objects.using(connection='cluster').create(partition=1, cluster=1) TestModel.objects(partition=1, cluster=1).using(connection='cluster').update(count=2) obj1 = TestModel.objects.using(connection='cluster').get(partition=1, cluster=1) - self.assertEqual(obj1.count, 2) + assert obj1.count == 2 obj1.using(connection='cluster').update(count=5) obj1 = TestModel.objects.using(connection='cluster').get(partition=1, cluster=1) - self.assertEqual(obj1.count, 5) + assert obj1.count == 5 obj1.using(connection='cluster').delete() with self.assertRaises(TestModel.DoesNotExist): diff --git a/tests/integration/cqlengine/test_consistency.py b/tests/integration/cqlengine/test_consistency.py index a93bbee1ae..6bd095706b 100644 --- a/tests/integration/cqlengine/test_consistency.py +++ b/tests/integration/cqlengine/test_consistency.py @@ -53,7 +53,7 @@ def test_create_uses_consistency(self): qs.create(text="i am not fault tolerant this way") args = m.call_args - self.assertEqual(CL.ALL, args[0][0].consistency_level) + assert CL.ALL == args[0][0].consistency_level def test_queryset_is_returned_on_create(self): qs = TestConsistencyModel.consistency(CL.ALL) @@ -67,7 +67,7 @@ def test_update_uses_consistency(self): t.consistency(CL.ALL).save() args = m.call_args - self.assertEqual(CL.ALL, args[0][0].consistency_level) + assert CL.ALL == args[0][0].consistency_level def test_batch_consistency(self): @@ -77,7 +77,7 @@ def test_batch_consistency(self): args = m.call_args - self.assertEqual(CL.ALL, args[0][0].consistency_level) + assert CL.ALL == args[0][0].consistency_level with mock.patch.object(self.session, 'execute') as m: with BatchQuery() as b: @@ -95,7 +95,7 @@ def test_blind_update(self): TestConsistencyModel.objects(id=uid).consistency(CL.ALL).update(text="grilled cheese") args = m.call_args - self.assertEqual(CL.ALL, args[0][0].consistency_level) + assert CL.ALL == args[0][0].consistency_level def test_delete(self): # ensures we always carry consistency through on delete statements @@ -110,13 +110,13 @@ def test_delete(self): TestConsistencyModel.objects(id=uid).consistency(CL.ALL).delete() args = m.call_args - self.assertEqual(CL.ALL, args[0][0].consistency_level) + assert CL.ALL == args[0][0].consistency_level def test_default_consistency(self): # verify global assumed default - self.assertEqual(Session._default_consistency_level, ConsistencyLevel.LOCAL_ONE) + assert Session._default_consistency_level == ConsistencyLevel.LOCAL_ONE # verify that this session default is set according to connection.setup # assumes tests/cqlengine/__init__ setup uses CL.ONE session = connection.get_session() - self.assertEqual(session.default_consistency_level, ConsistencyLevel.ONE) + assert session.default_consistency_level == ConsistencyLevel.ONE diff --git a/tests/integration/cqlengine/test_context_query.py b/tests/integration/cqlengine/test_context_query.py index 8ced5f0f49..fe4999c013 100644 --- a/tests/integration/cqlengine/test_context_query.py +++ b/tests/integration/cqlengine/test_context_query.py @@ -68,9 +68,9 @@ def test_context_manager(self): # model keyspace write/read for ks in self.KEYSPACES: with ContextQuery(TestModel, keyspace=ks) as tm: - self.assertEqual(tm.__keyspace__, ks) + assert tm.__keyspace__ == ks - self.assertEqual(TestModel._get_keyspace(), 'ks1') + assert TestModel._get_keyspace() == 'ks1' def test_default_keyspace(self): """ @@ -87,14 +87,14 @@ def test_default_keyspace(self): TestModel.objects.create(partition=i, cluster=i) with ContextQuery(TestModel) as tm: - self.assertEqual(5, len(tm.objects.all())) + assert 5 == len(tm.objects.all()) with ContextQuery(TestModel, keyspace='ks1') as tm: - self.assertEqual(5, len(tm.objects.all())) + assert 5 == len(tm.objects.all()) for ks in self.KEYSPACES[1:]: with ContextQuery(TestModel, keyspace=ks) as tm: - self.assertEqual(0, len(tm.objects.all())) + assert 0 == len(tm.objects.all()) def test_context_keyspace(self): """ @@ -111,20 +111,20 @@ def test_context_keyspace(self): tm.objects.create(partition=i, cluster=i) with ContextQuery(TestModel, keyspace='ks4') as tm: - self.assertEqual(5, len(tm.objects.all())) + assert 5 == len(tm.objects.all()) - self.assertEqual(0, len(TestModel.objects.all())) + assert 0 == len(TestModel.objects.all()) for ks in self.KEYSPACES[:2]: with ContextQuery(TestModel, keyspace=ks) as tm: - self.assertEqual(0, len(tm.objects.all())) + assert 0 == len(tm.objects.all()) # simple data update with ContextQuery(TestModel, keyspace='ks4') as tm: obj = tm.objects.get(partition=1) obj.update(count=42) - self.assertEqual(42, tm.objects.get(partition=1).count) + assert 42 == tm.objects.get(partition=1).count def test_context_multiple_models(self): """ @@ -140,8 +140,8 @@ def test_context_multiple_models(self): with ContextQuery(TestModel, TestModel, keyspace='ks4') as (tm1, tm2): self.assertNotEqual(tm1, tm2) - self.assertEqual(tm1.__keyspace__, 'ks4') - self.assertEqual(tm2.__keyspace__, 'ks4') + assert tm1.__keyspace__ == 'ks4' + assert tm2.__keyspace__ == 'ks4' def test_context_invalid_parameters(self): """ diff --git a/tests/integration/cqlengine/test_ifexists.py b/tests/integration/cqlengine/test_ifexists.py index 9e8e5d5424..225b59212c 100644 --- a/tests/integration/cqlengine/test_ifexists.py +++ b/tests/integration/cqlengine/test_ifexists.py @@ -95,25 +95,25 @@ def test_update_if_exists(self): m.text = 'changed' m.if_exists().update() m = TestIfExistsModel.get(id=id) - self.assertEqual(m.text, 'changed') + assert m.text == 'changed' # save() m.text = 'changed_again' m.if_exists().save() m = TestIfExistsModel.get(id=id) - self.assertEqual(m.text, 'changed_again') + assert m.text == 'changed_again' m = TestIfExistsModel(id=uuid4(), count=44) # do not exists with self.assertRaises(LWTException) as assertion: m.if_exists().update() - self.assertEqual(assertion.exception.existing.get('[applied]'), False) + assert assertion.exception.existing.get('[applied]') == False # queryset update with self.assertRaises(LWTException) as assertion: TestIfExistsModel.objects(id=uuid4()).if_exists().update(count=8) - self.assertEqual(assertion.exception.existing.get('[applied]'), False) + assert assertion.exception.existing.get('[applied]') == False @unittest.skipUnless(PROTOCOL_VERSION >= 2, "only runs against the cql3 protocol v2.0") def test_batch_update_if_exists_success(self): @@ -140,14 +140,14 @@ def test_batch_update_if_exists_success(self): m = TestIfExistsModel(id=uuid4(), count=42) # Doesn't exist m.batch(b).if_exists().update() - self.assertEqual(assertion.exception.existing.get('[applied]'), False) + assert assertion.exception.existing.get('[applied]') == False q = TestIfExistsModel.objects(id=id) - self.assertEqual(len(q), 1) + assert len(q) == 1 tm = q.first() - self.assertEqual(tm.count, 8) - self.assertEqual(tm.text, '111111111') + assert tm.count == 8 + assert tm.text == '111111111' @unittest.skipUnless(PROTOCOL_VERSION >= 2, "only runs against the cql3 protocol v2.0") def test_batch_mixed_update_if_exists_success(self): @@ -169,7 +169,7 @@ def test_batch_mixed_update_if_exists_success(self): n = TestIfExistsModel2(id=1, count=10, text="Failure") # Doesn't exist n.batch(b).if_exists().update() - self.assertEqual(assertion.exception.existing.get('[applied]'), False) + assert assertion.exception.existing.get('[applied]') == False @unittest.skipUnless(PROTOCOL_VERSION >= 2, "only runs against the cql3 protocol v2.0") def test_delete_if_exists(self): @@ -188,19 +188,19 @@ def test_delete_if_exists(self): m = TestIfExistsModel.create(id=id, count=8, text='123456789') m.if_exists().delete() q = TestIfExistsModel.objects(id=id) - self.assertEqual(len(q), 0) + assert len(q) == 0 m = TestIfExistsModel(id=uuid4(), count=44) # do not exists with self.assertRaises(LWTException) as assertion: m.if_exists().delete() - self.assertEqual(assertion.exception.existing.get('[applied]'), False) + assert assertion.exception.existing.get('[applied]') == False # queryset delete with self.assertRaises(LWTException) as assertion: TestIfExistsModel.objects(id=uuid4()).if_exists().delete() - self.assertEqual(assertion.exception.existing.get('[applied]'), False) + assert assertion.exception.existing.get('[applied]') == False @unittest.skipUnless(PROTOCOL_VERSION >= 2, "only runs against the cql3 protocol v2.0") def test_batch_delete_if_exists_success(self): @@ -222,14 +222,14 @@ def test_batch_delete_if_exists_success(self): m.batch(b).if_exists().delete() q = TestIfExistsModel.objects(id=id) - self.assertEqual(len(q), 0) + assert len(q) == 0 with self.assertRaises(LWTException) as assertion: with BatchQuery() as b: m = TestIfExistsModel(id=uuid4(), count=42) # Doesn't exist m.batch(b).if_exists().delete() - self.assertEqual(assertion.exception.existing.get('[applied]'), False) + assert assertion.exception.existing.get('[applied]') == False @unittest.skipUnless(PROTOCOL_VERSION >= 2, "only runs against the cql3 protocol v2.0") def test_batch_delete_mixed(self): @@ -251,9 +251,9 @@ def test_batch_delete_mixed(self): n = TestIfExistsModel2(id=3, count=42, text='1111111') # Doesn't exist n.batch(b).if_exists().delete() - self.assertEqual(assertion.exception.existing.get('[applied]'), False) + assert assertion.exception.existing.get('[applied]') == False q = TestIfExistsModel2.objects(id=3, count=8) - self.assertEqual(len(q), 1) + assert len(q) == 1 class IfExistsQueryTest(BaseIfExistsTest): diff --git a/tests/integration/cqlengine/test_ifnotexists.py b/tests/integration/cqlengine/test_ifnotexists.py index 013d4e245e..5da8b7a3b3 100644 --- a/tests/integration/cqlengine/test_ifnotexists.py +++ b/tests/integration/cqlengine/test_ifnotexists.py @@ -86,19 +86,19 @@ def test_insert_if_not_exists(self): with self.assertRaises(LWTException) as assertion: TestIfNotExistsModel.objects(count=9, text='111111111111').if_not_exists().create(id=id) - self.assertEqual(assertion.exception.existing, { + assert assertion.exception.existing == { 'count': 8, 'id': id, 'text': '123456789', '[applied]': False, - }) + } q = TestIfNotExistsModel.objects(id=id) - self.assertEqual(len(q), 1) + assert len(q) == 1 tm = q.first() - self.assertEqual(tm.count, 8) - self.assertEqual(tm.text, '123456789') + assert tm.count == 8 + assert tm.text == '123456789' @unittest.skipUnless(PROTOCOL_VERSION >= 2, "only runs against the cql3 protocol v2.0") def test_batch_insert_if_not_exists(self): @@ -114,19 +114,19 @@ def test_batch_insert_if_not_exists(self): with self.assertRaises(LWTException) as assertion: b.execute() - self.assertEqual(assertion.exception.existing, { + assert assertion.exception.existing == { 'count': 8, 'id': id, 'text': '123456789', '[applied]': False, - }) + } q = TestIfNotExistsModel.objects(id=id) - self.assertEqual(len(q), 1) + assert len(q) == 1 tm = q.first() - self.assertEqual(tm.count, 8) - self.assertEqual(tm.text, '123456789') + assert tm.count == 8 + assert tm.text == '123456789' class IfNotExistsModelTest(BaseIfNotExistsTest): @@ -174,7 +174,7 @@ def test_instance_is_returned(self): o = TestIfNotExistsModel.create(text="whatever") o.text = "new stuff" o = o.if_not_exists() - self.assertEqual(True, o._if_not_exists) + assert True == o._if_not_exists def test_if_not_exists_is_not_include_with_query_on_update(self): """ diff --git a/tests/integration/cqlengine/test_lwt_conditional.py b/tests/integration/cqlengine/test_lwt_conditional.py index f5ec89dd2e..b0d8b4a962 100644 --- a/tests/integration/cqlengine/test_lwt_conditional.py +++ b/tests/integration/cqlengine/test_lwt_conditional.py @@ -71,8 +71,8 @@ def test_update_conditional_success(self): t.iff(text='blah blah').save() updated = TestConditionalModel.objects(id=id).first() - self.assertEqual(updated.count, 5) - self.assertEqual(updated.text, 'new blah') + assert updated.count == 5 + assert updated.text == 'new blah' def test_update_failure(self): t = TestConditionalModel.if_not_exists().create(text='blah blah') @@ -82,10 +82,10 @@ def test_update_failure(self): with self.assertRaises(LWTException) as assertion: t.save() - self.assertEqual(assertion.exception.existing, { + assert assertion.exception.existing == { 'text': 'blah blah', '[applied]': False, - }) + } def test_blind_update(self): t = TestConditionalModel.if_not_exists().create(text='blah blah') @@ -106,17 +106,17 @@ def test_blind_update_fail(self): with self.assertRaises(LWTException) as assertion: qs.update(text='this will never work') - self.assertEqual(assertion.exception.existing, { + assert assertion.exception.existing == { 'text': 'blah blah', '[applied]': False, - }) + } def test_conditional_clause(self): tc = ConditionalClause('some_value', 23) tc.set_context_id(3) - self.assertEqual('"some_value" = %(3)s', str(tc)) - self.assertEqual('"some_value" = %(3)s', str(tc)) + assert '"some_value" = %(3)s' == str(tc) + assert '"some_value" = %(3)s' == str(tc) def test_batch_update_conditional(self): t = TestConditionalModel.if_not_exists().create(text='something', count=5) @@ -125,21 +125,21 @@ def test_batch_update_conditional(self): t.batch(b).iff(count=5).update(text='something else') updated = TestConditionalModel.objects(id=id).first() - self.assertEqual(updated.text, 'something else') + assert updated.text == 'something else' b = BatchQuery() updated.batch(b).iff(count=6).update(text='and another thing') with self.assertRaises(LWTException) as assertion: b.execute() - self.assertEqual(assertion.exception.existing, { + assert assertion.exception.existing == { 'id': id, 'count': 5, '[applied]': False, - }) + } updated = TestConditionalModel.objects(id=id).first() - self.assertEqual(updated.text, 'something else') + assert updated.text == 'something else' @unittest.skip("Skipping until PYTHON-943 is resolved") def test_batch_update_conditional_several_rows(self): @@ -166,21 +166,21 @@ def test_batch_update_conditional_several_rows(self): def test_delete_conditional(self): # DML path t = TestConditionalModel.if_not_exists().create(text='something', count=5) - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1) + assert TestConditionalModel.objects(id=t.id).count() == 1 with self.assertRaises(LWTException): t.iff(count=9999).delete() - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1) + assert TestConditionalModel.objects(id=t.id).count() == 1 t.iff(count=5).delete() - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 0) + assert TestConditionalModel.objects(id=t.id).count() == 0 # QuerySet path t = TestConditionalModel.if_not_exists().create(text='something', count=5) - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1) + assert TestConditionalModel.objects(id=t.id).count() == 1 with self.assertRaises(LWTException): TestConditionalModel.objects(id=t.id).iff(count=9999).delete() - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1) + assert TestConditionalModel.objects(id=t.id).count() == 1 TestConditionalModel.objects(id=t.id).iff(count=5).delete() - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 0) + assert TestConditionalModel.objects(id=t.id).count() == 0 def test_delete_lwt_ne(self): """ @@ -195,19 +195,19 @@ def test_delete_lwt_ne(self): # DML path t = TestConditionalModel.if_not_exists().create(text='something', count=5) - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1) + assert TestConditionalModel.objects(id=t.id).count() == 1 with self.assertRaises(LWTException): t.iff(count__ne=5).delete() t.iff(count__ne=2).delete() - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 0) + assert TestConditionalModel.objects(id=t.id).count() == 0 # QuerySet path t = TestConditionalModel.if_not_exists().create(text='something', count=5) - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1) + assert TestConditionalModel.objects(id=t.id).count() == 1 with self.assertRaises(LWTException): TestConditionalModel.objects(id=t.id).iff(count__ne=5).delete() TestConditionalModel.objects(id=t.id).iff(count__ne=2).delete() - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 0) + assert TestConditionalModel.objects(id=t.id).count() == 0 def test_update_lwt_ne(self): """ @@ -222,20 +222,20 @@ def test_update_lwt_ne(self): # DML path t = TestConditionalModel.if_not_exists().create(text='something', count=5) - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1) + assert TestConditionalModel.objects(id=t.id).count() == 1 with self.assertRaises(LWTException): t.iff(count__ne=5).update(text='nothing') t.iff(count__ne=2).update(text='nothing') - self.assertEqual(TestConditionalModel.objects(id=t.id).first().text, 'nothing') + assert TestConditionalModel.objects(id=t.id).first().text == 'nothing' t.delete() # QuerySet path t = TestConditionalModel.if_not_exists().create(text='something', count=5) - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1) + assert TestConditionalModel.objects(id=t.id).count() == 1 with self.assertRaises(LWTException): TestConditionalModel.objects(id=t.id).iff(count__ne=5).update(text='nothing') TestConditionalModel.objects(id=t.id).iff(count__ne=2).update(text='nothing') - self.assertEqual(TestConditionalModel.objects(id=t.id).first().text, 'nothing') + assert TestConditionalModel.objects(id=t.id).first().text == 'nothing' t.delete() def test_update_to_none(self): @@ -245,7 +245,7 @@ def test_update_to_none(self): # DML path t = TestConditionalModel.if_not_exists().create(text='something', count=5) - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1) + assert TestConditionalModel.objects(id=t.id).count() == 1 with self.assertRaises(LWTException): t.iff(count=9999).update(text=None) self.assertIsNotNone(TestConditionalModel.objects(id=t.id).first().text) @@ -254,7 +254,7 @@ def test_update_to_none(self): # QuerySet path t = TestConditionalModel.if_not_exists().create(text='something', count=5) - self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1) + assert TestConditionalModel.objects(id=t.id).count() == 1 with self.assertRaises(LWTException): TestConditionalModel.objects(id=t.id).iff(count=9999).update(text=None) self.assertIsNotNone(TestConditionalModel.objects(id=t.id).first().text) @@ -267,14 +267,14 @@ def test_column_delete_after_update(self): t.iff(count=5).update(text=None, count=6) self.assertIsNone(t.text) - self.assertEqual(t.count, 6) + assert t.count == 6 # QuerySet path t = TestConditionalModel.if_not_exists().create(text='something', count=5) TestConditionalModel.objects(id=t.id).iff(count=5).update(text=None, count=6) self.assertIsNone(TestConditionalModel.objects(id=t.id).first().text) - self.assertEqual(TestConditionalModel.objects(id=t.id).first().count, 6) + assert TestConditionalModel.objects(id=t.id).first().count == 6 def test_conditional_without_instance(self): """ @@ -295,4 +295,4 @@ def test_conditional_without_instance(self): t = TestConditionalModel.filter(id=uuid).first() self.assertIsNone(t.text) - self.assertEqual(t.count, 6) + assert t.count == 6 diff --git a/tests/integration/cqlengine/test_ttl.py b/tests/integration/cqlengine/test_ttl.py index 4507e91ae7..9431cc3717 100644 --- a/tests/integration/cqlengine/test_ttl.py +++ b/tests/integration/cqlengine/test_ttl.py @@ -130,7 +130,7 @@ def test_instance_is_returned(self): o = TestTTLModel.create(text="whatever") o.text = "new stuff" o = o.ttl(60) - self.assertEqual(60, o._ttl) + assert 60 == o._ttl def test_ttl_is_include_with_query_on_update(self): session = get_session() @@ -180,7 +180,7 @@ def test_default_ttl_not_set(self): self.assertIsNone(o._ttl) default_ttl = self.get_default_ttl('test_ttlmodel') - self.assertEqual(default_ttl, 0) + assert default_ttl == 0 with mock.patch.object(session, 'execute') as m: TestTTLModel.objects(id=tid).update(text="aligators") @@ -198,7 +198,7 @@ def test_default_ttl_set(self): self.assertIsNone(o._ttl) default_ttl = self.get_default_ttl('test_default_ttlmodel') - self.assertEqual(default_ttl, 20) + assert default_ttl == 20 with mock.patch.object(session, 'execute') as m: TestTTLModel.objects(id=tid).update(text="aligators expired") @@ -211,13 +211,13 @@ def test_default_ttl_modify(self): session = get_session() default_ttl = self.get_default_ttl('test_default_ttlmodel') - self.assertEqual(default_ttl, 20) + assert default_ttl == 20 TestDefaultTTLModel.__options__ = {'default_time_to_live': 10} sync_table(TestDefaultTTLModel) default_ttl = self.get_default_ttl('test_default_ttlmodel') - self.assertEqual(default_ttl, 10) + assert default_ttl == 10 # Restore default TTL TestDefaultTTLModel.__options__ = {'default_time_to_live': 20} @@ -229,7 +229,7 @@ def test_override_default_ttl(self): tid = o.id o.ttl(3600) - self.assertEqual(o._ttl, 3600) + assert o._ttl == 3600 with mock.patch.object(session, 'execute') as m: TestDefaultTTLModel.objects(id=tid).ttl(None).update(text="aligators expired") diff --git a/tests/integration/long/test_failure_types.py b/tests/integration/long/test_failure_types.py index ea8897185a..98ff114685 100644 --- a/tests/integration/long/test_failure_types.py +++ b/tests/integration/long/test_failure_types.py @@ -161,9 +161,9 @@ def _perform_cql_statement(self, text, consistency_level, expected_exception, se self.execute_helper(session, statement) if ProtocolVersion.uses_error_code_map(PROTOCOL_VERSION): if isinstance(cm.exception, ReadFailure): - self.assertEqual(list(cm.exception.error_code_map.values())[0], 1) + assert list(cm.exception.error_code_map.values())[0] == 1 if isinstance(cm.exception, WriteFailure): - self.assertEqual(list(cm.exception.error_code_map.values())[0], 0) + assert list(cm.exception.error_code_map.values())[0] == 0 def test_write_failures_from_coordinator(self): """ diff --git a/tests/integration/long/test_ipv6.py b/tests/integration/long/test_ipv6.py index d58bad987a..c57987cc26 100644 --- a/tests/integration/long/test_ipv6.py +++ b/tests/integration/long/test_ipv6.py @@ -83,7 +83,7 @@ def test_connect(self): session = cluster.connect() future = session.execute_async("SELECT * FROM system.local WHERE key='local'") future.result() - self.assertEqual(future._current_host.address, '::1') + assert future._current_host.address == '::1' cluster.shutdown() def test_error(self): diff --git a/tests/integration/long/test_large_data.py b/tests/integration/long/test_large_data.py index 59873204a4..6c75059a26 100644 --- a/tests/integration/long/test_large_data.py +++ b/tests/integration/long/test_large_data.py @@ -161,11 +161,11 @@ def test_wide_batch_rows(self): lastvalue = 0 for j, row in enumerate(results): lastValue=row['i'] - self.assertEqual(lastValue, j) + assert lastValue == j #check the last value make sure it's what we expect index_value = to_insert-1 - self.assertEqual(lastValue,index_value,"Verification failed only found {0} inserted we were expecting {1}".format(j,index_value)) + assert lastValue == index_value, "Verification failed only found {0} inserted we were expecting {1}".format(j,index_value) session.cluster.shutdown() @@ -200,7 +200,7 @@ def test_wide_byte_rows(self): # Verify bb = pack('>H', 0xCAFE) for i, row in enumerate(results): - self.assertEqual(row['v'], bb) + assert row['v'] == bb self.assertGreaterEqual(i, expected_results, "Verification failed only found {0} inserted we were expecting {1}".format(i,expected_results)) @@ -235,7 +235,7 @@ def test_large_text(self): # Verify found_result = False for i, row in enumerate(result): - self.assertEqual(row['txt'], text) + assert row['txt'] == text found_result = True self.assertTrue(found_result, "No results were found") @@ -266,6 +266,6 @@ def test_wide_table(self): # Verify for row in result: for i in range(table_width): - self.assertEqual(row[create_column_name(i)], i) + assert row[create_column_name(i)] == i session.cluster.shutdown() diff --git a/tests/integration/long/test_loadbalancingpolicies.py b/tests/integration/long/test_loadbalancingpolicies.py index a6dff4d786..91ff56042b 100644 --- a/tests/integration/long/test_loadbalancingpolicies.py +++ b/tests/integration/long/test_loadbalancingpolicies.py @@ -366,7 +366,7 @@ def test_dc_aware_roundrobin_one_remote_host(self): responses = set() for node in [1, 2, 5]: responses.add(self.coordinator_stats.get_query_count(node)) - self.assertEqual(set([0, 0, 12]), responses) + assert set([0, 0, 12]) == responses self.coordinator_stats.reset_counts() decommission(5) @@ -380,7 +380,7 @@ def test_dc_aware_roundrobin_one_remote_host(self): responses = set() for node in [1, 2]: responses.add(self.coordinator_stats.get_query_count(node)) - self.assertEqual(set([0, 12]), responses) + assert set([0, 12]) == responses self.coordinator_stats.reset_counts() decommission(1) @@ -440,9 +440,9 @@ def token_aware(self, keyspace, use_prepared=False): self._query(session, keyspace, use_prepared=use_prepared) self.fail() except Unavailable as e: - self.assertEqual(e.consistency, 1) - self.assertEqual(e.required_replicas, 1) - self.assertEqual(e.alive_replicas, 0) + assert e.consistency == 1 + assert e.required_replicas == 1 + assert e.alive_replicas == 0 self.coordinator_stats.reset_counts() start(2) @@ -476,7 +476,7 @@ def token_aware(self, keyspace, use_prepared=False): self.coordinator_stats.get_query_count(1), self.coordinator_stats.get_query_count(3) ]) - self.assertEqual(results, set([0, 12])) + assert results == set([0, 12]) self.coordinator_stats.assert_query_count_equals(self, 2, 0) def test_token_aware_composite_key(self): @@ -547,7 +547,7 @@ def test_token_aware_with_local_table(self): p = session.prepare("SELECT * FROM system.local WHERE key=?") # this would blow up prior to 61b4fad r = session.execute(p, ('local',)) - self.assertEqual(r[0].key, 'local') + assert r[0].key == 'local' def test_token_aware_with_shuffle_rf2(self): """ @@ -602,7 +602,7 @@ def test_token_aware_with_shuffle_rf3(self): self.coordinator_stats.assert_query_count_equals(self, 1, 0) query_count_two = self.coordinator_stats.get_query_count(2) query_count_three = self.coordinator_stats.get_query_count(3) - self.assertEqual(query_count_two + query_count_three, 12) + assert query_count_two + query_count_three == 12 self.coordinator_stats.reset_counts() stop(2) @@ -644,7 +644,7 @@ def test_token_aware_with_transient_replication(self): f = session.execute_async(query, (i,), trace=True) full_dc1_replicas = [h for h in cluster.metadata.get_replicas('test_tr', cqltypes.Int32Type.serialize(i, cluster.protocol_version)) if h.datacenter == 'dc1'] - self.assertEqual(len(full_dc1_replicas), 2) + assert len(full_dc1_replicas) == 2 f.result() trace_hosts = [cluster.metadata.get_host(e.source) for e in f.get_query_trace().events] @@ -681,7 +681,7 @@ def _check_query_order_changes(self, session, keyspace): self.coordinator_stats.get_query_count(3)) query_counts.add(loop_qcs) - self.assertEqual(sum(loop_qcs), 12) + assert sum(loop_qcs) == 12 # end the loop if we get more than one query ordering self.coordinator_stats.reset_counts() @@ -763,7 +763,7 @@ def test_black_list_with_host_filter_policy(self): # will be 4 and for the other 8 first_node_count = self.coordinator_stats.get_query_count(1) third_node_count = self.coordinator_stats.get_query_count(3) - self.assertEqual(first_node_count + third_node_count, 12) + assert first_node_count + third_node_count == 12 self.assertTrue(first_node_count == 8 or first_node_count == 4) self.coordinator_stats.assert_query_count_equals(self, 2, 0) diff --git a/tests/integration/long/test_policies.py b/tests/integration/long/test_policies.py index 33f35ced0d..9936209e0d 100644 --- a/tests/integration/long/test_policies.py +++ b/tests/integration/long/test_policies.py @@ -62,6 +62,6 @@ def test_should_rethrow_on_unvailable_with_default_policy_if_cas(self): session.execute("update test_retry_policy_cas.t set data = 'staging' where id = 42 if data ='testing'") exception = cm.exception - self.assertEqual(exception.consistency, ConsistencyLevel.SERIAL) - self.assertEqual(exception.required_replicas, 2) - self.assertEqual(exception.alive_replicas, 1) + assert exception.consistency == ConsistencyLevel.SERIAL + assert exception.required_replicas == 2 + assert exception.alive_replicas == 1 diff --git a/tests/integration/long/test_schema.py b/tests/integration/long/test_schema.py index f1cc80a17a..f892acba52 100644 --- a/tests/integration/long/test_schema.py +++ b/tests/integration/long/test_schema.py @@ -156,6 +156,6 @@ def test_for_schema_disagreement_attribute(self): def check_and_wait_for_agreement(self, session, rs, exepected): # Wait for RESULT_KIND_SCHEMA_CHANGE message to arrive time.sleep(1) - self.assertEqual(rs.response_future.is_schema_agreed, exepected) + assert rs.response_future.is_schema_agreed == exepected if not rs.response_future.is_schema_agreed: session.cluster.control_connection.wait_for_schema_agreement(wait_time=1000) diff --git a/tests/integration/simulacron/test_backpressure.py b/tests/integration/simulacron/test_backpressure.py index 69c38da8fe..db81b66420 100644 --- a/tests/integration/simulacron/test_backpressure.py +++ b/tests/integration/simulacron/test_backpressure.py @@ -123,7 +123,7 @@ def test_queued_requests_timeout(self): # that only "a few" successes happened here self.assertLess(successes, 50) self.assertLess(self.callback_successes, 50) - self.assertEqual(self.callback_errors, len(futures) - self.callback_successes) + assert self.callback_errors == len(futures) - self.callback_successes def test_cluster_busy(self): """ Verify that once TCP buffer is full we get busy exceptions rather than timeouts """ diff --git a/tests/integration/simulacron/test_cluster.py b/tests/integration/simulacron/test_cluster.py index 53aa9936fc..499cf10d10 100644 --- a/tests/integration/simulacron/test_cluster.py +++ b/tests/integration/simulacron/test_cluster.py @@ -51,10 +51,10 @@ def test_writetimeout(self): with self.assertRaises(WriteTimeout) as assert_raised_context: self.session.execute(query_to_prime_simple) wt = assert_raised_context.exception - self.assertEqual(wt.write_type, WriteType.name_to_value[write_type]) - self.assertEqual(wt.consistency, ConsistencyLevel.name_to_value[consistency]) - self.assertEqual(wt.received_responses, received_responses) - self.assertEqual(wt.required_responses, required_responses) + assert wt.write_type == WriteType.name_to_value[write_type] + assert wt.consistency == ConsistencyLevel.name_to_value[consistency] + assert wt.received_responses == received_responses + assert wt.required_responses == required_responses self.assertIn(write_type, str(wt)) self.assertIn(consistency, str(wt)) self.assertIn(str(received_responses), str(wt)) @@ -102,6 +102,6 @@ def test_duplicate(self): session = cluster.connect(wait_for_all_pools=True) warnings = mock_handler.messages.get("warning") - self.assertEqual(len(warnings), 1) + assert len(warnings) == 1 self.assertTrue('multiple hosts with the same endpoint' in warnings[0]) cluster.shutdown() diff --git a/tests/integration/simulacron/test_connection.py b/tests/integration/simulacron/test_connection.py index 95df69e44c..aed4fbafe1 100644 --- a/tests/integration/simulacron/test_connection.py +++ b/tests/integration/simulacron/test_connection.py @@ -386,7 +386,7 @@ def test_idle_connection_is_not_closed(self): time.sleep(20) - self.assertEqual(listener.hosts_marked_down, []) + assert listener.hosts_marked_down == [] def test_host_is_not_set_to_down_after_query_oto(self): """ @@ -420,7 +420,7 @@ def test_host_is_not_set_to_down_after_query_oto(self): f._event.wait() self.assertIsInstance(f._final_exception, OperationTimedOut) - self.assertEqual(listener.hosts_marked_down, []) + assert listener.hosts_marked_down == [] assert_quiescent_pool_state(self, cluster) def test_can_shutdown_connection_subclass(self): diff --git a/tests/integration/simulacron/test_empty_column.py b/tests/integration/simulacron/test_empty_column.py index 046aaacf79..fbbb9ec878 100644 --- a/tests/integration/simulacron/test_empty_column.py +++ b/tests/integration/simulacron/test_empty_column.py @@ -81,28 +81,16 @@ def test_empty_columns_with_all_row_factories(self): # Test all row factories self.cluster.profile_manager.profiles[EXEC_PROFILE_DEFAULT].row_factory = named_tuple_factory - self.assertEqual( - list(self.session.execute(query)), - [namedtuple('Row', ['field_0_', 'field_1_'])('testval', 'testval1')] - ) + assert list(self.session.execute(query)) == [namedtuple('Row', ['field_0_', 'field_1_'])('testval', 'testval1')] self.cluster.profile_manager.profiles[EXEC_PROFILE_DEFAULT].row_factory = tuple_factory - self.assertEqual( - list(self.session.execute(query)), - [('testval', 'testval1')] - ) + assert list(self.session.execute(query)) == [('testval', 'testval1')] self.cluster.profile_manager.profiles[EXEC_PROFILE_DEFAULT].row_factory = dict_factory - self.assertEqual( - list(self.session.execute(query)), - [{'': 'testval', ' ': 'testval1'}] - ) + assert list(self.session.execute(query)) == [{'': 'testval', ' ': 'testval1'}] self.cluster.profile_manager.profiles[EXEC_PROFILE_DEFAULT].row_factory = ordered_dict_factory - self.assertEqual( - list(self.session.execute(query)), - [OrderedDict((('', 'testval'), (' ', 'testval1')))] - ) + assert list(self.session.execute(query)) == [OrderedDict((('', 'testval'), (' ', 'testval1')))] def test_empty_columns_in_system_schema(self): queries = [ @@ -232,7 +220,7 @@ def test_empty_columns_in_system_schema(self): self.session = self.cluster.connect(wait_for_all_pools=True) table_metadata = self.cluster.metadata.keyspaces['testks'].tables['testtable'] - self.assertEqual(len(table_metadata.columns), 2) + assert len(table_metadata.columns) == 2 self.assertIn('', table_metadata.columns) self.assertIn(' ', table_metadata.columns) @@ -249,7 +237,4 @@ class TestModel(Model): empty = columns.Text(db_field='', primary_key=True) space = columns.Text(db_field=' ') - self.assertEqual( - [TestModel(empty='testval', space='testval1')], - list(TestModel.objects.only(['empty', 'space']).all()) - ) + assert [TestModel(empty='testval', space='testval1')] == list(TestModel.objects.only(['empty', 'space']).all()) diff --git a/tests/integration/simulacron/test_endpoint.py b/tests/integration/simulacron/test_endpoint.py index 9e2d91b6d3..d053da3596 100644 --- a/tests/integration/simulacron/test_endpoint.py +++ b/tests/integration/simulacron/test_endpoint.py @@ -76,12 +76,12 @@ class EndPointTests(SimulacronCluster): def test_default_endpoint(self): hosts = self.cluster.metadata.all_hosts() - self.assertEqual(len(hosts), 3) + assert len(hosts) == 3 for host in hosts: self.assertIsNotNone(host.endpoint) self.assertIsInstance(host.endpoint, DefaultEndPoint) - self.assertEqual(host.address, host.endpoint.address) - self.assertEqual(host.broadcast_rpc_address, host.endpoint.address) + assert host.address == host.endpoint.address + assert host.broadcast_rpc_address == host.endpoint.address self.assertIsInstance(self.cluster.control_connection._connection.endpoint, DefaultEndPoint) self.assertIsNotNone(self.cluster.control_connection._connection.endpoint) @@ -98,13 +98,13 @@ def test_custom_endpoint(self): cluster.connect(wait_for_all_pools=True) hosts = cluster.metadata.all_hosts() - self.assertEqual(len(hosts), 3) + assert len(hosts) == 3 for host in hosts: self.assertIsNotNone(host.endpoint) self.assertIsInstance(host.endpoint, AddressEndPoint) - self.assertEqual(str(host.endpoint), host.endpoint.address) - self.assertEqual(host.address, host.endpoint.address) - self.assertEqual(host.broadcast_rpc_address, host.endpoint.address) + assert str(host.endpoint) == host.endpoint.address + assert host.address == host.endpoint.address + assert host.broadcast_rpc_address == host.endpoint.address self.assertIsInstance(cluster.control_connection._connection.endpoint, AddressEndPoint) self.assertIsNotNone(cluster.control_connection._connection.endpoint) diff --git a/tests/integration/simulacron/test_policies.py b/tests/integration/simulacron/test_policies.py index 6d0d081889..e24ea8aef7 100644 --- a/tests/integration/simulacron/test_policies.py +++ b/tests/integration/simulacron/test_policies.py @@ -101,27 +101,27 @@ def test_speculative_execution(self): # This LBP should repeat hosts up to around 30 result = self.session.execute(statement, execution_profile='spec_ep_brr') - self.assertEqual(7, len(result.response_future.attempted_hosts)) + assert 7 == len(result.response_future.attempted_hosts) # This LBP should keep host list to 3 result = self.session.execute(statement, execution_profile='spec_ep_rr') - self.assertEqual(3, len(result.response_future.attempted_hosts)) + assert 3 == len(result.response_future.attempted_hosts) # Spec_execution policy should limit retries to 1 result = self.session.execute(statement, execution_profile='spec_ep_rr_lim') - self.assertEqual(2, len(result.response_future.attempted_hosts)) + assert 2 == len(result.response_future.attempted_hosts) # Spec_execution policy should not be used if the query is not idempotent result = self.session.execute(statement_non_idem, execution_profile='spec_ep_brr') - self.assertEqual(1, len(result.response_future.attempted_hosts)) + assert 1 == len(result.response_future.attempted_hosts) # Default policy with non_idem query result = self.session.execute(statement_non_idem, timeout=12) - self.assertEqual(1, len(result.response_future.attempted_hosts)) + assert 1 == len(result.response_future.attempted_hosts) # Should be able to run an idempotent query against default execution policy with no speculative_execution_policy result = self.session.execute(statement, timeout=12) - self.assertEqual(1, len(result.response_future.attempted_hosts)) + assert 1 == len(result.response_future.attempted_hosts) # Test timeout with spec_ex with self.assertRaises(OperationTimedOut): @@ -135,7 +135,7 @@ def test_speculative_execution(self): prepared_statement = self.session.prepare(prepared_query_to_prime) # non-idempotent result = self.session.execute(prepared_statement, ("0",), execution_profile='spec_ep_brr') - self.assertEqual(1, len(result.response_future.attempted_hosts)) + assert 1 == len(result.response_future.attempted_hosts) # idempotent prepared_statement.is_idempotent = True result = self.session.execute(prepared_statement, ("0",), execution_profile='spec_ep_brr') @@ -165,7 +165,7 @@ def test_speculative_and_timeout(self): self.assertIsInstance(response_future._final_exception, OperationTimedOut) # This is because 14 / 4 + 1 = 4 - self.assertEqual(len(response_future.attempted_hosts), 4) + assert len(response_future.attempted_hosts) == 4 def test_delay_can_be_0(self): """ @@ -199,11 +199,11 @@ def patched(*args, **kwargs): stmt = SimpleStatement(query_to_prime) stmt.is_idempotent = True results = session.execute(stmt, execution_profile="spec") - self.assertEqual(len(results.response_future.attempted_hosts), 3) + assert len(results.response_future.attempted_hosts) == 3 # send_request is called number_of_requests times for the speculative request # plus one for the call from the main thread. - self.assertEqual(next(counter), number_of_requests + 1) + assert next(counter) == number_of_requests + 1 class CustomRetryPolicy(RetryPolicy): @@ -337,7 +337,7 @@ def test_retry_policy_with_prepared(self): } prime_query(query_to_prime, then=then, rows=None, column_types=None) self.session.execute(query_to_prime) - self.assertEqual(next(counter_policy.write_timeout), 1) + assert next(counter_policy.write_timeout) == 1 counter_policy.reset_counters() query_to_prime_prepared = "SELECT * from simulacron_keyspace.simulacron_table WHERE key = :key" @@ -349,11 +349,11 @@ def test_retry_policy_with_prepared(self): bound_stm = prepared_stmt.bind({"key": "0"}) self.session.execute(bound_stm) - self.assertEqual(next(counter_policy.write_timeout), 1) + assert next(counter_policy.write_timeout) == 1 counter_policy.reset_counters() self.session.execute(prepared_stmt, ("0",)) - self.assertEqual(next(counter_policy.write_timeout), 1) + assert next(counter_policy.write_timeout) == 1 def test_setting_retry_policy_to_statement(self): """ @@ -385,13 +385,13 @@ def test_setting_retry_policy_to_statement(self): prepared_stmt = self.session.prepare(query_to_prime_prepared) prepared_stmt.retry_policy = counter_policy self.session.execute(prepared_stmt, ("0",)) - self.assertEqual(next(counter_policy.write_timeout), 1) + assert next(counter_policy.write_timeout) == 1 counter_policy.reset_counters() bound_stmt = prepared_stmt.bind({"key": "0"}) bound_stmt.retry_policy = counter_policy self.session.execute(bound_stmt) - self.assertEqual(next(counter_policy.write_timeout), 1) + assert next(counter_policy.write_timeout) == 1 def test_retry_policy_on_request_error(self): """ @@ -441,9 +441,9 @@ def test_retry_policy_on_request_error(self): with self.assertRaises(exc): rf.result() - self.assertEqual(len(rf.attempted_hosts), 1) # no retry + assert len(rf.attempted_hosts) == 1 # no retry - self.assertEqual(next(retry_policy.request_error), 4) + assert next(retry_policy.request_error) == 4 # Test that by default, retry on next host retry_policy = RetryPolicy() @@ -458,4 +458,4 @@ def test_retry_policy_on_request_error(self): with self.assertRaises(NoHostAvailable): rf.result() - self.assertEqual(len(rf.attempted_hosts), 3) # all 3 nodes failed + assert len(rf.attempted_hosts) == 3 # all 3 nodes failed diff --git a/tests/integration/standard/column_encryption/test_policies.py b/tests/integration/standard/column_encryption/test_policies.py index 36376d689b..7f0f593b13 100644 --- a/tests/integration/standard/column_encryption/test_policies.py +++ b/tests/integration/standard/column_encryption/test_policies.py @@ -59,14 +59,14 @@ def test_end_to_end_prepared(self): # A straight select from the database will now return the decrypted bits. We select both encrypted and unencrypted # values here to confirm that we don't interfere with regular processing of unencrypted vals. (encrypted,unencrypted) = session.execute("select encrypted, unencrypted from foo.bar where unencrypted = %s allow filtering", (expected,)).one() - self.assertEqual(expected, encrypted) - self.assertEqual(expected, unencrypted) + assert expected == encrypted + assert expected == unencrypted # Confirm the same behaviour from a subsequent prepared statement as well prepared = session.prepare("select encrypted, unencrypted from foo.bar where unencrypted = ? allow filtering") (encrypted,unencrypted) = session.execute(prepared, [expected]).one() - self.assertEqual(expected, encrypted) - self.assertEqual(expected, unencrypted) + assert expected == encrypted + assert expected == unencrypted def test_end_to_end_simple(self): @@ -87,14 +87,14 @@ def test_end_to_end_simple(self): # A straight select from the database will now return the decrypted bits. We select both encrypted and unencrypted # values here to confirm that we don't interfere with regular processing of unencrypted vals. (encrypted,unencrypted) = session.execute("select encrypted, unencrypted from foo.bar where unencrypted = %s allow filtering", (expected,)).one() - self.assertEqual(expected, encrypted) - self.assertEqual(expected, unencrypted) + assert expected == encrypted + assert expected == unencrypted # Confirm the same behaviour from a subsequent prepared statement as well prepared = session.prepare("select encrypted, unencrypted from foo.bar where unencrypted = ? allow filtering") (encrypted,unencrypted) = session.execute(prepared, [expected]).one() - self.assertEqual(expected, encrypted) - self.assertEqual(expected, unencrypted) + assert expected == encrypted + assert expected == unencrypted def test_end_to_end_different_cle_contexts_different_ivs(self): """ @@ -129,15 +129,15 @@ def test_end_to_end_different_cle_contexts_different_ivs(self): # that would entail not re-using any cached ciphers AES256ColumnEncryptionPolicy._build_cipher.cache_clear() cache_info = cl_policy1.cache_info() - self.assertEqual(cache_info.currsize, 0) + assert cache_info.currsize == 0 iv2 = os.urandom(AES256_BLOCK_SIZE_BYTES) (_, cl_policy2) = self._create_policy(key, iv=iv2) cluster2 = TestCluster(column_encryption_policy=cl_policy2) session2 = cluster2.connect() (encrypted,unencrypted) = session2.execute("select encrypted, unencrypted from foo.bar where unencrypted = %s allow filtering", (expected,)).one() - self.assertEqual(expected, encrypted) - self.assertEqual(expected, unencrypted) + assert expected == encrypted + assert expected == unencrypted def test_end_to_end_different_cle_contexts_different_policies(self): """ @@ -162,10 +162,10 @@ def test_end_to_end_different_cle_contexts_different_policies(self): # A straight select from the database will now return the decrypted bits. We select both encrypted and unencrypted # values here to confirm that we don't interfere with regular processing of unencrypted vals. (encrypted,unencrypted) = session2.execute("select encrypted, unencrypted from foo.bar where unencrypted = %s allow filtering", (expected,)).one() - self.assertEqual(cl_policy.encode_and_encrypt(col_desc, expected), encrypted) - self.assertEqual(expected, unencrypted) + assert cl_policy.encode_and_encrypt(col_desc, expected) == encrypted + assert expected == unencrypted # Confirm the same behaviour from a subsequent prepared statement as well prepared = session2.prepare("select encrypted, unencrypted from foo.bar where unencrypted = ? allow filtering") (encrypted,unencrypted) = session2.execute(prepared, [expected]).one() - self.assertEqual(cl_policy.encode_and_encrypt(col_desc, expected), encrypted) + assert cl_policy.encode_and_encrypt(col_desc, expected) == encrypted diff --git a/tests/integration/standard/test_authentication.py b/tests/integration/standard/test_authentication.py index 122df55a02..d87c3c26e1 100644 --- a/tests/integration/standard/test_authentication.py +++ b/tests/integration/standard/test_authentication.py @@ -109,7 +109,7 @@ def test_auth_connect(self): assert_quiescent_pool_state(self, cluster, wait=1) for pool in session.get_pools(): connection, _ = pool.borrow_connection(timeout=0) - self.assertEqual(connection.authenticator.server_authenticator_class, 'org.apache.cassandra.auth.PasswordAuthenticator') + assert connection.authenticator.server_authenticator_class == 'org.apache.cassandra.auth.PasswordAuthenticator' pool.return_connection(connection) finally: cluster.shutdown() @@ -184,7 +184,7 @@ def test_host_passthrough(self): provider = SaslAuthProvider(**sasl_kwargs) host = 'thehostname' authenticator = provider.new_authenticator(host) - self.assertEqual(authenticator.sasl.host, host) + assert authenticator.sasl.host == host def test_host_rejected(self): sasl_kwargs = {'host': 'something'} diff --git a/tests/integration/standard/test_authentication_misconfiguration.py b/tests/integration/standard/test_authentication_misconfiguration.py index 2b02664c3f..9ad4ad997d 100644 --- a/tests/integration/standard/test_authentication_misconfiguration.py +++ b/tests/integration/standard/test_authentication_misconfiguration.py @@ -40,7 +40,7 @@ def test_connect_no_auth_provider(self): cluster.connect() cluster.refresh_nodes() down_hosts = [host for host in cluster.metadata.all_hosts() if not host.is_up] - self.assertEqual(len(down_hosts), 1) + assert len(down_hosts) == 1 cluster.shutdown() @classmethod diff --git a/tests/integration/standard/test_client_warnings.py b/tests/integration/standard/test_client_warnings.py index ce5332a59f..f84bda456e 100644 --- a/tests/integration/standard/test_client_warnings.py +++ b/tests/integration/standard/test_client_warnings.py @@ -70,7 +70,7 @@ def test_warning_basic(self): """ future = self.session.execute_async(self.warn_batch) future.result() - self.assertEqual(len(future.warnings), 1) + assert len(future.warnings) == 1 self.assertRegex(future.warnings[0], 'Batch.*exceeding.*') def test_warning_with_trace(self): @@ -86,7 +86,7 @@ def test_warning_with_trace(self): """ future = self.session.execute_async(self.warn_batch, trace=True) future.result() - self.assertEqual(len(future.warnings), 1) + assert len(future.warnings) == 1 self.assertRegex(future.warnings[0], 'Batch.*exceeding.*') self.assertIsNotNone(future.get_query_trace()) @@ -106,7 +106,7 @@ def test_warning_with_custom_payload(self): payload = {'key': b'value'} future = self.session.execute_async(self.warn_batch, custom_payload=payload) future.result() - self.assertEqual(len(future.warnings), 1) + assert len(future.warnings) == 1 self.assertRegex(future.warnings[0], 'Batch.*exceeding.*') self.assertDictEqual(future.custom_payload, payload) @@ -126,7 +126,7 @@ def test_warning_with_trace_and_custom_payload(self): payload = {'key': b'value'} future = self.session.execute_async(self.warn_batch, trace=True, custom_payload=payload) future.result() - self.assertEqual(len(future.warnings), 1) + assert len(future.warnings) == 1 self.assertRegex(future.warnings[0], 'Batch.*exceeding.*') self.assertIsNotNone(future.get_query_trace()) self.assertDictEqual(future.custom_payload, payload) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 503b9304b3..52680459fe 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -122,11 +122,11 @@ def test_host_duplication(self): connect_timeout=1 ) cluster.connect(wait_for_all_pools=True) - self.assertEqual(len(cluster.metadata.all_hosts()), 3) + assert len(cluster.metadata.all_hosts()) == 3 cluster.shutdown() cluster = TestCluster(contact_points=["127.0.0.1", "localhost"], connect_timeout=1) cluster.connect(wait_for_all_pools=True) - self.assertEqual(len(cluster.metadata.all_hosts()), 3) + assert len(cluster.metadata.all_hosts()) == 3 cluster.shutdown() @local @@ -188,7 +188,7 @@ def test_basic(self): self.assertFalse(result) result = session.execute("SELECT * FROM clustertests.cf0") - self.assertEqual([('a', 'b', 'c')], result) + assert [('a', 'b', 'c')] == result execute_with_long_wait_retry(session, "DROP KEYSPACE clustertests") @@ -256,29 +256,29 @@ def test_protocol_negotiation(self): updated_cluster_version = cluster.protocol_version # Make sure the correct protocol was selected by default if CASSANDRA_VERSION >= Version('4.0-beta5'): - self.assertEqual(updated_protocol_version, cassandra.ProtocolVersion.V5) - self.assertEqual(updated_cluster_version, cassandra.ProtocolVersion.V5) + assert updated_protocol_version == cassandra.ProtocolVersion.V5 + assert updated_cluster_version == cassandra.ProtocolVersion.V5 elif CASSANDRA_VERSION >= Version('4.0-a'): - self.assertEqual(updated_protocol_version, cassandra.ProtocolVersion.V4) - self.assertEqual(updated_cluster_version, cassandra.ProtocolVersion.V4) + assert updated_protocol_version == cassandra.ProtocolVersion.V4 + assert updated_cluster_version == cassandra.ProtocolVersion.V4 elif CASSANDRA_VERSION >= Version('3.11'): - self.assertEqual(updated_protocol_version, cassandra.ProtocolVersion.V4) - self.assertEqual(updated_cluster_version, cassandra.ProtocolVersion.V4) + assert updated_protocol_version == cassandra.ProtocolVersion.V4 + assert updated_cluster_version == cassandra.ProtocolVersion.V4 elif CASSANDRA_VERSION >= Version('3.0'): - self.assertEqual(updated_protocol_version, cassandra.ProtocolVersion.V4) - self.assertEqual(updated_cluster_version, cassandra.ProtocolVersion.V4) + assert updated_protocol_version == cassandra.ProtocolVersion.V4 + assert updated_cluster_version == cassandra.ProtocolVersion.V4 elif CASSANDRA_VERSION >= Version('2.2'): - self.assertEqual(updated_protocol_version, 4) - self.assertEqual(updated_cluster_version, 4) + assert updated_protocol_version == 4 + assert updated_cluster_version == 4 elif CASSANDRA_VERSION >= Version('2.1'): - self.assertEqual(updated_protocol_version, 3) - self.assertEqual(updated_cluster_version, 3) + assert updated_protocol_version == 3 + assert updated_cluster_version == 3 elif CASSANDRA_VERSION >= Version('2.0'): - self.assertEqual(updated_protocol_version, 2) - self.assertEqual(updated_cluster_version, 2) + assert updated_protocol_version == 2 + assert updated_cluster_version == 2 else: - self.assertEqual(updated_protocol_version, 1) - self.assertEqual(updated_cluster_version, 1) + assert updated_protocol_version == 1 + assert updated_cluster_version == 1 cluster.shutdown() @@ -334,12 +334,12 @@ def test_connect_on_keyspace(self): self.assertFalse(result) result = session.execute("SELECT * FROM test1rf.test") - self.assertEqual([(8889, 8889)], result, "Rows in ResultSet are {0}".format(result.current_rows)) + assert [(8889, 8889)] == result, "Rows in ResultSet are {0}".format(result.current_rows) # test_connect_on_keyspace session2 = cluster.connect('test1rf') result2 = session2.execute("SELECT * FROM test") - self.assertEqual(result, result2) + assert result == result2 cluster.shutdown() def test_set_keyspace_twice(self): @@ -410,7 +410,7 @@ def test_refresh_schema(self): # full schema refresh, with wait cluster.refresh_schema_metadata() self.assertIsNot(original_meta, cluster.metadata.keyspaces) - self.assertEqual(original_meta, cluster.metadata.keyspaces) + assert original_meta == cluster.metadata.keyspaces cluster.shutdown() @@ -427,7 +427,7 @@ def test_refresh_schema_keyspace(self): self.assertIs(original_meta, current_meta) current_system_meta = current_meta['system'] self.assertIsNot(original_system_meta, current_system_meta) - self.assertEqual(original_system_meta.as_cql_query(), current_system_meta.as_cql_query()) + assert original_system_meta.as_cql_query() == current_system_meta.as_cql_query() cluster.shutdown() def test_refresh_schema_table(self): @@ -446,7 +446,7 @@ def test_refresh_schema_table(self): self.assertIs(original_meta, current_meta) self.assertIs(original_system_meta, current_system_meta) self.assertIsNot(original_system_schema_meta, current_system_schema_meta) - self.assertEqual(original_system_schema_meta.as_cql_query(), current_system_schema_meta.as_cql_query()) + assert original_system_schema_meta.as_cql_query() == current_system_schema_meta.as_cql_query() cluster.shutdown() def test_refresh_schema_type(self): @@ -474,9 +474,9 @@ def test_refresh_schema_type(self): current_test1rf_meta = current_meta[keyspace_name] current_type_meta = current_test1rf_meta.user_types[type_name] self.assertIs(original_meta, current_meta) - self.assertEqual(original_test1rf_meta.export_as_string(), current_test1rf_meta.export_as_string()) + assert original_test1rf_meta.export_as_string() == current_test1rf_meta.export_as_string() self.assertIsNot(original_type_meta, current_type_meta) - self.assertEqual(original_type_meta.as_cql_query(), current_type_meta.as_cql_query()) + assert original_type_meta.as_cql_query() == current_type_meta.as_cql_query() cluster.shutdown() @local @@ -515,7 +515,7 @@ def patched_wait_for_responses(*args, **kwargs): end_time = time.time() self.assertLess(end_time - start_time, agreement_timeout) self.assertIsNot(original_meta, c.metadata.keyspaces) - self.assertEqual(original_meta, c.metadata.keyspaces) + assert original_meta == c.metadata.keyspaces c.shutdown() @@ -535,7 +535,7 @@ def patched_wait_for_responses(*args, **kwargs): end_time = time.time() self.assertLess(end_time - start_time, refresh_threshold) self.assertIsNot(original_meta, c.metadata.keyspaces) - self.assertEqual(original_meta, c.metadata.keyspaces) + assert original_meta == c.metadata.keyspaces # refresh wait overrides cluster value original_meta = c.metadata.keyspaces @@ -713,10 +713,7 @@ def _warning_are_issued_when_auth(self, auth_provider): # Three conenctions to nodes plus the control connection auth_warning = mock_handler.get_message_count('warning', "An authentication challenge was not sent") self.assertGreaterEqual(auth_warning, 4) - self.assertEqual( - auth_warning, - mock_handler.get_message_count("debug", "Got ReadyMessage on new connection") - ) + assert auth_warning == mock_handler.get_message_count("debug", "Got ReadyMessage on new connection") def test_idle_heartbeat(self): interval = 2 @@ -763,14 +760,14 @@ def test_idle_heartbeat(self): # holders include session pools and cc holders = cluster.get_connection_holders() self.assertIn(cluster.control_connection, holders) - self.assertEqual(len(holders), len(cluster.metadata.all_hosts()) + 1) # hosts pools, 1 for cc + assert len(holders) == len(cluster.metadata.all_hosts()) + 1 # hosts pools, 1 for cc # include additional sessions session2 = cluster.connect(wait_for_all_pools=True) holders = cluster.get_connection_holders() self.assertIn(cluster.control_connection, holders) - self.assertEqual(len(holders), 2 * len(cluster.metadata.all_hosts()) + 1) # 2 sessions' hosts pools, 1 for cc + assert len(holders) == 2 * len(cluster.metadata.all_hosts()) + 1 # 2 sessions' hosts pools, 1 for cc cluster._idle_heartbeat.stop() cluster._idle_heartbeat.join() @@ -784,7 +781,7 @@ def test_idle_heartbeat_disabled(self): # heartbeat disabled with '0' cluster = TestCluster(idle_heartbeat_interval=0) - self.assertEqual(cluster.idle_heartbeat_interval, 0) + assert cluster.idle_heartbeat_interval == 0 session = cluster.connect() # let two heatbeat intervals pass (first one had startup messages in it) @@ -852,7 +849,7 @@ def test_profile_load_balancing(self): for _ in expected_hosts: rs = session.execute(query) queried_hosts.add(rs.response_future._current_host) - self.assertEqual(queried_hosts, expected_hosts) + assert queried_hosts == expected_hosts # by name we should only hit the one expected_hosts = set(h for h in cluster.metadata.all_hosts() if h.address == CASSANDRA_IP) @@ -860,7 +857,7 @@ def test_profile_load_balancing(self): for _ in cluster.metadata.all_hosts(): rs = session.execute(query, execution_profile='node1') queried_hosts.add(rs.response_future._current_host) - self.assertEqual(queried_hosts, expected_hosts) + assert queried_hosts == expected_hosts # use a copied instance and override the row factory # assert last returned value can be accessed as a namedtuple so we can prove something different @@ -874,7 +871,7 @@ def test_profile_load_balancing(self): for _ in cluster.metadata.all_hosts(): rs = session.execute(query, execution_profile=tmp_profile) queried_hosts.add(rs.response_future._current_host) - self.assertEqual(queried_hosts, expected_hosts) + assert queried_hosts == expected_hosts tuple_row = rs.one() self.assertIsInstance(tuple_row, tuple) with self.assertRaises(AttributeError): @@ -887,9 +884,7 @@ def test_setting_lbp_legacy(self): cluster = TestCluster() self.addCleanup(cluster.shutdown) cluster.load_balancing_policy = RoundRobinPolicy() - self.assertEqual( - list(cluster.load_balancing_policy.make_query_plan()), [] - ) + assert list(cluster.load_balancing_policy.make_query_plan()) == [] cluster.connect() self.assertNotEqual( list(cluster.load_balancing_policy.make_query_plan()), [] @@ -925,7 +920,7 @@ def test_profile_lb_swap(self): rs = session.execute(query, execution_profile='rr2') rr2_queried_hosts.add(rs.response_future._current_host) - self.assertEqual(rr2_queried_hosts, rr1_queried_hosts) + assert rr2_queried_hosts == rr1_queried_hosts def test_ta_lbp(self): """ @@ -1020,7 +1015,7 @@ def test_profile_pool_management(self): pools = session.get_pool_state() # there are more hosts, but we connected to the ones in the lbp aggregate self.assertGreater(len(cluster.metadata.all_hosts()), 2) - self.assertEqual(set(h.address for h in pools), set(('127.0.0.1', '127.0.0.2'))) + assert set(h.address for h in pools) == set(('127.0.0.1', '127.0.0.2')) # dynamically update pools on add node3 = ExecutionProfile( @@ -1030,7 +1025,7 @@ def test_profile_pool_management(self): ) cluster.add_execution_profile('node3', node3) pools = session.get_pool_state() - self.assertEqual(set(h.address for h in pools), set(('127.0.0.1', '127.0.0.2', '127.0.0.3'))) + assert set(h.address for h in pools) == set(('127.0.0.1', '127.0.0.2', '127.0.0.3')) @local def test_add_profile_timeout(self): @@ -1054,7 +1049,7 @@ def test_add_profile_timeout(self): session = cluster.connect(wait_for_all_pools=True) pools = session.get_pool_state() self.assertGreater(len(cluster.metadata.all_hosts()), 2) - self.assertEqual(set(h.address for h in pools), set(('127.0.0.1',))) + assert set(h.address for h in pools) == set(('127.0.0.1',)) node2 = ExecutionProfile( load_balancing_policy=HostFilterPolicy( @@ -1112,7 +1107,7 @@ def test_execute_query_timeout(self): # default is passed down default_profile = cluster.profile_manager.profiles[EXEC_PROFILE_DEFAULT] rs = session.execute(query) - self.assertEqual(rs.response_future.timeout, default_profile.request_timeout) + assert rs.response_future.timeout == default_profile.request_timeout # tiny timeout times out as expected tmp_profile = copy(default_profile) @@ -1227,31 +1222,27 @@ def test_compact_option(self): "({i}, 'a{i}{i}', {i}{i}, {i}{i}, textAsBlob('b{i}{i}'))".format(i=i)) nc_results = nc_session.execute("SELECT * FROM compact_table") - self.assertEqual( - set(nc_results.current_rows), - {(1, u'a1', 11, 11, 'b1'), - (1, u'a11', 11, 11, 'b11'), - (2, u'a2', 22, 22, 'b2'), - (2, u'a22', 22, 22, 'b22'), - (3, u'a3', 33, 33, 'b3'), - (3, u'a33', 33, 33, 'b33'), - (4, u'a4', 44, 44, 'b4'), - (4, u'a44', 44, 44, 'b44')}) + assert set(nc_results.current_rows) == {(1, u'a1', 11, 11, 'b1'), + (1, u'a11', 11, 11, 'b11'), + (2, u'a2', 22, 22, 'b2'), + (2, u'a22', 22, 22, 'b22'), + (3, u'a3', 33, 33, 'b3'), + (3, u'a33', 33, 33, 'b33'), + (4, u'a4', 44, 44, 'b4'), + (4, u'a44', 44, 44, 'b44')} results = session.execute("SELECT * FROM compact_table") - self.assertEqual( - set(results.current_rows), - {(1, 11, 11), - (2, 22, 22), - (3, 33, 33), - (4, 44, 44)}) + assert set(results.current_rows) == {(1, 11, 11), + (2, 22, 22), + (3, 33, 33), + (4, 44, 44)} def _assert_replica_queried(self, trace, only_replicas=True): queried_hosts = set() for row in trace.events: queried_hosts.add(row.source) if only_replicas: - self.assertEqual(len(queried_hosts), 1, "The hosts queried where {}".format(queried_hosts)) + assert len(queried_hosts) == 1, "The hosts queried where {}".format(queried_hosts) else: self.assertGreater(len(queried_hosts), 1, "The host queried was {}".format(queried_hosts)) return queried_hosts @@ -1293,7 +1284,7 @@ def test_address_translator_basic(self): lh_ad = LocalHostAdressTranslator({'127.0.0.1': '127.0.0.1', '127.0.0.2': '127.0.0.1', '127.0.0.3': '127.0.0.1'}) c = TestCluster(address_translator=lh_ad) c.connect() - self.assertEqual(len(c.metadata.all_hosts()), 1) + assert len(c.metadata.all_hosts()) == 1 c.shutdown() def test_address_translator_with_mixed_nodes(self): @@ -1314,7 +1305,7 @@ def test_address_translator_with_mixed_nodes(self): c = TestCluster(address_translator=lh_ad) c.connect() for host in c.metadata.all_hosts(): - self.assertEqual(adder_map.get(host.address), host.broadcast_address) + assert adder_map.get(host.address) == host.broadcast_address c.shutdown() @local @@ -1475,7 +1466,7 @@ def test_prepare_on_ignored_hosts(self): # the length of mock_calls will vary, but all should use the unignored # address for c in cluster.connection_factory.mock_calls: - self.assertEqual(unignored_address, c.args[0].address) + assert unignored_address == c.args[0].address cluster.shutdown() @@ -1515,7 +1506,7 @@ def test_valid_protocol_version_beta_options_connect(self): """ cluster = Cluster(protocol_version=cassandra.ProtocolVersion.V6, allow_beta_protocol_version=True) session = cluster.connect() - self.assertEqual(cluster.protocol_version, cassandra.ProtocolVersion.V6) + assert cluster.protocol_version == cassandra.ProtocolVersion.V6 self.assertTrue(session.execute("select release_version from system.local").one()) cluster.shutdown() diff --git a/tests/integration/standard/test_concurrent.py b/tests/integration/standard/test_concurrent.py index ba891b4bd0..031065df63 100644 --- a/tests/integration/standard/test_concurrent.py +++ b/tests/integration/standard/test_concurrent.py @@ -90,7 +90,7 @@ def execute_concurrent_base(self, test_fn, validate_fn, zip_args=True): results = \ test_fn(self.session, list(zip(statements, parameters))) if zip_args else \ test_fn(self.session, statement, parameters) - self.assertEqual(num_statements, len(results)) + assert num_statements == len(results) for success, result in results: self.assertTrue(success) self.assertFalse(result) @@ -108,12 +108,12 @@ def execute_concurrent_base(self, test_fn, validate_fn, zip_args=True): validate_fn(num_statements, results) def execute_concurrent_valiate_tuple(self, num_statements, results): - self.assertEqual(num_statements, len(results)) - self.assertEqual([(True, [(i,)]) for i in range(num_statements)], results) + assert num_statements == len(results) + assert [(True, [(i,)]) for i in range(num_statements)] == results def execute_concurrent_valiate_dict(self, num_statements, results): - self.assertEqual(num_statements, len(results)) - self.assertEqual([(True, [{"v":i}]) for i in range(num_statements)], results) + assert num_statements == len(results) + assert [(True, [{"v":i}]) for i in range(num_statements)] == results def test_execute_concurrent(self): self.execute_concurrent_base(self.execute_concurrent_helper, \ @@ -174,7 +174,7 @@ def test_execute_concurrent_with_args_generator(self): for i in range(num_statements): result = next(results) - self.assertEqual((True, [(i,)]), result) + assert (True, [(i,)]) == result self.assertRaises(StopIteration, next, results) def test_execute_concurrent_paged_result(self): @@ -190,7 +190,7 @@ def test_execute_concurrent_paged_result(self): parameters = [(i, i) for i in range(num_statements)] results = self.execute_concurrent_args_helper(self.session, statement, parameters) - self.assertEqual(num_statements, len(results)) + assert num_statements == len(results) for success, result in results: self.assertTrue(success) self.assertFalse(result) @@ -202,11 +202,11 @@ def test_execute_concurrent_paged_result(self): fetch_size=int(num_statements / 2)) results = self.execute_concurrent_args_helper(self.session, statement, [(num_statements,)]) - self.assertEqual(1, len(results)) + assert 1 == len(results) self.assertTrue(results[0][0]) result = results[0][1] self.assertTrue(result.has_more_pages) - self.assertEqual(num_statements, sum(1 for _ in result)) + assert num_statements == sum(1 for _ in result) def test_execute_concurrent_paged_result_generator(self): """ @@ -233,7 +233,7 @@ def test_execute_concurrent_paged_result_generator(self): parameters = [(i, i) for i in range(num_statements)] results = self.execute_concurrent_args_helper(self.session, statement, parameters, results_generator=True) - self.assertEqual(num_statements, sum(1 for _ in results)) + assert num_statements == sum(1 for _ in results) # read statement = SimpleStatement( @@ -250,7 +250,7 @@ def test_execute_concurrent_paged_result_generator(self): for _ in paged_result: found_results += 1 - self.assertEqual(found_results, num_statements) + assert found_results == num_statements def test_first_failure(self): statements = cycle(("INSERT INTO test3rf.test (k, v) VALUES (%s, %s)", )) diff --git a/tests/integration/standard/test_connection.py b/tests/integration/standard/test_connection.py index 4a5c23d6bf..7b34c3dd72 100644 --- a/tests/integration/standard/test_connection.py +++ b/tests/integration/standard/test_connection.py @@ -420,7 +420,7 @@ class C2(self.klass): self.addCleanup(clusterC1.shutdown) self.addCleanup(clusterC2.shutdown) - self.assertEqual(len(get_eventloop_threads(self.event_loop_name)), 1) + assert len(get_eventloop_threads(self.event_loop_name)) == 1 def get_eventloop_threads(name): diff --git a/tests/integration/standard/test_control_connection.py b/tests/integration/standard/test_control_connection.py index ea434c37c5..f2ab5a9b18 100644 --- a/tests/integration/standard/test_control_connection.py +++ b/tests/integration/standard/test_control_connection.py @@ -72,7 +72,7 @@ def test_drop_keyspace(self): cc_id_pre_drop = id(self.cluster.control_connection._connection) self.session.execute("DROP KEYSPACE keyspacetodrop") cc_id_post_drop = id(self.cluster.control_connection._connection) - self.assertEqual(cc_id_post_drop, cc_id_pre_drop) + assert cc_id_post_drop == cc_id_pre_drop def test_get_control_connection_host(self): """ @@ -86,14 +86,14 @@ def test_get_control_connection_host(self): """ host = self.cluster.get_control_connection_host() - self.assertEqual(host, None) + assert host == None self.session = self.cluster.connect() cc_host = self.cluster.control_connection._connection.host host = self.cluster.get_control_connection_host() - self.assertEqual(host.address, cc_host) - self.assertEqual(host.is_up, True) + assert host.address == cc_host + assert host.is_up == True # reconnect and make sure that the new host is reflected correctly self.cluster.control_connection._reconnect() @@ -113,17 +113,17 @@ def test_control_connection_port_discovery(self): self.cluster = TestCluster() host = self.cluster.get_control_connection_host() - self.assertEqual(host, None) + assert host == None self.session = self.cluster.connect() cc_endpoint = self.cluster.control_connection._connection.endpoint host = self.cluster.get_control_connection_host() - self.assertEqual(host.endpoint, cc_endpoint) - self.assertEqual(host.is_up, True) + assert host.endpoint == cc_endpoint + assert host.is_up == True hosts = self.cluster.metadata.all_hosts() - self.assertEqual(3, len(hosts)) + assert 3 == len(hosts) for host in hosts: - self.assertEqual(9042, host.broadcast_rpc_port) - self.assertEqual(7000, host.broadcast_port) + assert 9042 == host.broadcast_rpc_port + assert 7000 == host.broadcast_port diff --git a/tests/integration/standard/test_custom_payload.py b/tests/integration/standard/test_custom_payload.py index 92372972c6..b77d4e336d 100644 --- a/tests/integration/standard/test_custom_payload.py +++ b/tests/integration/standard/test_custom_payload.py @@ -164,4 +164,4 @@ def execute_async_validate_custom_payload(self, statement, custom_payload): response_future = self.session.execute_async(statement, custom_payload=custom_payload) response_future.result() returned_custom_payload = response_future.custom_payload - self.assertEqual(custom_payload, returned_custom_payload) + assert custom_payload == returned_custom_payload diff --git a/tests/integration/standard/test_custom_protocol_handler.py b/tests/integration/standard/test_custom_protocol_handler.py index 26d3f5fe35..a1c1e61d83 100644 --- a/tests/integration/standard/test_custom_protocol_handler.py +++ b/tests/integration/standard/test_custom_protocol_handler.py @@ -71,20 +71,20 @@ def test_custom_raw_uuid_row_results(self): result = session.execute("SELECT schema_version FROM system.local WHERE key='local'") uuid_type = result.one()[0] - self.assertEqual(type(uuid_type), uuid.UUID) + assert type(uuid_type) == uuid.UUID # use our custom protocol handlder session.client_protocol_handler = CustomTestRawRowType result_set = session.execute("SELECT schema_version FROM system.local WHERE key='local'") raw_value = result_set.one()[0] self.assertTrue(isinstance(raw_value, bytes)) - self.assertEqual(len(raw_value), 16) + assert len(raw_value) == 16 # Ensure that we get normal uuid back when we re-connect session.client_protocol_handler = ProtocolHandler result_set = session.execute("SELECT schema_version FROM system.local WHERE key='local'") uuid_type = result_set.one()[0] - self.assertEqual(type(uuid_type), uuid.UUID) + assert type(uuid_type) == uuid.UUID cluster.shutdown() def test_custom_raw_row_results_all_types(self): @@ -115,9 +115,9 @@ def test_custom_raw_row_results_all_types(self): params = get_all_primitive_params(0) results = session.execute("SELECT {0} FROM alltypes WHERE primkey=0".format(columns_string)).one() for expected, actual in zip(params, results): - self.assertEqual(actual, expected) + assert actual == expected # Ensure we have covered the various primitive types - self.assertEqual(len(CustomResultMessageTracked.checked_rev_row_set), len(PRIMITIVE_DATATYPES)-1) + assert len(CustomResultMessageTracked.checked_rev_row_set) == len(PRIMITIVE_DATATYPES)-1 cluster.shutdown() @unittest.expectedFailure @@ -219,7 +219,7 @@ def _protocol_divergence_fail_by_flag_uses_int(self, version, uses_int_query_fla response = future.result() # This means the flag are not handled as they are meant by the server if uses_int=False - self.assertEqual(response.has_more_pages, uses_int_query_flag) + assert response.has_more_pages == uses_int_query_flag execute_with_long_wait_retry(session, SimpleStatement("TRUNCATE test3rf.test")) cluster.shutdown() diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index 9e85edb914..0f78a45dd1 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -74,7 +74,7 @@ def test_cython_lazy_results_paged(self): results = session.execute("SELECT * FROM test_table") self.assertTrue(results.has_more_pages) - self.assertEqual(verify_iterator_data(self.assertEqual, results), self.N_ITEMS) # make sure we see all rows + assert verify_iterator_data(self.assertEqual, results) == self.N_ITEMS # make sure we see all rows cluster.shutdown() @@ -119,9 +119,9 @@ def test_numpy_results_paged(self): else: # we get one extra item out of this iteration because of the way NumpyParser returns results # The last page is returned as a dict with zero-length arrays - self.assertEqual(len(arr), 0) - self.assertEqual(self._verify_numpy_page(page), len(arr)) - self.assertEqual(count, expected_pages + 1) # see note about extra 'page' above + assert len(arr) == 0 + assert self._verify_numpy_page(page) == len(arr) + assert count == expected_pages + 1 # see note about extra 'page' above cluster.shutdown() @@ -161,11 +161,11 @@ def match_dtype(self, datatype, dtype): elif datatype == 'double': self.match_dtype_props(dtype, 'f', 8) else: - self.assertEqual(dtype.kind, 'O', msg=(dtype, datatype)) + assert dtype.kind == 'O', (dtype, datatype) def match_dtype_props(self, dtype, kind, size, signed=None): - self.assertEqual(dtype.kind, kind, msg=dtype) - self.assertEqual(dtype.itemsize, size, msg=dtype) + assert dtype.kind == kind, dtype + assert dtype.itemsize == size, dtype def arrays_to_list_of_tuples(arrays, colnames): diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 73bc40878a..96ed4b33b6 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -154,11 +154,11 @@ def test_schema_metadata_disable(self): # Validate metadata is missing where appropriate no_schema = TestCluster(schema_metadata_enabled=False) no_schema_session = no_schema.connect() - self.assertEqual(len(no_schema.metadata.keyspaces), 0) - self.assertEqual(no_schema.metadata.export_schema_as_string(), '') + assert len(no_schema.metadata.keyspaces) == 0 + assert no_schema.metadata.export_schema_as_string() == '' no_token = TestCluster(token_metadata_enabled=False) no_token_session = no_token.connect() - self.assertEqual(len(no_token.metadata.token_map.token_to_host_owner), 0) + assert len(no_token.metadata.token_map.token_to_host_owner) == 0 # Do a simple query to ensure queries are working query = "SELECT * FROM system.local WHERE key='local'" @@ -202,7 +202,7 @@ def make_create_statement(self, partition_cols, clustering_cols=None, other_cols def check_create_statement(self, tablemeta, original): recreate = tablemeta.as_cql_query(formatted=False) - self.assertEqual(original, recreate[:len(original)]) + assert original == recreate[:len(original)] execute_until_pass(self.session, "DROP TABLE {0}.{1}".format(self.keyspace_name, self.function_table_name)) execute_until_pass(self.session, recreate) @@ -226,20 +226,20 @@ def test_basic_table_meta_properties(self): self.assertTrue(self.keyspace_name in meta.keyspaces) ksmeta = meta.keyspaces[self.keyspace_name] - self.assertEqual(ksmeta.name, self.keyspace_name) + assert ksmeta.name == self.keyspace_name self.assertTrue(ksmeta.durable_writes) - self.assertEqual(ksmeta.replication_strategy.name, 'SimpleStrategy') - self.assertEqual(ksmeta.replication_strategy.replication_factor, 1) + assert ksmeta.replication_strategy.name == 'SimpleStrategy' + assert ksmeta.replication_strategy.replication_factor == 1 self.assertTrue(self.function_table_name in ksmeta.tables) tablemeta = ksmeta.tables[self.function_table_name] - self.assertEqual(tablemeta.keyspace_name, ksmeta.name) - self.assertEqual(tablemeta.name, self.function_table_name) - self.assertEqual(tablemeta.name, self.function_table_name) + assert tablemeta.keyspace_name == ksmeta.name + assert tablemeta.name == self.function_table_name + assert tablemeta.name == self.function_table_name - self.assertEqual([u'a'], [c.name for c in tablemeta.partition_key]) - self.assertEqual([], tablemeta.clustering_key) - self.assertEqual([u'a', u'b', u'c'], sorted(tablemeta.columns.keys())) + assert [u'a'] == [c.name for c in tablemeta.partition_key] + assert [] == tablemeta.clustering_key + assert [u'a', u'b', u'c'] == sorted(tablemeta.columns.keys()) cc = self.cluster.control_connection._connection parser = get_schema_parser( @@ -261,9 +261,9 @@ def test_compound_primary_keys(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - self.assertEqual([u'a'], [c.name for c in tablemeta.partition_key]) - self.assertEqual([u'b'], [c.name for c in tablemeta.clustering_key]) - self.assertEqual([u'a', u'b', u'c'], sorted(tablemeta.columns.keys())) + assert [u'a'] == [c.name for c in tablemeta.partition_key] + assert [u'b'] == [c.name for c in tablemeta.clustering_key] + assert [u'a', u'b', u'c'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -273,9 +273,9 @@ def test_compound_primary_keys_protected(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - self.assertEqual([u'Aa'], [c.name for c in tablemeta.partition_key]) - self.assertEqual([u'Bb'], [c.name for c in tablemeta.clustering_key]) - self.assertEqual([u'Aa', u'Bb', u'Cc'], sorted(tablemeta.columns.keys())) + assert [u'Aa'] == [c.name for c in tablemeta.partition_key] + assert [u'Bb'] == [c.name for c in tablemeta.clustering_key] + assert [u'Aa', u'Bb', u'Cc'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -285,11 +285,9 @@ def test_compound_primary_keys_more_columns(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - self.assertEqual([u'a'], [c.name for c in tablemeta.partition_key]) - self.assertEqual([u'b', u'c'], [c.name for c in tablemeta.clustering_key]) - self.assertEqual( - [u'a', u'b', u'c', u'd', u'e', u'f'], - sorted(tablemeta.columns.keys())) + assert [u'a'] == [c.name for c in tablemeta.partition_key] + assert [u'b', u'c'] == [c.name for c in tablemeta.clustering_key] + assert [u'a', u'b', u'c', u'd', u'e', u'f'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -298,9 +296,9 @@ def test_composite_primary_key(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - self.assertEqual([u'a', u'b'], [c.name for c in tablemeta.partition_key]) - self.assertEqual([], tablemeta.clustering_key) - self.assertEqual([u'a', u'b', u'c'], sorted(tablemeta.columns.keys())) + assert [u'a', u'b'] == [c.name for c in tablemeta.partition_key] + assert [] == tablemeta.clustering_key + assert [u'a', u'b', u'c'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -310,9 +308,9 @@ def test_composite_in_compound_primary_key(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - self.assertEqual([u'a', u'b'], [c.name for c in tablemeta.partition_key]) - self.assertEqual([u'c'], [c.name for c in tablemeta.clustering_key]) - self.assertEqual([u'a', u'b', u'c', u'd', u'e'], sorted(tablemeta.columns.keys())) + assert [u'a', u'b'] == [c.name for c in tablemeta.partition_key] + assert [u'c'] == [c.name for c in tablemeta.clustering_key] + assert [u'a', u'b', u'c', u'd', u'e'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -322,9 +320,9 @@ def test_compound_primary_keys_compact(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - self.assertEqual([u'a'], [c.name for c in tablemeta.partition_key]) - self.assertEqual([u'b'], [c.name for c in tablemeta.clustering_key]) - self.assertEqual([u'a', u'b', u'c'], sorted(tablemeta.columns.keys())) + assert [u'a'] == [c.name for c in tablemeta.partition_key] + assert [u'b'] == [c.name for c in tablemeta.clustering_key] + assert [u'a', u'b', u'c'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -356,9 +354,9 @@ def test_compound_primary_keys_more_columns_compact(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - self.assertEqual([u'a'], [c.name for c in tablemeta.partition_key]) - self.assertEqual([u'b', u'c'], [c.name for c in tablemeta.clustering_key]) - self.assertEqual([u'a', u'b', u'c', u'd'], sorted(tablemeta.columns.keys())) + assert [u'a'] == [c.name for c in tablemeta.partition_key] + assert [u'b', u'c'] == [c.name for c in tablemeta.clustering_key] + assert [u'a', u'b', u'c', u'd'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -367,9 +365,9 @@ def test_composite_primary_key_compact(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - self.assertEqual([u'a', u'b'], [c.name for c in tablemeta.partition_key]) - self.assertEqual([], tablemeta.clustering_key) - self.assertEqual([u'a', u'b', u'c'], sorted(tablemeta.columns.keys())) + assert [u'a', u'b'] == [c.name for c in tablemeta.partition_key] + assert [] == tablemeta.clustering_key + assert [u'a', u'b', u'c'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -379,9 +377,9 @@ def test_composite_in_compound_primary_key_compact(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - self.assertEqual([u'a', u'b'], [c.name for c in tablemeta.partition_key]) - self.assertEqual([u'c'], [c.name for c in tablemeta.clustering_key]) - self.assertEqual([u'a', u'b', u'c', u'd'], sorted(tablemeta.columns.keys())) + assert [u'a', u'b'] == [c.name for c in tablemeta.partition_key] + assert [u'c'] == [c.name for c in tablemeta.clustering_key] + assert [u'a', u'b', u'c', u'd'] == sorted(tablemeta.columns.keys()) self.check_create_statement(tablemeta, create_statement) @@ -394,9 +392,9 @@ def test_cql_compatibility(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() - self.assertEqual([u'a'], [c.name for c in tablemeta.partition_key]) - self.assertEqual([], tablemeta.clustering_key) - self.assertEqual([u'a', u'b', u'c', u'd'], sorted(tablemeta.columns.keys())) + assert [u'a'] == [c.name for c in tablemeta.partition_key] + assert [] == tablemeta.clustering_key + assert [u'a', u'b', u'c', u'd'] == sorted(tablemeta.columns.keys()) self.assertTrue(tablemeta.is_cql_compatible) @@ -498,7 +496,7 @@ def test_indexes(self): statements = tablemeta.export_as_string().strip() statements = [s.strip() for s in statements.split(';')] statements = list(filter(bool, statements)) - self.assertEqual(3, len(statements)) + assert 3 == len(statements) self.assertIn(d_index, statements) self.assertIn(e_index, statements) @@ -626,7 +624,7 @@ def test_refresh_schema_metadata(self): if PROTOCOL_VERSION >= 3: # UDT metadata modification self.session.execute("CREATE TYPE {0}.user (age int, name text)".format(self.keyspace_name)) - self.assertEqual(cluster2.metadata.keyspaces[self.keyspace_name].user_types, {}) + assert cluster2.metadata.keyspaces[self.keyspace_name].user_types == {} cluster2.refresh_schema_metadata() self.assertIn("user", cluster2.metadata.keyspaces[self.keyspace_name].user_types) @@ -637,7 +635,7 @@ def test_refresh_schema_metadata(self): RETURNS int LANGUAGE java AS 'return key+val;';""".format(self.keyspace_name)) - self.assertEqual(cluster2.metadata.keyspaces[self.keyspace_name].functions, {}) + assert cluster2.metadata.keyspaces[self.keyspace_name].functions == {} cluster2.refresh_schema_metadata() self.assertIn("sum_int(int,int)", cluster2.metadata.keyspaces[self.keyspace_name].functions) @@ -648,7 +646,7 @@ def test_refresh_schema_metadata(self): INITCOND 0""" .format(self.keyspace_name)) - self.assertEqual(cluster2.metadata.keyspaces[self.keyspace_name].aggregates, {}) + assert cluster2.metadata.keyspaces[self.keyspace_name].aggregates == {} cluster2.refresh_schema_metadata() self.assertIn("sum_agg(int)", cluster2.metadata.keyspaces[self.keyspace_name].aggregates) @@ -765,7 +763,7 @@ def test_refresh_metadata_for_mv(self): current_meta = self.cluster.metadata.keyspaces[self.keyspace_name].views['mv1'] self.assertIsNot(current_meta, original_meta) self.assertIsNot(original_meta, self.session.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views['mv1']) - self.assertEqual(original_meta.as_cql_query(), current_meta.as_cql_query()) + assert original_meta.as_cql_query() == current_meta.as_cql_query() cluster3 = TestCluster(schema_event_refresh_window=-1) cluster3.connect() @@ -805,9 +803,9 @@ def test_refresh_user_type_metadata(self): cluster2 = TestCluster(schema_event_refresh_window=-1) cluster2.connect() - self.assertEqual(cluster2.metadata.keyspaces[self.keyspace_name].user_types, {}) + assert cluster2.metadata.keyspaces[self.keyspace_name].user_types == {} self.session.execute("CREATE TYPE {0}.user (age int, name text)".format(self.keyspace_name)) - self.assertEqual(cluster2.metadata.keyspaces[self.keyspace_name].user_types, {}) + assert cluster2.metadata.keyspaces[self.keyspace_name].user_types == {} cluster2.refresh_user_type_metadata(self.keyspace_name, "user") self.assertIn("user", cluster2.metadata.keyspaces[self.keyspace_name].user_types) @@ -832,7 +830,7 @@ def test_refresh_user_type_metadata_proto_2(self): for protocol_version in (1, 2): cluster = TestCluster() session = cluster.connect() - self.assertEqual(cluster.metadata.keyspaces[self.keyspace_name].user_types, {}) + assert cluster.metadata.keyspaces[self.keyspace_name].user_types == {} session.execute("CREATE TYPE {0}.user (age int, name text)".format(self.keyspace_name)) self.assertIn("user", cluster.metadata.keyspaces[self.keyspace_name].user_types) @@ -846,7 +844,7 @@ def test_refresh_user_type_metadata_proto_2(self): self.assertIn("something", cluster.metadata.keyspaces[self.keyspace_name].user_types["user"].field_names) session.execute("DROP TYPE {0}.user".format(self.keyspace_name)) - self.assertEqual(cluster.metadata.keyspaces[self.keyspace_name].user_types, {}) + assert cluster.metadata.keyspaces[self.keyspace_name].user_types == {} cluster.shutdown() @requires_java_udf @@ -874,13 +872,13 @@ def test_refresh_user_function_metadata(self): cluster2 = TestCluster(schema_event_refresh_window=-1) cluster2.connect() - self.assertEqual(cluster2.metadata.keyspaces[self.keyspace_name].functions, {}) + assert cluster2.metadata.keyspaces[self.keyspace_name].functions == {} self.session.execute("""CREATE FUNCTION {0}.sum_int(key int, val int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE java AS ' return key + val;';""".format(self.keyspace_name)) - self.assertEqual(cluster2.metadata.keyspaces[self.keyspace_name].functions, {}) + assert cluster2.metadata.keyspaces[self.keyspace_name].functions == {} cluster2.refresh_user_function_metadata(self.keyspace_name, UserFunctionDescriptor("sum_int", ["int", "int"])) self.assertIn("sum_int(int,int)", cluster2.metadata.keyspaces[self.keyspace_name].functions) @@ -911,7 +909,7 @@ def test_refresh_user_aggregate_metadata(self): cluster2 = TestCluster(schema_event_refresh_window=-1) cluster2.connect() - self.assertEqual(cluster2.metadata.keyspaces[self.keyspace_name].aggregates, {}) + assert cluster2.metadata.keyspaces[self.keyspace_name].aggregates == {} self.session.execute("""CREATE FUNCTION {0}.sum_int(key int, val int) RETURNS NULL ON NULL INPUT RETURNS int @@ -923,7 +921,7 @@ def test_refresh_user_aggregate_metadata(self): INITCOND 0""" .format(self.keyspace_name)) - self.assertEqual(cluster2.metadata.keyspaces[self.keyspace_name].aggregates, {}) + assert cluster2.metadata.keyspaces[self.keyspace_name].aggregates == {} cluster2.refresh_user_aggregate_metadata(self.keyspace_name, UserAggregateDescriptor("sum_agg", ["int"])) self.assertIn("sum_agg(int)", cluster2.metadata.keyspaces[self.keyspace_name].aggregates) @@ -949,19 +947,19 @@ def test_multiple_indices(self): self.session.execute("CREATE INDEX index_2 ON {0}.{1}(keys(b))".format(self.keyspace_name, self.function_table_name)) indices = self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].indexes - self.assertEqual(len(indices), 2) + assert len(indices) == 2 index_1 = indices["index_1"] index_2 = indices['index_2'] - self.assertEqual(index_1.table_name, "test_multiple_indices") - self.assertEqual(index_1.name, "index_1") - self.assertEqual(index_1.kind, "COMPOSITES") - self.assertEqual(index_1.index_options["target"], "values(b)") - self.assertEqual(index_1.keyspace_name, "schemametadatatests") - self.assertEqual(index_2.table_name, "test_multiple_indices") - self.assertEqual(index_2.name, "index_2") - self.assertEqual(index_2.kind, "COMPOSITES") - self.assertEqual(index_2.index_options["target"], "keys(b)") - self.assertEqual(index_2.keyspace_name, "schemametadatatests") + assert index_1.table_name == "test_multiple_indices" + assert index_1.name == "index_1" + assert index_1.kind == "COMPOSITES" + assert index_1.index_options["target"] == "values(b)" + assert index_1.keyspace_name == "schemametadatatests" + assert index_2.table_name == "test_multiple_indices" + assert index_2.name == "index_2" + assert index_2.kind == "COMPOSITES" + assert index_2.index_options["target"] == "keys(b)" + assert index_2.keyspace_name == "schemametadatatests" @greaterthanorequalcass30 def test_table_extensions(self): @@ -998,14 +996,14 @@ class Ext1(Ext0): self.assertIn(Ext0.name, _RegisteredExtensionType._extension_registry) self.assertIn(Ext1.name, _RegisteredExtensionType._extension_registry) # There will bee the RLAC extension here. - self.assertEqual(len(_RegisteredExtensionType._extension_registry), 3) + assert len(_RegisteredExtensionType._extension_registry) == 3 self.cluster.refresh_table_metadata(ks, t) table_meta = ks_meta.tables[t] view_meta = table_meta.views[v] - self.assertEqual(table_meta.export_as_string(), original_table_cql) - self.assertEqual(view_meta.export_as_string(), original_view_cql) + assert table_meta.export_as_string() == original_table_cql + assert view_meta.export_as_string() == original_view_cql update_t = s.prepare('UPDATE system_schema.tables SET extensions=? WHERE keyspace_name=? AND table_name=?') # for blob type coercing update_v = s.prepare('UPDATE system_schema.views SET extensions=? WHERE keyspace_name=? AND view_name=?') @@ -1064,7 +1062,7 @@ def test_metadata_pagination(self): self.cluster.schema_metadata_page_size = 5 self.cluster.refresh_schema_metadata() - self.assertEqual(len(self.cluster.metadata.keyspaces[self.keyspace_name].tables), 12) + assert len(self.cluster.metadata.keyspaces[self.keyspace_name].tables) == 12 def test_metadata_pagination_keyspaces(self): """ @@ -1089,7 +1087,7 @@ def test_metadata_pagination_keyspaces(self): after_ks_num = len(self.cluster.metadata.keyspaces) - self.assertEqual(before_ks_num, after_ks_num) + assert before_ks_num == after_ks_num class TestCodeCoverage(unittest.TestCase): @@ -1293,14 +1291,14 @@ def test_replicas(self): raise unittest.SkipTest('the murmur3 extension is not available') cluster = TestCluster() - self.assertEqual(cluster.metadata.get_replicas('test3rf', 'key'), []) + assert cluster.metadata.get_replicas('test3rf', 'key') == [] cluster.connect('test3rf') self.assertNotEqual(list(cluster.metadata.get_replicas('test3rf', b'key')), []) host = list(cluster.metadata.get_replicas('test3rf', b'key'))[0] - self.assertEqual(host.datacenter, 'dc1') - self.assertEqual(host.rack, 'r1') + assert host.datacenter == 'dc1' + assert host.rack == 'r1' cluster.shutdown() def test_token_map(self): @@ -1318,9 +1316,9 @@ def test_token_map(self): self.assertNotEqual(list(get_replicas(ksname, ring[0])), []) for i, token in enumerate(ring): - self.assertEqual(set(get_replicas('test3rf', token)), set(owners)) - self.assertEqual(set(get_replicas('test2rf', token)), set([owners[i], owners[(i + 1) % 3]])) - self.assertEqual(set(get_replicas('test1rf', token)), set([owners[i]])) + assert set(get_replicas('test3rf', token)) == set(owners) + assert set(get_replicas('test2rf', token)) == set([owners[i], owners[(i + 1) % 3]]) + assert set(get_replicas('test1rf', token)) == set([owners[i]]) cluster.shutdown() @@ -1336,7 +1334,7 @@ def test_token(self): cluster.connect() tmap = cluster.metadata.token_map self.assertTrue(issubclass(tmap.token_class, Token)) - self.assertEqual(expected_node_count, len(tmap.ring)) + assert expected_node_count == len(tmap.ring) cluster.shutdown() @@ -1403,13 +1401,13 @@ def test_keyspace_alter(self): self.session.execute('CREATE TABLE %s.d (d INT PRIMARY KEY)' % name) original_keyspace_meta = self.cluster.metadata.keyspaces[name] - self.assertEqual(original_keyspace_meta.durable_writes, True) - self.assertEqual(len(original_keyspace_meta.tables), 1) + assert original_keyspace_meta.durable_writes == True + assert len(original_keyspace_meta.tables) == 1 self.session.execute('ALTER KEYSPACE %s WITH durable_writes = false' % name) new_keyspace_meta = self.cluster.metadata.keyspaces[name] self.assertNotEqual(original_keyspace_meta, new_keyspace_meta) - self.assertEqual(new_keyspace_meta.durable_writes, False) + assert new_keyspace_meta.durable_writes == False class IndexMapTests(unittest.TestCase): @@ -1660,7 +1658,7 @@ def test_function_same_name_diff_types(self): # Ensure they are surfaced separately with self.VerifiedFunction(self, **kwargs): functions = [f for f in self.keyspace_function_meta.values() if f.name == self.function_name] - self.assertEqual(len(functions), 2) + assert len(functions) == 2 self.assertNotEqual(functions[0].argument_types, functions[1].argument_types) def test_function_no_parameters(self): @@ -1798,7 +1796,7 @@ def test_return_type_meta(self): """ with self.VerifiedAggregate(self, **self.make_aggregate_kwargs('sum_int', 'int', init_cond='1')) as va: - self.assertEqual(self.keyspace_aggregate_meta[va.signature].return_type, 'int') + assert self.keyspace_aggregate_meta[va.signature].return_type == 'int' def test_init_cond(self): """ @@ -1827,7 +1825,7 @@ def test_init_cond(self): cql_init = encoder.cql_encode_all_types(init_cond) with self.VerifiedAggregate(self, **self.make_aggregate_kwargs('sum_int', 'int', init_cond=cql_init)) as va: sum_res = s.execute("SELECT %s(v) AS sum FROM t" % va.function_kwargs['name']).one().sum - self.assertEqual(sum_res, int(init_cond) + sum(expected_values)) + assert sum_res == int(init_cond) + sum(expected_values) # list for init_cond in ([], ['1', '2']): @@ -1835,8 +1833,7 @@ def test_init_cond(self): with self.VerifiedAggregate(self, **self.make_aggregate_kwargs('extend_list', 'list', init_cond=cql_init)) as va: list_res = s.execute("SELECT %s(v) AS list_res FROM t" % va.function_kwargs['name']).one().list_res self.assertListEqual(list_res[:len(init_cond)], init_cond) - self.assertEqual(set(i for i in list_res[len(init_cond):]), - set(str(i) for i in expected_values)) + assert set(i for i in list_res[len(init_cond):]) == set(str(i) for i in expected_values) # map expected_map_values = dict((i, i) for i in expected_values) @@ -1891,7 +1888,7 @@ def test_same_name_diff_types(self): kwargs['argument_types'] = ['int', 'int'] with self.VerifiedAggregate(self, **kwargs): aggregates = [a for a in self.keyspace_aggregate_meta.values() if a.name == kwargs['name']] - self.assertEqual(len(aggregates), 2) + assert len(aggregates) == 2 self.assertNotEqual(aggregates[0].argument_types, aggregates[1].argument_types) def test_aggregates_follow_keyspace_alter(self): @@ -1943,19 +1940,19 @@ def test_cql_optional_params(self): self.assertIsNone(meta.initial_condition) self.assertIsNone(meta.final_func) cql = meta.as_cql_query() - self.assertEqual(cql.find('INITCOND'), -1) - self.assertEqual(cql.find('FINALFUNC'), -1) + assert cql.find('INITCOND') == -1 + assert cql.find('FINALFUNC') == -1 # initial condition, no final func kwargs['initial_condition'] = encoder.cql_encode_all_types(['init', 'cond']) with self.VerifiedAggregate(self, **kwargs) as va: meta = self.keyspace_aggregate_meta[va.signature] - self.assertEqual(meta.initial_condition, kwargs['initial_condition']) + assert meta.initial_condition == kwargs['initial_condition'] self.assertIsNone(meta.final_func) cql = meta.as_cql_query() search_string = "INITCOND %s" % kwargs['initial_condition'] self.assertGreater(cql.find(search_string), 0, '"%s" search string not found in cql:\n%s' % (search_string, cql)) - self.assertEqual(cql.find('FINALFUNC'), -1) + assert cql.find('FINALFUNC') == -1 # no initial condition, final func kwargs['initial_condition'] = None @@ -1963,9 +1960,9 @@ def test_cql_optional_params(self): with self.VerifiedAggregate(self, **kwargs) as va: meta = self.keyspace_aggregate_meta[va.signature] self.assertIsNone(meta.initial_condition) - self.assertEqual(meta.final_func, kwargs['final_func']) + assert meta.final_func == kwargs['final_func'] cql = meta.as_cql_query() - self.assertEqual(cql.find('INITCOND'), -1) + assert cql.find('INITCOND') == -1 search_string = 'FINALFUNC "%s"' % kwargs['final_func'] self.assertGreater(cql.find(search_string), 0, '"%s" search string not found in cql:\n%s' % (search_string, cql)) @@ -1974,8 +1971,8 @@ def test_cql_optional_params(self): kwargs['final_func'] = 'List_As_String' with self.VerifiedAggregate(self, **kwargs) as va: meta = self.keyspace_aggregate_meta[va.signature] - self.assertEqual(meta.initial_condition, kwargs['initial_condition']) - self.assertEqual(meta.final_func, kwargs['final_func']) + assert meta.initial_condition == kwargs['initial_condition'] + assert meta.final_func == kwargs['final_func'] cql = meta.as_cql_query() init_cond_idx = cql.find("INITCOND %s" % kwargs['initial_condition']) final_func_idx = cql.find('FINALFUNC "%s"' % kwargs['final_func']) @@ -2159,8 +2156,8 @@ def test_materialized_view_metadata_creation(self): self.assertIn("mv1", self.cluster.metadata.keyspaces[self.keyspace_name].views) self.assertIn("mv1", self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) - self.assertEqual(self.keyspace_name, self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].keyspace_name) - self.assertEqual(self.function_table_name, self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].base_table_name) + assert self.keyspace_name == self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].keyspace_name + assert self.function_table_name == self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].base_table_name def test_materialized_view_metadata_alter(self): """ @@ -2254,12 +2251,12 @@ def test_create_view_metadata(self): self.assertIsNotNone(len(score_table.views), 1) # Make sure user is a partition key, and not null - self.assertEqual(len(score_table.partition_key), 1) + assert len(score_table.partition_key) == 1 self.assertIsNotNone(score_table.columns['user']) self.assertTrue(score_table.columns['user'], score_table.partition_key[0]) # Validate clustering keys - self.assertEqual(len(score_table.clustering_key), 4) + assert len(score_table.clustering_key) == 4 self.assertIsNotNone(score_table.columns['game']) self.assertTrue(score_table.columns['game'], score_table.clustering_key[0]) @@ -2276,37 +2273,37 @@ def test_create_view_metadata(self): self.assertIsNotNone(score_table.columns['score']) # Validate basic mv information - self.assertEqual(mv.keyspace_name, self.keyspace_name) - self.assertEqual(mv.name, "monthlyhigh") - self.assertEqual(mv.base_table_name, "scores") + assert mv.keyspace_name == self.keyspace_name + assert mv.name == "monthlyhigh" + assert mv.base_table_name == "scores" self.assertFalse(mv.include_all_columns) # Validate that all columns are preset and correct mv_columns = list(mv.columns.values()) - self.assertEqual(len(mv_columns), 6) + assert len(mv_columns) == 6 game_column = mv_columns[0] self.assertIsNotNone(game_column) - self.assertEqual(game_column.name, 'game') - self.assertEqual(game_column, mv.partition_key[0]) + assert game_column.name == 'game' + assert game_column == mv.partition_key[0] year_column = mv_columns[1] self.assertIsNotNone(year_column) - self.assertEqual(year_column.name, 'year') - self.assertEqual(year_column, mv.partition_key[1]) + assert year_column.name == 'year' + assert year_column == mv.partition_key[1] month_column = mv_columns[2] self.assertIsNotNone(month_column) - self.assertEqual(month_column.name, 'month') - self.assertEqual(month_column, mv.partition_key[2]) + assert month_column.name == 'month' + assert month_column == mv.partition_key[2] def compare_columns(a, b, name): - self.assertEqual(a.name, name) - self.assertEqual(a.name, b.name) - self.assertEqual(a.table, b.table) - self.assertEqual(a.cql_type, b.cql_type) - self.assertEqual(a.is_static, b.is_static) - self.assertEqual(a.is_reversed, b.is_reversed) + assert a.name == name + assert a.name == b.name + assert a.table == b.table + assert a.cql_type == b.cql_type + assert a.is_static == b.is_static + assert a.is_reversed == b.is_reversed score_column = mv_columns[3] compare_columns(score_column, mv.clustering_key[0], 'score') @@ -2362,12 +2359,12 @@ def test_base_table_column_addition_mv(self): self.assertIsNotNone(score_table.views["monthlyhigh"]) self.assertIsNotNone(score_table.views["alltimehigh"]) - self.assertEqual(len(self.cluster.metadata.keyspaces[self.keyspace_name].views), 2) + assert len(self.cluster.metadata.keyspaces[self.keyspace_name].views) == 2 insert_fouls = """ALTER TABLE {0}.scores ADD fouls INT""".format((self.keyspace_name)) self.session.execute(insert_fouls) - self.assertEqual(len(self.cluster.metadata.keyspaces[self.keyspace_name].views), 2) + assert len(self.cluster.metadata.keyspaces[self.keyspace_name].views) == 2 score_table = self.cluster.metadata.keyspaces[self.keyspace_name].tables['scores'] self.assertIn("fouls", score_table.columns) @@ -2383,7 +2380,7 @@ def test_base_table_column_addition_mv(self): self.assertIn("fouls", mv_alltime.columns) mv_alltime_fouls_comumn = self.cluster.metadata.keyspaces[self.keyspace_name].views["alltimehigh"].columns['fouls'] - self.assertEqual(mv_alltime_fouls_comumn.cql_type, 'int') + assert mv_alltime_fouls_comumn.cql_type == 'int' @lessthancass30 def test_base_table_type_alter_mv(self): @@ -2421,13 +2418,13 @@ def test_base_table_type_alter_mv(self): WITH CLUSTERING ORDER BY (score DESC, user ASC, day ASC)""".format(self.keyspace_name) self.session.execute(create_mv) - self.assertEqual(len(self.cluster.metadata.keyspaces[self.keyspace_name].views), 1) + assert len(self.cluster.metadata.keyspaces[self.keyspace_name].views) == 1 alter_scores = """ALTER TABLE {0}.scores ALTER score TYPE blob""".format((self.keyspace_name)) self.session.execute(alter_scores) - self.assertEqual(len(self.cluster.metadata.keyspaces[self.keyspace_name].views), 1) + assert len(self.cluster.metadata.keyspaces[self.keyspace_name].views) == 1 score_column = self.cluster.metadata.keyspaces[self.keyspace_name].tables['scores'].columns['score'] - self.assertEqual(score_column.cql_type, 'blob') + assert score_column.cql_type == 'blob' # until CASSANDRA-9920+CASSANDRA-10500 MV updates are only available later with an async event for i in range(10): @@ -2436,7 +2433,7 @@ def test_base_table_type_alter_mv(self): break time.sleep(.2) - self.assertEqual(score_mv_column.cql_type, 'blob') + assert score_mv_column.cql_type == 'blob' def test_metadata_with_quoted_identifiers(self): """ @@ -2476,12 +2473,12 @@ def test_metadata_with_quoted_identifiers(self): self.assertIsNotNone(len(t1_table.views), 1) # Validate partition key, and not null - self.assertEqual(len(t1_table.partition_key), 1) + assert len(t1_table.partition_key) == 1 self.assertIsNotNone(t1_table.columns['theKey']) self.assertTrue(t1_table.columns['theKey'], t1_table.partition_key[0]) # Validate clustering key column - self.assertEqual(len(t1_table.clustering_key), 1) + assert len(t1_table.clustering_key) == 1 self.assertIsNotNone(t1_table.columns['the;Clustering']) self.assertTrue(t1_table.columns['the;Clustering'], t1_table.clustering_key[0]) @@ -2489,31 +2486,31 @@ def test_metadata_with_quoted_identifiers(self): self.assertIsNotNone(t1_table.columns['the Value']) # Validate basic mv information - self.assertEqual(mv.keyspace_name, self.keyspace_name) - self.assertEqual(mv.name, "mv1") - self.assertEqual(mv.base_table_name, "t1") + assert mv.keyspace_name == self.keyspace_name + assert mv.name == "mv1" + assert mv.base_table_name == "t1" self.assertFalse(mv.include_all_columns) # Validate that all columns are preset and correct mv_columns = list(mv.columns.values()) - self.assertEqual(len(mv_columns), 3) + assert len(mv_columns) == 3 theKey_column = mv_columns[0] self.assertIsNotNone(theKey_column) - self.assertEqual(theKey_column.name, 'theKey') - self.assertEqual(theKey_column, mv.partition_key[0]) + assert theKey_column.name == 'theKey' + assert theKey_column == mv.partition_key[0] cluster_column = mv_columns[1] self.assertIsNotNone(cluster_column) - self.assertEqual(cluster_column.name, 'the;Clustering') - self.assertEqual(cluster_column.name, mv.clustering_key[0].name) - self.assertEqual(cluster_column.table, mv.clustering_key[0].table) - self.assertEqual(cluster_column.is_static, mv.clustering_key[0].is_static) - self.assertEqual(cluster_column.is_reversed, mv.clustering_key[0].is_reversed) + assert cluster_column.name == 'the;Clustering' + assert cluster_column.name == mv.clustering_key[0].name + assert cluster_column.table == mv.clustering_key[0].table + assert cluster_column.is_static == mv.clustering_key[0].is_static + assert cluster_column.is_reversed == mv.clustering_key[0].is_reversed value_column = mv_columns[2] self.assertIsNotNone(value_column) - self.assertEqual(value_column.name, 'the Value') + assert value_column.name == 'the Value' class GroupPerHost(BasicSharedKeyspaceUnitTestCase): @@ -2560,7 +2557,7 @@ def _assert_group_keys_by_host(self, keys, table_name, stmt): for key in keys: routing_key = prepared_stmt.bind(key).routing_key hosts = self.cluster.metadata.get_replicas(self.ks_name, routing_key) - self.assertEqual(1, len(hosts)) # RF is 1 for this keyspace + assert 1 == len(hosts) # RF is 1 for this keyspace self.assertIn(key, keys_per_host[hosts[0]]) diff --git a/tests/integration/standard/test_metrics.py b/tests/integration/standard/test_metrics.py index 4b9ddb1351..972a073aa0 100644 --- a/tests/integration/standard/test_metrics.py +++ b/tests/integration/standard/test_metrics.py @@ -102,7 +102,7 @@ def test_write_timeout(self): query = SimpleStatement("INSERT INTO test (k, v) VALUES (2, 2)", consistency_level=ConsistencyLevel.ALL) with self.assertRaises(WriteTimeout): self.session.execute(query, timeout=None) - self.assertEqual(1, self.cluster.metrics.stats.write_timeouts) + assert 1 == self.cluster.metrics.stats.write_timeouts finally: get_node(1).resume() @@ -131,7 +131,7 @@ def test_read_timeout(self): query = SimpleStatement("SELECT * FROM test", consistency_level=ConsistencyLevel.ALL) with self.assertRaises(ReadTimeout): self.session.execute(query, timeout=None) - self.assertEqual(1, self.cluster.metrics.stats.read_timeouts) + assert 1 == self.cluster.metrics.stats.read_timeouts finally: get_node(1).resume() @@ -161,13 +161,13 @@ def test_unavailable(self): query = SimpleStatement("INSERT INTO test (k, v) VALUES (2, 2)", consistency_level=ConsistencyLevel.ALL) with self.assertRaises(Unavailable): self.session.execute(query) - self.assertEqual(self.cluster.metrics.stats.unavailables, 1) + assert self.cluster.metrics.stats.unavailables == 1 # Test write query = SimpleStatement("SELECT * FROM test", consistency_level=ConsistencyLevel.ALL) with self.assertRaises(Unavailable): self.session.execute(query, timeout=None) - self.assertEqual(self.cluster.metrics.stats.unavailables, 2) + assert self.cluster.metrics.stats.unavailables == 2 finally: get_node(1).start(wait_other_notice=True, wait_for_binary_proto=True) # Give some time for the cluster to come back up, for the next test @@ -206,7 +206,7 @@ def test_metrics_per_cluster(self): ) cluster2.connect(self.ks_name, wait_for_all_pools=True) - self.assertEqual(len(cluster2.metadata.all_hosts()), 3) + assert len(cluster2.metadata.all_hosts()) == 3 query = SimpleStatement("SELECT * FROM {0}.{0}".format(self.ks_name), consistency_level=ConsistencyLevel.ALL) self.session.execute(query) @@ -229,19 +229,19 @@ def test_metrics_per_cluster(self): stats_cluster2 = cluster2.metrics.get_stats() # Test direct access to stats - self.assertEqual(1, self.cluster.metrics.stats.write_timeouts) - self.assertEqual(0, cluster2.metrics.stats.write_timeouts) + assert 1 == self.cluster.metrics.stats.write_timeouts + assert 0 == cluster2.metrics.stats.write_timeouts # Test direct access to a child stats self.assertNotEqual(0.0, self.cluster.metrics.request_timer['mean']) - self.assertEqual(0.0, cluster2.metrics.request_timer['mean']) + assert 0.0 == cluster2.metrics.request_timer['mean'] # Test access via metrics.get_stats() self.assertNotEqual(0.0, stats_cluster1['request_timer']['mean']) - self.assertEqual(0.0, stats_cluster2['request_timer']['mean']) + assert 0.0 == stats_cluster2['request_timer']['mean'] # Test access by stats_name - self.assertEqual(0.0, scales.getStats()['cluster2-metrics']['request_timer']['mean']) + assert 0.0 == scales.getStats()['cluster2-metrics']['request_timer']['mean'] cluster2.shutdown() @@ -285,8 +285,8 @@ def test_duplicate_metrics_per_cluster(self): query = SimpleStatement("SELECT * FROM {0}.{0}".format(self.ks_name), consistency_level=ConsistencyLevel.ALL) session3.execute(query) - self.assertEqual(cluster2.metrics.get_stats()['request_timer']['count'], 10) - self.assertEqual(cluster3.metrics.get_stats()['request_timer']['count'], 5) + assert cluster2.metrics.get_stats()['request_timer']['count'] == 10 + assert cluster3.metrics.get_stats()['request_timer']['count'] == 5 # Check scales to ensure they are appropriately named self.assertTrue("appcluster" in scales._Stats.stats.keys()) diff --git a/tests/integration/standard/test_policies.py b/tests/integration/standard/test_policies.py index faa21efb02..0c84fd06be 100644 --- a/tests/integration/standard/test_policies.py +++ b/tests/integration/standard/test_policies.py @@ -62,7 +62,7 @@ def test_predicate_changes(self): response = session.execute("SELECT * from system.local WHERE key='local'") queried_hosts.update(response.response_future.attempted_hosts) - self.assertEqual(queried_hosts, single_host) + assert queried_hosts == single_host external_event = False futures = session.update_created_pools() @@ -72,7 +72,7 @@ def test_predicate_changes(self): for _ in range(10): response = session.execute("SELECT * from system.local WHERE key='local'") queried_hosts.update(response.response_future.attempted_hosts) - self.assertEqual(queried_hosts, all_hosts) + assert queried_hosts == all_hosts class WhiteListRoundRobinPolicyTests(unittest.TestCase): @@ -89,7 +89,7 @@ def test_only_connects_to_subset(self): response = session.execute("SELECT * from system.local WHERE key='local'", execution_profile="white_list") queried_hosts.update(response.response_future.attempted_hosts) queried_hosts = set(host.address for host in queried_hosts) - self.assertEqual(queried_hosts, only_connect_hosts) + assert queried_hosts == only_connect_hosts class ExponentialRetryPolicyTests(unittest.TestCase): diff --git a/tests/integration/standard/test_prepared_statements.py b/tests/integration/standard/test_prepared_statements.py index d413a4dc95..478399191d 100644 --- a/tests/integration/standard/test_prepared_statements.py +++ b/tests/integration/standard/test_prepared_statements.py @@ -93,7 +93,7 @@ def test_basic(self): bound = prepared.bind(('a')) results = self.session.execute(bound) - self.assertEqual(results, [('a', 'b', 'c')]) + assert results == [('a', 'b', 'c')] # test with new dict binding prepared = self.session.prepare( @@ -119,7 +119,7 @@ def test_basic(self): bound = prepared.bind({'a': 'x'}) results = self.session.execute(bound) - self.assertEqual(results, [('x', 'y', 'z')]) + assert results == [('x', 'y', 'z')] def test_missing_primary_key(self): """ @@ -229,7 +229,7 @@ def test_none_values(self): bound = prepared.bind((1,)) results = self.session.execute(bound) - self.assertEqual(results.one().v, None) + assert results.one().v == None def test_unset_values(self): """ @@ -272,7 +272,7 @@ def test_unset_values(self): for params, expected in bind_expected: self.session.execute(insert, params) results = self.session.execute(select, (0,)) - self.assertEqual(results.one(), expected) + assert results.one() == expected self.assertRaises(ValueError, self.session.execute, select, (UNSET_VALUE, 0, 0)) @@ -297,7 +297,7 @@ def test_no_meta(self): bound = prepared.bind(None) bound.consistency_level = ConsistencyLevel.ALL results = self.session.execute(bound) - self.assertEqual(results.one().v, 0) + assert results.one().v == 0 def test_none_values_dicts(self): """ @@ -322,7 +322,7 @@ def test_none_values_dicts(self): bound = prepared.bind({'k': 1}) results = self.session.execute(bound) - self.assertEqual(results.one().v, None) + assert results.one().v == None def test_async_binding(self): """ @@ -346,7 +346,7 @@ def test_async_binding(self): future = self.session.execute_async(prepared, (873,)) results = future.result() - self.assertEqual(results.one().v, None) + assert results.one().v == None def test_async_binding_dicts(self): """ @@ -369,7 +369,7 @@ def test_async_binding_dicts(self): future = self.session.execute_async(prepared, {'k': 873}) results = future.result() - self.assertEqual(results.one().v, None) + assert results.one().v == None def test_raise_error_on_prepared_statement_execution_dropped_table(self): """ @@ -441,17 +441,17 @@ def test_invalidated_result_metadata(self): """ wildcard_prepared = self.session.prepare("SELECT * FROM {}".format(self.table_name)) original_result_metadata = wildcard_prepared.result_metadata - self.assertEqual(len(original_result_metadata), 3) + assert len(original_result_metadata) == 3 r = self.session.execute(wildcard_prepared) - self.assertEqual(r[0], (1, 1, 1)) + assert r[0] == (1, 1, 1) self.session.execute("ALTER TABLE {} DROP d".format(self.table_name)) # Get a bunch of requests in the pipeline with varying states of result_meta, reprepare, resolved futures = set(self.session.execute_async(wildcard_prepared.bind(None)) for _ in range(200)) for f in futures: - self.assertEqual(f.result()[0], (1, 1)) + assert f.result()[0] == (1, 1) self.assertIsNot(wildcard_prepared.result_metadata, original_result_metadata) @@ -468,7 +468,7 @@ def test_prepared_id_is_update(self): """ prepared_statement = self.session.prepare("SELECT * from {} WHERE a = ?".format(self.table_name)) id_before = prepared_statement.result_metadata_id - self.assertEqual(len(prepared_statement.result_metadata), 3) + assert len(prepared_statement.result_metadata) == 3 self.session.execute("ALTER TABLE {} ADD c int".format(self.table_name)) bound_statement = prepared_statement.bind((1, )) @@ -477,7 +477,7 @@ def test_prepared_id_is_update(self): id_after = prepared_statement.result_metadata_id self.assertNotEqual(id_before, id_after) - self.assertEqual(len(prepared_statement.result_metadata), 4) + assert len(prepared_statement.result_metadata) == 4 def test_prepared_id_is_updated_across_pages(self): """ @@ -491,7 +491,7 @@ def test_prepared_id_is_updated_across_pages(self): """ prepared_statement = self.session.prepare("SELECT * from {}".format(self.table_name)) id_before = prepared_statement.result_metadata_id - self.assertEqual(len(prepared_statement.result_metadata), 3) + assert len(prepared_statement.result_metadata) == 3 prepared_statement.fetch_size = 2 result = self.session.execute(prepared_statement.bind((None))) @@ -505,9 +505,9 @@ def test_prepared_id_is_updated_across_pages(self): id_after = prepared_statement.result_metadata_id - self.assertEqual(result_set, expected_result_set) + assert result_set == expected_result_set self.assertNotEqual(id_before, id_after) - self.assertEqual(len(prepared_statement.result_metadata), 4) + assert len(prepared_statement.result_metadata) == 4 def test_prepare_id_is_updated_across_session(self): """ @@ -524,7 +524,7 @@ def test_prepare_id_is_updated_across_session(self): stm = "SELECT * from {} WHERE a = ?".format(self.table_name) one_prepared_stm = one_session.prepare(stm) - self.assertEqual(len(one_prepared_stm.result_metadata), 3) + assert len(one_prepared_stm.result_metadata) == 3 one_id_before = one_prepared_stm.result_metadata_id @@ -533,7 +533,7 @@ def test_prepare_id_is_updated_across_session(self): one_id_after = one_prepared_stm.result_metadata_id self.assertNotEqual(one_id_before, one_id_after) - self.assertEqual(len(one_prepared_stm.result_metadata), 4) + assert len(one_prepared_stm.result_metadata) == 4 def test_not_reprepare_invalid_statements(self): """ @@ -584,11 +584,8 @@ def _test_updated_conditional(self, session, value): LOG.debug('initial result_metadata_id: {}'.format(first_id)) def check_result_and_metadata(expected): - self.assertEqual( - session.execute(prepared_statement, (value, value, value)).one(), - expected - ) - self.assertEqual(prepared_statement.result_metadata_id, first_id) + assert session.execute(prepared_statement, (value, value, value)).one() == expected + assert prepared_statement.result_metadata_id == first_id self.assertIsNone(prepared_statement.result_metadata) # Successful conditional update diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index a4d1b083bf..cf103a0612 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -66,9 +66,9 @@ def test_query(self): self.assertIsInstance(prepared, PreparedStatement) bound = prepared.bind((1, None)) self.assertIsInstance(bound, BoundStatement) - self.assertEqual(2, len(bound.values)) + assert 2 == len(bound.values) self.session.execute(bound) - self.assertEqual(bound.routing_key, b'\x00\x00\x00\x01') + assert bound.routing_key == b'\x00\x00\x00\x01' def test_trace_prints_okay(self): """ @@ -112,9 +112,9 @@ def test_trace_id_to_resultset(self): self.assertIsNotNone(future_trace) rs_trace = rs.get_query_trace() - self.assertEqual(rs_trace, future_trace) + assert rs_trace == future_trace self.assertTrue(rs_trace.events) - self.assertEqual(len(rs_trace.events), len(future_trace.events)) + assert len(rs_trace.events) == len(future_trace.events) self.assertListEqual([rs_trace], rs.get_all_query_traces()) @@ -222,7 +222,7 @@ def test_incomplete_query_trace(self): response_future = self.session.execute_async("SELECT i FROM {0} WHERE k=0".format(self.keyspace_table_name), trace=True) response_future.result() - self.assertEqual(len(response_future._query_traces), 1) + assert len(response_future._query_traces) == 1 trace = response_future._query_traces[0] self.assertTrue(self._wait_for_trace_to_populate(trace.trace_id)) @@ -284,16 +284,16 @@ def test_query_by_id(self): results1 = self.session.execute("select id, m from {0}.{1}".format(self.keyspace_name, self.function_table_name)) self.assertIsNotNone(results1.column_types) - self.assertEqual(results1.column_types[0].typename, 'int') - self.assertEqual(results1.column_types[1].typename, 'map') - self.assertEqual(results1.column_types[0].cassname, 'Int32Type') - self.assertEqual(results1.column_types[1].cassname, 'MapType') - self.assertEqual(len(results1.column_types[0].subtypes), 0) - self.assertEqual(len(results1.column_types[1].subtypes), 2) - self.assertEqual(results1.column_types[1].subtypes[0].typename, "int") - self.assertEqual(results1.column_types[1].subtypes[1].typename, "varchar") - self.assertEqual(results1.column_types[1].subtypes[0].cassname, "Int32Type") - self.assertEqual(results1.column_types[1].subtypes[1].cassname, "VarcharType") + assert results1.column_types[0].typename == 'int' + assert results1.column_types[1].typename == 'map' + assert results1.column_types[0].cassname == 'Int32Type' + assert results1.column_types[1].cassname == 'MapType' + assert len(results1.column_types[0].subtypes) == 0 + assert len(results1.column_types[1].subtypes) == 2 + assert results1.column_types[1].subtypes[0].typename == "int" + assert results1.column_types[1].subtypes[1].typename == "varchar" + assert results1.column_types[1].subtypes[0].cassname == "Int32Type" + assert results1.column_types[1].subtypes[1].cassname == "VarcharType" def test_column_names(self): """ @@ -321,7 +321,7 @@ def test_column_names(self): result_set = self.session.execute("SELECT * FROM {0}.{1}".format(self.keyspace_name, self.function_table_name)) self.assertIsNotNone(result_set.column_types) - self.assertEqual(result_set.column_names, [u'user', u'game', u'year', u'month', u'day', u'score']) + assert result_set.column_names == [u'user', u'game', u'year', u'month', u'day', u'score'] @greaterthanorequalcass30 def test_basic_json_query(self): @@ -330,8 +330,8 @@ def test_basic_json_query(self): self.session.execute(insert_query) results = self.session.execute(json_query) - self.assertEqual(results.column_names, ["[json]"]) - self.assertEqual(results.one()[0], '{"k": 1, "v": 1}') + assert results.column_names == ["[json]"] + assert results.one()[0] == '{"k": 1, "v": 1}' def test_host_targeting_query(self): """ @@ -356,7 +356,7 @@ def test_host_targeting_query(self): future = self.session.execute_async(query, host=host, execution_profile=checkable_ep) future.result() # check we're using the selected host - self.assertEqual(host, future.coordinator_host) + assert host == future.coordinator_host # check that this bypasses the LBP self.assertFalse(checkable_ep.load_balancing_policy.make_query_plan.called) @@ -381,7 +381,7 @@ def test_routing_key(self): self.assertIsInstance(prepared, PreparedStatement) bound = prepared.bind((1, None)) - self.assertEqual(bound.routing_key, b'\x00\x00\x00\x01') + assert bound.routing_key == b'\x00\x00\x00\x01' def test_empty_routing_key_indexes(self): """ @@ -396,7 +396,7 @@ def test_empty_routing_key_indexes(self): self.assertIsInstance(prepared, PreparedStatement) bound = prepared.bind((1, None)) - self.assertEqual(bound.routing_key, None) + assert bound.routing_key == None def test_predefined_routing_key(self): """ @@ -411,7 +411,7 @@ def test_predefined_routing_key(self): self.assertIsInstance(prepared, PreparedStatement) bound = prepared.bind((1, None)) bound._set_routing_key('fake_key') - self.assertEqual(bound.routing_key, 'fake_key') + assert bound.routing_key == 'fake_key' def test_multiple_routing_key_indexes(self): """ @@ -425,11 +425,11 @@ def test_multiple_routing_key_indexes(self): prepared.routing_key_indexes = [0, 1] bound = prepared.bind((1, 2)) - self.assertEqual(bound.routing_key, b'\x00\x04\x00\x00\x00\x01\x00\x00\x04\x00\x00\x00\x02\x00') + assert bound.routing_key == b'\x00\x04\x00\x00\x00\x01\x00\x00\x04\x00\x00\x00\x02\x00' prepared.routing_key_indexes = [1, 0] bound = prepared.bind((1, 2)) - self.assertEqual(bound.routing_key, b'\x00\x04\x00\x00\x00\x02\x00\x00\x04\x00\x00\x00\x01\x00') + assert bound.routing_key == b'\x00\x04\x00\x00\x00\x02\x00\x00\x04\x00\x00\x00\x01\x00' def test_bound_keyspace(self): """ @@ -442,7 +442,7 @@ def test_bound_keyspace(self): self.assertIsInstance(prepared, PreparedStatement) bound = prepared.bind((1, 2)) - self.assertEqual(bound.keyspace, 'test3rf') + assert bound.keyspace == 'test3rf' class ForcedHostIndexPolicy(RoundRobinPolicy): @@ -490,7 +490,7 @@ def test_prepared_metadata_generation(self): session = cluster.connect() select_statement = session.prepare("SELECT * FROM system.local WHERE key='local'") if proto_version == 1: - self.assertEqual(select_statement.result_metadata, None) + assert select_statement.result_metadata == None else: self.assertNotEqual(select_statement.result_metadata, None) future = session.execute_async(select_statement) @@ -498,7 +498,7 @@ def test_prepared_metadata_generation(self): if base_line is None: base_line = results.one()._asdict().keys() else: - self.assertEqual(base_line, results.one()._asdict().keys()) + assert base_line == results.one()._asdict().keys() cluster.shutdown() @@ -522,7 +522,7 @@ def test_prepare_on_all_hosts(self): select_statement = session.prepare("SELECT k FROM test3rf.test WHERE k = ?") for host in clus.metadata.all_hosts(): session.execute(select_statement, (1, ), host=host) - self.assertEqual(2, mock_handler.get_message_count('debug', "Re-preparing")) + assert 2 == mock_handler.get_message_count('debug', "Re-preparing") def test_prepare_batch_statement(self): """ @@ -562,12 +562,12 @@ def test_prepare_batch_statement(self): session.execute(batch_statement) # To verify our test assumption that queries are getting re-prepared properly - self.assertEqual(1, mock_handler.get_message_count('debug', "Re-preparing")) + assert 1 == mock_handler.get_message_count('debug', "Re-preparing") select_results = session.execute(SimpleStatement("SELECT * FROM %s WHERE k = 1" % table, consistency_level=ConsistencyLevel.ALL)) first_row = select_results.one()[:2] - self.assertEqual((1, 2), first_row) + assert (1, 2) == first_row def test_prepare_batch_statement_after_alter(self): """ @@ -615,10 +615,10 @@ def test_prepare_batch_statement_after_alter(self): (4, None, 5, None, 6) ] - self.assertEqual(set(expected_results), set(select_results._current_rows)) + assert set(expected_results) == set(select_results._current_rows) # To verify our test assumption that queries are getting re-prepared properly - self.assertEqual(3, mock_handler.get_message_count('debug', "Re-preparing")) + assert 3 == mock_handler.get_message_count('debug', "Re-preparing") class PrintStatementTests(unittest.TestCase): @@ -632,8 +632,7 @@ def test_simple_statement(self): """ ss = SimpleStatement('SELECT * FROM test3rf.test', consistency_level=ConsistencyLevel.ONE) - self.assertEqual(str(ss), - '') + assert str(ss) == '' def test_prepared_statement(self): """ @@ -646,12 +645,10 @@ def test_prepared_statement(self): prepared = session.prepare('INSERT INTO test3rf.test (k, v) VALUES (?, ?)') prepared.consistency_level = ConsistencyLevel.ONE - self.assertEqual(str(prepared), - '') + assert str(prepared) == '' bound = prepared.bind((1, 2)) - self.assertEqual(str(bound), - '') + assert str(bound) == '' cluster.shutdown() @@ -683,8 +680,8 @@ def confirm_results(self): keys.add(result.k) values.add(result.v) - self.assertEqual(set(range(10)), keys, msg=results) - self.assertEqual(set(range(10)), values, msg=results) + assert set(range(10)) == keys, results + assert set(range(10)) == values, results def test_string_statements(self): batch = BatchStatement(BatchType.LOGGED) @@ -810,20 +807,20 @@ def test_conditional_update(self): serial_consistency_level=ConsistencyLevel.SERIAL) # crazy test, but PYTHON-299 # TODO: expand to check more parameters get passed to statement, and on to messages - self.assertEqual(statement.serial_consistency_level, ConsistencyLevel.SERIAL) + assert statement.serial_consistency_level == ConsistencyLevel.SERIAL future = self.session.execute_async(statement) result = future.result() - self.assertEqual(future.message.serial_consistency_level, ConsistencyLevel.SERIAL) + assert future.message.serial_consistency_level == ConsistencyLevel.SERIAL self.assertTrue(result) self.assertFalse(result.one().applied) statement = SimpleStatement( "UPDATE test3rf.test SET v=1 WHERE k=0 IF v=0", serial_consistency_level=ConsistencyLevel.LOCAL_SERIAL) - self.assertEqual(statement.serial_consistency_level, ConsistencyLevel.LOCAL_SERIAL) + assert statement.serial_consistency_level == ConsistencyLevel.LOCAL_SERIAL future = self.session.execute_async(statement) result = future.result() - self.assertEqual(future.message.serial_consistency_level, ConsistencyLevel.LOCAL_SERIAL) + assert future.message.serial_consistency_level == ConsistencyLevel.LOCAL_SERIAL self.assertTrue(result) self.assertTrue(result.one().applied) @@ -835,7 +832,7 @@ def test_conditional_update_with_prepared_statements(self): statement.serial_consistency_level = ConsistencyLevel.SERIAL future = self.session.execute_async(statement) result = future.result() - self.assertEqual(future.message.serial_consistency_level, ConsistencyLevel.SERIAL) + assert future.message.serial_consistency_level == ConsistencyLevel.SERIAL self.assertTrue(result) self.assertFalse(result.one().applied) @@ -845,7 +842,7 @@ def test_conditional_update_with_prepared_statements(self): bound.serial_consistency_level = ConsistencyLevel.LOCAL_SERIAL future = self.session.execute_async(bound) result = future.result() - self.assertEqual(future.message.serial_consistency_level, ConsistencyLevel.LOCAL_SERIAL) + assert future.message.serial_consistency_level == ConsistencyLevel.LOCAL_SERIAL self.assertTrue(result) self.assertTrue(result.one().applied) @@ -853,19 +850,19 @@ def test_conditional_update_with_batch_statements(self): self.session.execute("INSERT INTO test3rf.test (k, v) VALUES (0, 0)") statement = BatchStatement(serial_consistency_level=ConsistencyLevel.SERIAL) statement.add("UPDATE test3rf.test SET v=1 WHERE k=0 IF v=1") - self.assertEqual(statement.serial_consistency_level, ConsistencyLevel.SERIAL) + assert statement.serial_consistency_level == ConsistencyLevel.SERIAL future = self.session.execute_async(statement) result = future.result() - self.assertEqual(future.message.serial_consistency_level, ConsistencyLevel.SERIAL) + assert future.message.serial_consistency_level == ConsistencyLevel.SERIAL self.assertTrue(result) self.assertFalse(result.one().applied) statement = BatchStatement(serial_consistency_level=ConsistencyLevel.LOCAL_SERIAL) statement.add("UPDATE test3rf.test SET v=1 WHERE k=0 IF v=0") - self.assertEqual(statement.serial_consistency_level, ConsistencyLevel.LOCAL_SERIAL) + assert statement.serial_consistency_level == ConsistencyLevel.LOCAL_SERIAL future = self.session.execute_async(statement) result = future.result() - self.assertEqual(future.message.serial_consistency_level, ConsistencyLevel.LOCAL_SERIAL) + assert future.message.serial_consistency_level == ConsistencyLevel.LOCAL_SERIAL self.assertTrue(result) self.assertTrue(result.one().applied) @@ -989,7 +986,7 @@ def test_was_applied_batch_stmt(self): all_rows = self.session.execute("SELECT * from test3rf.lwt_clustering", execution_profile='serial') # Verify the non conditional insert hasn't been inserted - self.assertEqual(len(all_rows.current_rows), 3) + assert len(all_rows.current_rows) == 3 # Should fail since (0, 0, 10) have already been written batch_statement = BatchStatement(batch_type) @@ -1015,7 +1012,7 @@ def test_was_applied_batch_stmt(self): all_rows = self.session.execute("SELECT * from test3rf.lwt_clustering", execution_profile='serial') for i, row in enumerate(all_rows): - self.assertEqual((0, i, 10), (row[0], row[1], row[2])) + assert (0, i, 10) == (row[0], row[1], row[2]) self.session.execute("TRUNCATE TABLE test3rf.lwt_clustering") @@ -1092,7 +1089,7 @@ def test_rk_from_bound(self): batch = BatchStatement() batch.add(bound) self.assertIsNotNone(batch.routing_key) - self.assertEqual(batch.routing_key, bound.routing_key) + assert batch.routing_key == bound.routing_key def test_rk_from_simple(self): """ @@ -1101,7 +1098,7 @@ def test_rk_from_simple(self): batch = BatchStatement() batch.add(self.simple_statement) self.assertIsNotNone(batch.routing_key) - self.assertEqual(batch.routing_key, self.simple_statement.routing_key) + assert batch.routing_key == self.simple_statement.routing_key def test_inherit_first_rk_bound(self): """ @@ -1117,7 +1114,7 @@ def test_inherit_first_rk_bound(self): batch.add(self.prepared, (i, i)) self.assertIsNotNone(batch.routing_key) - self.assertEqual(batch.routing_key, bound.routing_key) + assert batch.routing_key == bound.routing_key def test_inherit_first_rk_simple_statement(self): """ @@ -1133,7 +1130,7 @@ def test_inherit_first_rk_simple_statement(self): batch.add(self.prepared, (i, i)) self.assertIsNotNone(batch.routing_key) - self.assertEqual(batch.routing_key, self.simple_statement.routing_key) + assert batch.routing_key == self.simple_statement.routing_key def test_inherit_first_rk_prepared_param(self): """ @@ -1147,7 +1144,7 @@ def test_inherit_first_rk_prepared_param(self): batch.add(self.simple_statement) self.assertIsNotNone(batch.routing_key) - self.assertEqual(batch.routing_key, self.prepared.bind((1, 0)).routing_key) + assert batch.routing_key == self.prepared.bind((1, 0)).routing_key @greaterthanorequalcass30 @@ -1243,73 +1240,73 @@ def test_mv_filtering(self): query_statement = SimpleStatement("SELECT * FROM {0}.alltimehigh WHERE game='Coup'".format(self.keyspace_name), consistency_level=ConsistencyLevel.QUORUM) results = self.session.execute(query_statement) - self.assertEqual(results.one().game, 'Coup') - self.assertEqual(results.one().year, 2015) - self.assertEqual(results.one().month, 5) - self.assertEqual(results.one().day, 1) - self.assertEqual(results.one().score, 4000) - self.assertEqual(results.one().user, "pcmanus") + assert results.one().game == 'Coup' + assert results.one().year == 2015 + assert results.one().month == 5 + assert results.one().day == 1 + assert results.one().score == 4000 + assert results.one().user == "pcmanus" # Test prepared statement and daily high filtering prepared_query = self.session.prepare("SELECT * FROM {0}.dailyhigh WHERE game=? AND year=? AND month=? and day=?".format(self.keyspace_name)) bound_query = prepared_query.bind(("Coup", 2015, 6, 2)) results = self.session.execute(bound_query) - self.assertEqual(results.one().game, 'Coup') - self.assertEqual(results.one().year, 2015) - self.assertEqual(results.one().month, 6) - self.assertEqual(results.one().day, 2) - self.assertEqual(results.one().score, 2000) - self.assertEqual(results.one().user, "pcmanus") - - self.assertEqual(results[1].game, 'Coup') - self.assertEqual(results[1].year, 2015) - self.assertEqual(results[1].month, 6) - self.assertEqual(results[1].day, 2) - self.assertEqual(results[1].score, 1000) - self.assertEqual(results[1].user, "tjake") + assert results.one().game == 'Coup' + assert results.one().year == 2015 + assert results.one().month == 6 + assert results.one().day == 2 + assert results.one().score == 2000 + assert results.one().user == "pcmanus" + + assert results[1].game == 'Coup' + assert results[1].year == 2015 + assert results[1].month == 6 + assert results[1].day == 2 + assert results[1].score == 1000 + assert results[1].user == "tjake" # Test montly high range queries prepared_query = self.session.prepare("SELECT * FROM {0}.monthlyhigh WHERE game=? AND year=? AND month=? and score >= ? and score <= ?".format(self.keyspace_name)) bound_query = prepared_query.bind(("Coup", 2015, 6, 2500, 3500)) results = self.session.execute(bound_query) - self.assertEqual(results.one().game, 'Coup') - self.assertEqual(results.one().year, 2015) - self.assertEqual(results.one().month, 6) - self.assertEqual(results.one().day, 20) - self.assertEqual(results.one().score, 3500) - self.assertEqual(results.one().user, "jbellis") - - self.assertEqual(results[1].game, 'Coup') - self.assertEqual(results[1].year, 2015) - self.assertEqual(results[1].month, 6) - self.assertEqual(results[1].day, 9) - self.assertEqual(results[1].score, 2700) - self.assertEqual(results[1].user, "jmckenzie") - - self.assertEqual(results[2].game, 'Coup') - self.assertEqual(results[2].year, 2015) - self.assertEqual(results[2].month, 6) - self.assertEqual(results[2].day, 1) - self.assertEqual(results[2].score, 2500) - self.assertEqual(results[2].user, "iamaleksey") + assert results.one().game == 'Coup' + assert results.one().year == 2015 + assert results.one().month == 6 + assert results.one().day == 20 + assert results.one().score == 3500 + assert results.one().user == "jbellis" + + assert results[1].game == 'Coup' + assert results[1].year == 2015 + assert results[1].month == 6 + assert results[1].day == 9 + assert results[1].score == 2700 + assert results[1].user == "jmckenzie" + + assert results[2].game == 'Coup' + assert results[2].year == 2015 + assert results[2].month == 6 + assert results[2].day == 1 + assert results[2].score == 2500 + assert results[2].user == "iamaleksey" # Test filtered user high scores query_statement = SimpleStatement("SELECT * FROM {0}.filtereduserhigh WHERE game='Chess'".format(self.keyspace_name), consistency_level=ConsistencyLevel.QUORUM) results = self.session.execute(query_statement) - self.assertEqual(results.one().game, 'Chess') - self.assertEqual(results.one().year, 2015) - self.assertEqual(results.one().month, 6) - self.assertEqual(results.one().day, 21) - self.assertEqual(results.one().score, 3500) - self.assertEqual(results.one().user, "jbellis") + assert results.one().game == 'Chess' + assert results.one().year == 2015 + assert results.one().month == 6 + assert results.one().day == 21 + assert results.one().score == 3500 + assert results.one().user == "jbellis" - self.assertEqual(results[1].game, 'Chess') - self.assertEqual(results[1].year, 2015) - self.assertEqual(results[1].month, 1) - self.assertEqual(results[1].day, 25) - self.assertEqual(results[1].score, 3200) - self.assertEqual(results[1].user, "pcmanus") + assert results[1].game == 'Chess' + assert results[1].year == 2015 + assert results[1].month == 1 + assert results[1].day == 25 + assert results[1].score == 3200 + assert results[1].user == "pcmanus" class UnicodeQueryTest(BasicSharedKeyspaceUnitTestCase): @@ -1477,12 +1474,12 @@ def test_lower_protocol(self): def _check_set_keyspace_in_statement(self, session): simple_stmt = SimpleStatement("SELECT * from {}".format(self.table_name), keyspace=self.ks_name) results = session.execute(simple_stmt) - self.assertEqual(results.one(), (1, 1)) + assert results.one() == (1, 1) simple_stmt = SimpleStatement("SELECT * from {}".format(self.table_name)) simple_stmt.keyspace = self.ks_name results = session.execute(simple_stmt) - self.assertEqual(results.one(), (1, 1)) + assert results.one() == (1, 1) @greaterthanorequalcass40 @@ -1507,8 +1504,8 @@ def confirm_results(self): keys.add(result.k) values.add(result.v) - self.assertEqual(set(range(10)), keys, msg=results) - self.assertEqual(set(range(10)), values, msg=results) + assert set(range(10)) == keys, results + assert set(range(10)) == values, results @greaterthanorequalcass40 @@ -1536,14 +1533,14 @@ def test_prepared_with_keyspace_explicit(self): prepared_statement = self.session.prepare(query, keyspace=self.ks_name) results = self.session.execute(prepared_statement, (1, )) - self.assertEqual(results.one(), (1, 1)) + assert results.one() == (1, 1) prepared_statement_alternative = self.session.prepare(query, keyspace=self.alternative_ks) self.assertNotEqual(prepared_statement.query_id, prepared_statement_alternative.query_id) results = self.session.execute(prepared_statement_alternative, (2,)) - self.assertEqual(results.one(), (2, 2)) + assert results.one() == (2, 2) def test_reprepare_after_host_is_down(self): """ @@ -1570,13 +1567,13 @@ def test_reprepare_after_host_is_down(self): # We wait for cluster._prepare_all_queries to be called time.sleep(5) - self.assertEqual(1, mock_handler.get_message_count('debug', 'Preparing all known prepared statements')) + assert 1 == mock_handler.get_message_count('debug', 'Preparing all known prepared statements') results = self.session.execute(prepared_statement, (1,), execution_profile="only_first") - self.assertEqual(results.one(), (1, )) + assert results.one() == (1, ) results = self.session.execute(prepared_statement_alternative, (2,), execution_profile="only_first") - self.assertEqual(results.one(), (2, )) + assert results.one() == (2, ) def test_prepared_not_found(self): """ @@ -1600,7 +1597,7 @@ def test_prepared_not_found(self): for _ in range(10): results = session.execute(prepared_statement, (1, )) - self.assertEqual(results.one(), (1,)) + assert results.one() == (1,) def test_prepared_in_query_keyspace(self): """ @@ -1619,12 +1616,12 @@ def test_prepared_in_query_keyspace(self): query = "SELECT k from {}.{} WHERE k = ?".format(self.ks_name, self.table_name) prepared_statement = session.prepare(query) results = session.execute(prepared_statement, (1,)) - self.assertEqual(results.one(), (1,)) + assert results.one() == (1,) query = "SELECT k from {}.{} WHERE k = ?".format(self.alternative_ks, self.table_name) prepared_statement = session.prepare(query) results = session.execute(prepared_statement, (2,)) - self.assertEqual(results.one(), (2,)) + assert results.one() == (2,) def test_prepared_in_query_keyspace_and_explicit(self): """ @@ -1641,9 +1638,9 @@ def test_prepared_in_query_keyspace_and_explicit(self): query = "SELECT k from {}.{} WHERE k = ?".format(self.ks_name, self.table_name) prepared_statement = self.session.prepare(query, keyspace="system") results = self.session.execute(prepared_statement, (1,)) - self.assertEqual(results.one(), (1,)) + assert results.one() == (1,) query = "SELECT k from {}.{} WHERE k = ?".format(self.ks_name, self.table_name) prepared_statement = self.session.prepare(query, keyspace=self.alternative_ks) results = self.session.execute(prepared_statement, (1,)) - self.assertEqual(results.one(), (1,)) + assert results.one() == (1,) diff --git a/tests/integration/standard/test_query_paging.py b/tests/integration/standard/test_query_paging.py index 26c1ca0da6..9748e9d88f 100644 --- a/tests/integration/standard/test_query_paging.py +++ b/tests/integration/standard/test_query_paging.py @@ -60,12 +60,12 @@ def test_paging(self): for fetch_size in (2, 3, 7, 10, 99, 100, 101, 10000): self.session.default_fetch_size = fetch_size - self.assertEqual(100, len(list(self.session.execute("SELECT * FROM test3rf.test")))) + assert 100 == len(list(self.session.execute("SELECT * FROM test3rf.test"))) statement = SimpleStatement("SELECT * FROM test3rf.test") - self.assertEqual(100, len(list(self.session.execute(statement)))) + assert 100 == len(list(self.session.execute(statement))) - self.assertEqual(100, len(list(self.session.execute(prepared)))) + assert 100 == len(list(self.session.execute(prepared))) def test_paging_state(self): """ @@ -93,7 +93,7 @@ def test_paging_state(self): if(len(result_set.current_rows) > 0): list_all_results.append(result_set.current_rows) - self.assertEqual(len(list_all_results), 100) + assert len(list_all_results) == 100 def test_paging_verify_writes(self): statements_and_params = zip(cycle(["INSERT INTO test3rf.test (k, v) VALUES (%s, 0)"]), @@ -111,8 +111,8 @@ def test_paging_verify_writes(self): result_array.add(result.k) result_set.add(result.v) - self.assertEqual(set(range(100)), result_array) - self.assertEqual(set([0]), result_set) + assert set(range(100)) == result_array + assert set([0]) == result_set statement = SimpleStatement("SELECT * FROM test3rf.test") results = self.session.execute(statement) @@ -122,8 +122,8 @@ def test_paging_verify_writes(self): result_array.add(result.k) result_set.add(result.v) - self.assertEqual(set(range(100)), result_array) - self.assertEqual(set([0]), result_set) + assert set(range(100)) == result_array + assert set([0]) == result_set results = self.session.execute(prepared) result_array = set() @@ -132,8 +132,8 @@ def test_paging_verify_writes(self): result_array.add(result.k) result_set.add(result.v) - self.assertEqual(set(range(100)), result_array) - self.assertEqual(set([0]), result_set) + assert set(range(100)) == result_array + assert set([0]) == result_set def test_paging_verify_with_composite_keys(self): ddl = ''' @@ -194,12 +194,12 @@ def test_async_paging(self): for fetch_size in (2, 3, 7, 10, 99, 100, 101, 10000): self.session.default_fetch_size = fetch_size - self.assertEqual(100, len(list(self.session.execute_async("SELECT * FROM test3rf.test").result()))) + assert 100 == len(list(self.session.execute_async("SELECT * FROM test3rf.test").result())) statement = SimpleStatement("SELECT * FROM test3rf.test") - self.assertEqual(100, len(list(self.session.execute_async(statement).result()))) + assert 100 == len(list(self.session.execute_async(statement).result())) - self.assertEqual(100, len(list(self.session.execute_async(prepared).result()))) + assert 100 == len(list(self.session.execute_async(prepared).result())) def test_async_paging_verify_writes(self): ddl = ''' @@ -292,8 +292,8 @@ def handle_error(err): future.add_callbacks(callback=handle_page, callback_args=(future, counter, number_of_calls), errback=handle_error) event.wait() - self.assertEqual(next(number_of_calls), 100 // fetch_size + 1) - self.assertEqual(next(counter), 100) + assert next(number_of_calls) == 100 // fetch_size + 1 + assert next(counter) == 100 # simple statement future = self.session.execute_async(SimpleStatement("SELECT * FROM test3rf.test"), timeout=20) @@ -304,8 +304,8 @@ def handle_error(err): future.add_callbacks(callback=handle_page, callback_args=(future, counter, number_of_calls), errback=handle_error) event.wait() - self.assertEqual(next(number_of_calls), 100 // fetch_size + 1) - self.assertEqual(next(counter), 100) + assert next(number_of_calls) == 100 // fetch_size + 1 + assert next(counter) == 100 # prepared statement future = self.session.execute_async(prepared, timeout=20) @@ -316,8 +316,8 @@ def handle_error(err): future.add_callbacks(callback=handle_page, callback_args=(future, counter, number_of_calls), errback=handle_error) event.wait() - self.assertEqual(next(number_of_calls), 100 // fetch_size + 1) - self.assertEqual(next(counter), 100) + assert next(number_of_calls) == 100 // fetch_size + 1 + assert next(counter) == 100 def test_concurrent_with_paging(self): statements_and_params = zip(cycle(["INSERT INTO test3rf.test (k, v) VALUES (%s, 0)"]), @@ -329,10 +329,10 @@ def test_concurrent_with_paging(self): for fetch_size in (2, 3, 7, 10, 99, 100, 101, 10000): self.session.default_fetch_size = fetch_size results = execute_concurrent_with_args(self.session, prepared, [None] * 10) - self.assertEqual(10, len(results)) + assert 10 == len(results) for (success, result) in results: self.assertTrue(success) - self.assertEqual(100, len(list(result))) + assert 100 == len(list(result)) def test_fetch_size(self): """ diff --git a/tests/integration/standard/test_rack_aware_policy.py b/tests/integration/standard/test_rack_aware_policy.py index 5d7a69642f..f31cb77ce8 100644 --- a/tests/integration/standard/test_rack_aware_policy.py +++ b/tests/integration/standard/test_rack_aware_policy.py @@ -66,7 +66,7 @@ def test_rack_aware(self): for i in range (10): bound = prepared.bind([i]) results = self.session.execute(bound) - self.assertEqual(results, [(i, i%5, i%2)]) + assert results == [(i, i%5, i%2)] coordinator = str(results.response_future.coordinator_host.endpoint) self.assertTrue(coordinator in set(["127.0.0.1:9042", "127.0.0.2:9042"])) @@ -75,15 +75,15 @@ def test_rack_aware(self): for i in range (10): bound = prepared.bind([i]) results = self.session.execute(bound) - self.assertEqual(results, [(i, i%5, i%2)]) + assert results == [(i, i%5, i%2)] coordinator =str(results.response_future.coordinator_host.endpoint) - self.assertEqual(coordinator, "127.0.0.1:9042") + assert coordinator == "127.0.0.1:9042" self.node1.stop(wait_other_notice=True, gently=True) for i in range (10): bound = prepared.bind([i]) results = self.session.execute(bound) - self.assertEqual(results, [(i, i%5, i%2)]) + assert results == [(i, i%5, i%2)] coordinator = str(results.response_future.coordinator_host.endpoint) self.assertTrue(coordinator in set(["127.0.0.3:9042", "127.0.0.4:9042"])) diff --git a/tests/integration/standard/test_rate_limit_exceeded.py b/tests/integration/standard/test_rate_limit_exceeded.py index 280d6426e1..2fa2953cb9 100644 --- a/tests/integration/standard/test_rate_limit_exceeded.py +++ b/tests/integration/standard/test_rate_limit_exceeded.py @@ -56,4 +56,4 @@ def execute_write(): with self.assertRaises(RateLimitReached) as context: execute_write() - self.assertEqual(context.exception.op_type, OperationType.Write) + assert context.exception.op_type == OperationType.Write diff --git a/tests/integration/standard/test_routing.py b/tests/integration/standard/test_routing.py index 7d6651cf8b..ae45e7ade4 100644 --- a/tests/integration/standard/test_routing.py +++ b/tests/integration/standard/test_routing.py @@ -50,7 +50,7 @@ def insert_select_token(self, insert, select, key_values): cass_token = s.execute(select, key_values).one()[0] token = s.cluster.metadata.token_map.token_class(cass_token) - self.assertEqual(my_token, token) + assert my_token == token def create_prepare(self, key_types): s = self.session diff --git a/tests/integration/standard/test_row_factories.py b/tests/integration/standard/test_row_factories.py index 413a6bf50b..44b33733bd 100644 --- a/tests/integration/standard/test_row_factories.py +++ b/tests/integration/standard/test_row_factories.py @@ -97,12 +97,12 @@ def test_tuple_factory(self): result = result.all() for row in result: - self.assertEqual(row[0], row[1]) + assert row[0] == row[1] - self.assertEqual(result[0][0], result[0][1]) - self.assertEqual(result[0][0], 1) - self.assertEqual(result[1][0], result[1][1]) - self.assertEqual(result[1][0], 2) + assert result[0][0] == result[0][1] + assert result[0][0] == 1 + assert result[1][0] == result[1][1] + assert result[1][0] == 2 def test_named_tuple_factory(self): result = self._results_from_row_factory(named_tuple_factory) @@ -110,12 +110,12 @@ def test_named_tuple_factory(self): result = result.all() for row in result: - self.assertEqual(row.k, row.v) + assert row.k == row.v - self.assertEqual(result[0].k, result[0].v) - self.assertEqual(result[0].k, 1) - self.assertEqual(result[1].k, result[1].v) - self.assertEqual(result[1].k, 2) + assert result[0].k == result[0].v + assert result[0].k == 1 + assert result[1].k == result[1].v + assert result[1].k == 2 def _test_dict_factory(self, row_factory, row_type): result = self._results_from_row_factory(row_factory) @@ -124,12 +124,12 @@ def _test_dict_factory(self, row_factory, row_type): result = result.all() for row in result: - self.assertEqual(row['k'], row['v']) + assert row['k'] == row['v'] - self.assertEqual(result[0]['k'], result[0]['v']) - self.assertEqual(result[0]['k'], 1) - self.assertEqual(result[1]['k'], result[1]['v']) - self.assertEqual(result[1]['k'], 2) + assert result[0]['k'] == result[0]['v'] + assert result[0]['k'] == 1 + assert result[1]['k'] == result[1]['v'] + assert result[1]['k'] == 2 def test_dict_factory(self): self._test_dict_factory(dict_factory, dict) @@ -166,7 +166,7 @@ def _gen_row_factory(rows): result = session.execute(self.select) self.assertIsInstance(result, ResultSet) first_row = result.one() - self.assertEqual(first_row[0], first_row[1]) + assert first_row[0] == first_row[1] class NamedTupleFactoryAndNumericColNamesTests(unittest.TestCase): diff --git a/tests/integration/standard/test_shard_aware.py b/tests/integration/standard/test_shard_aware.py index cf8f17e209..0680b0a458 100644 --- a/tests/integration/standard/test_shard_aware.py +++ b/tests/integration/standard/test_shard_aware.py @@ -120,13 +120,13 @@ def query_data(self, session, verify_in_tracing=True): bound = prepared.bind(('a', 'b')) results = session.execute(bound, trace=True) - self.assertEqual(results, [('a', 'b', 'c')]) + assert results == [('a', 'b', 'c')] if verify_in_tracing: self.verify_same_shard_in_tracing(results, "shard 0") bound = prepared.bind(('100002', 'f')) results = session.execute(bound, trace=True) - self.assertEqual(results, [('100002', 'f', 'g')]) + assert results == [('100002', 'f', 'g')] if verify_in_tracing: self.verify_same_shard_in_tracing(results, "shard 1") diff --git a/tests/integration/standard/test_single_interface.py b/tests/integration/standard/test_single_interface.py index 681e992477..802b42277e 100644 --- a/tests/integration/standard/test_single_interface.py +++ b/tests/integration/standard/test_single_interface.py @@ -52,11 +52,11 @@ def test_single_interface(self): hosts = self.cluster.metadata._hosts broadcast_rpc_ports = [] broadcast_ports = [] - self.assertEqual(len(hosts), 3) + assert len(hosts) == 3 for endpoint, host in hosts.items(): - self.assertEqual(endpoint.address, host.broadcast_rpc_address) - self.assertEqual(endpoint.port, host.broadcast_rpc_port) + assert endpoint.address == host.broadcast_rpc_address + assert endpoint.port == host.broadcast_rpc_port if host.broadcast_rpc_port in broadcast_rpc_ports: self.fail("Duplicate broadcast_rpc_port") @@ -70,4 +70,4 @@ def test_single_interface(self): consistency_level=ConsistencyLevel.ALL)) for pool in self.session.get_pools(): - self.assertEqual(1, pool.get_state()['open_count']) + assert 1 == pool.get_state()['open_count'] diff --git a/tests/integration/standard/test_types.py b/tests/integration/standard/test_types.py index eb50c7780a..df5a73b20b 100644 --- a/tests/integration/standard/test_types.py +++ b/tests/integration/standard/test_types.py @@ -72,7 +72,7 @@ def test_can_insert_blob_type_as_string(self): results = s.execute("SELECT * FROM blobstring").one() for expected, actual in zip(params, results): - self.assertEqual(expected, actual) + assert expected == actual def test_can_insert_blob_type_as_bytearray(self): """ @@ -87,7 +87,7 @@ def test_can_insert_blob_type_as_bytearray(self): results = s.execute("SELECT * FROM blobbytes").one() for expected, actual in zip(params, results): - self.assertEqual(expected, actual) + assert expected == actual @unittest.skipIf(not hasattr(cassandra, 'deserializers'), "Cython required for to test DesBytesTypeArray deserializer") def test_des_bytes_type_array(self): @@ -114,7 +114,7 @@ def test_des_bytes_type_array(self): results = s.execute("SELECT * FROM blobbytes2").one() for expected, actual in zip(params, results): - self.assertEqual(expected, actual) + assert expected == actual finally: if original is not None: cassandra.deserializers.DesBytesType=original @@ -149,7 +149,7 @@ def test_can_insert_primitive_datatypes(self): # verify data results = s.execute("SELECT {0} FROM alltypes WHERE zz=0".format(columns_string)).one() for expected, actual in zip(params, results): - self.assertEqual(actual, expected) + assert actual == expected # try the same thing sending one insert at the time s.execute("TRUNCATE alltypes;") @@ -169,7 +169,7 @@ def test_can_insert_primitive_datatypes(self): if isinstance(data_sample, ipaddress.IPv4Address) or isinstance(data_sample, ipaddress.IPv6Address): compare_value = str(data_sample) - self.assertEqual(result, compare_value) + assert result == compare_value # try the same thing with a prepared statement placeholders = ','.join(["?"] * len(col_names)) @@ -180,13 +180,13 @@ def test_can_insert_primitive_datatypes(self): # verify data results = s.execute("SELECT {0} FROM alltypes WHERE zz=0".format(columns_string)).one() for expected, actual in zip(params, results): - self.assertEqual(actual, expected) + assert actual == expected # verify data with prepared statement query select = s.prepare("SELECT {0} FROM alltypes WHERE zz=?".format(columns_string)) results = s.execute(select.bind([0])).one() for expected, actual in zip(params, results): - self.assertEqual(actual, expected) + assert actual == expected # verify data with with prepared statement, use dictionary with no explicit columns select = s.prepare("SELECT * FROM alltypes") @@ -194,7 +194,7 @@ def test_can_insert_primitive_datatypes(self): execution_profile=s.execution_profile_clone_update(EXEC_PROFILE_DEFAULT, row_factory=ordered_dict_factory)).one() for expected, actual in zip(params, results.values()): - self.assertEqual(actual, expected) + assert actual == expected c.shutdown() @@ -242,7 +242,7 @@ def test_can_insert_collection_datatypes(self): # verify data results = s.execute("SELECT {0} FROM allcoltypes WHERE zz=0".format(columns_string)).one() for expected, actual in zip(params, results): - self.assertEqual(actual, expected) + assert actual == expected # create the input for prepared statement params = [0] @@ -258,13 +258,13 @@ def test_can_insert_collection_datatypes(self): # verify data results = s.execute("SELECT {0} FROM allcoltypes WHERE zz=0".format(columns_string)).one() for expected, actual in zip(params, results): - self.assertEqual(actual, expected) + assert actual == expected # verify data with prepared statement query select = s.prepare("SELECT {0} FROM allcoltypes WHERE zz=?".format(columns_string)) results = s.execute(select.bind([0])).one() for expected, actual in zip(params, results): - self.assertEqual(actual, expected) + assert actual == expected # verify data with with prepared statement, use dictionary with no explicit columns select = s.prepare("SELECT * FROM allcoltypes") @@ -273,7 +273,7 @@ def test_can_insert_collection_datatypes(self): row_factory=ordered_dict_factory)).one() for expected, actual in zip(params, results.values()): - self.assertEqual(actual, expected) + assert actual == expected c.shutdown() @@ -323,12 +323,12 @@ def test_can_insert_empty_strings_and_nulls(self): # verify string types empty with simple statement results = s.execute("SELECT {0} FROM all_empty WHERE zz=3".format(columns_string)).one() for expected, actual in zip(expected_values.values(), results): - self.assertEqual(actual, expected) + assert actual == expected # verify string types empty with prepared statement results = s.execute(s.prepare("SELECT {0} FROM all_empty WHERE zz=?".format(columns_string)), [3]).one() for expected, actual in zip(expected_values.values(), results): - self.assertEqual(actual, expected) + assert actual == expected # non-string types shouldn't accept empty strings for col in non_string_columns: @@ -360,13 +360,13 @@ def test_can_insert_empty_strings_and_nulls(self): query = "SELECT {0} FROM all_empty WHERE zz=5".format(columns_string) results = s.execute(query).one() for col in results: - self.assertEqual(None, col) + assert None == col # check via prepared statement select = s.prepare("SELECT {0} FROM all_empty WHERE zz=?".format(columns_string)) results = s.execute(select.bind([5])).one() for col in results: - self.assertEqual(None, col) + assert None == col # do the same thing again, but use a prepared statement to insert the nulls s.execute(simple_insert, params) @@ -377,11 +377,11 @@ def test_can_insert_empty_strings_and_nulls(self): results = s.execute(query).one() for col in results: - self.assertEqual(None, col) + assert None == col results = s.execute(select.bind([5])).one() for col in results: - self.assertEqual(None, col) + assert None == col def test_can_insert_empty_values_for_int32(self): """ @@ -419,13 +419,13 @@ def test_timezone_aware_datetimes_are_timestamps(self): # test non-prepared statement s.execute("INSERT INTO tz_aware (a, b) VALUES ('key1', %s)", [dt]) result = s.execute("SELECT b FROM tz_aware WHERE a='key1'").one().b - self.assertEqual(dt.utctimetuple(), result.utctimetuple()) + assert dt.utctimetuple() == result.utctimetuple() # test prepared statement insert = s.prepare("INSERT INTO tz_aware (a, b) VALUES ('key2', ?)") s.execute(insert.bind([dt])) result = s.execute("SELECT b FROM tz_aware WHERE a='key2'").one().b - self.assertEqual(dt.utctimetuple(), result.utctimetuple()) + assert dt.utctimetuple() == result.utctimetuple() def test_can_insert_tuples(self): """ @@ -447,20 +447,20 @@ def test_can_insert_tuples(self): complete = ('foo', 123, True) s.execute("INSERT INTO tuple_type (a, b) VALUES (0, %s)", parameters=(complete,)) result = s.execute("SELECT b FROM tuple_type WHERE a=0").one() - self.assertEqual(complete, result.b) + assert complete == result.b partial = ('bar', 456) partial_result = partial + (None,) s.execute("INSERT INTO tuple_type (a, b) VALUES (1, %s)", parameters=(partial,)) result = s.execute("SELECT b FROM tuple_type WHERE a=1").one() - self.assertEqual(partial_result, result.b) + assert partial_result == result.b # test single value tuples subpartial = ('zoo',) subpartial_result = subpartial + (None, None) s.execute("INSERT INTO tuple_type (a, b) VALUES (2, %s)", parameters=(subpartial,)) result = s.execute("SELECT b FROM tuple_type WHERE a=2").one() - self.assertEqual(subpartial_result, result.b) + assert subpartial_result == result.b # test prepared statement prepared = s.prepare("INSERT INTO tuple_type (a, b) VALUES (?, ?)") @@ -472,9 +472,9 @@ def test_can_insert_tuples(self): self.assertRaises(ValueError, s.execute, prepared, parameters=(0, (1, 2, 3, 4, 5, 6))) prepared = s.prepare("SELECT b FROM tuple_type WHERE a=?") - self.assertEqual(complete, s.execute(prepared, (3,)).one().b) - self.assertEqual(partial_result, s.execute(prepared, (4,)).one().b) - self.assertEqual(subpartial_result, s.execute(prepared, (5,)).one().b) + assert complete == s.execute(prepared, (3,)).one().b + assert partial_result == s.execute(prepared, (4,)).one().b + assert subpartial_result == s.execute(prepared, (5,)).one().b c.shutdown() @@ -515,7 +515,7 @@ def test_can_insert_tuples_with_varying_lengths(self): s.execute("INSERT INTO tuple_lengths (k, v_%s) VALUES (0, %s)", (i, created_tuple)) result = s.execute("SELECT v_%s FROM tuple_lengths WHERE k=0", (i,)).one() - self.assertEqual(tuple(created_tuple), result['v_%s' % i]) + assert tuple(created_tuple) == result['v_%s' % i] c.shutdown() def test_can_insert_tuples_all_primitive_datatypes(self): @@ -543,7 +543,7 @@ def test_can_insert_tuples_all_primitive_datatypes(self): expected = tuple(values + [None] * (type_count - len(values))) s.execute("INSERT INTO tuple_primitive (k, v) VALUES (%s, %s)", (i, tuple(values))) result = s.execute("SELECT v FROM tuple_primitive WHERE k=%s", (i,)).one() - self.assertEqual(result.v, expected) + assert result.v == expected c.shutdown() def test_can_insert_tuples_all_collection_datatypes(self): @@ -598,7 +598,7 @@ def test_can_insert_tuples_all_collection_datatypes(self): s.execute("INSERT INTO tuple_non_primative (k, v_%s) VALUES (0, %s)", (i, created_tuple)) result = s.execute("SELECT v_%s FROM tuple_non_primative WHERE k=0", (i,)).one() - self.assertEqual(created_tuple, result['v_%s' % i]) + assert created_tuple == result['v_%s' % i] i += 1 # test tuple> @@ -607,7 +607,7 @@ def test_can_insert_tuples_all_collection_datatypes(self): s.execute("INSERT INTO tuple_non_primative (k, v_%s) VALUES (0, %s)", (i, created_tuple)) result = s.execute("SELECT v_%s FROM tuple_non_primative WHERE k=0", (i,)).one() - self.assertEqual(created_tuple, result['v_%s' % i]) + assert created_tuple == result['v_%s' % i] i += 1 # test tuple> @@ -621,7 +621,7 @@ def test_can_insert_tuples_all_collection_datatypes(self): s.execute("INSERT INTO tuple_non_primative (k, v_%s) VALUES (0, %s)", (i, created_tuple)) result = s.execute("SELECT v_%s FROM tuple_non_primative WHERE k=0", (i,)).one() - self.assertEqual(created_tuple, result['v_%s' % i]) + assert created_tuple == result['v_%s' % i] i += 1 c.shutdown() @@ -682,7 +682,7 @@ def test_can_insert_nested_tuples(self): # verify tuple was written and read correctly result = s.execute("SELECT v_%s FROM nested_tuples WHERE k=%s", (i, i)).one() - self.assertEqual(created_tuple, result['v_%s' % i]) + assert created_tuple == result['v_%s' % i] c.shutdown() def test_can_insert_tuples_with_nulls(self): @@ -701,16 +701,16 @@ def test_can_insert_tuples_with_nulls(self): s.execute(insert, [(None, None, None, None)]) result = s.execute("SELECT * FROM tuples_nulls WHERE k=0") - self.assertEqual((None, None, None, None), result.one().t) + assert (None, None, None, None) == result.one().t read = s.prepare("SELECT * FROM tuples_nulls WHERE k=0") - self.assertEqual((None, None, None, None), s.execute(read).one().t) + assert (None, None, None, None) == s.execute(read).one().t # also test empty strings where compatible s.execute(insert, [('', None, None, b'')]) result = s.execute("SELECT * FROM tuples_nulls WHERE k=0") - self.assertEqual(('', None, None, b''), result.one().t) - self.assertEqual(('', None, None, b''), s.execute(read).one().t) + assert ('', None, None, b'') == result.one().t + assert ('', None, None, b'') == s.execute(read).one().t def test_insert_collection_with_null_fails(self): """ @@ -781,14 +781,14 @@ def test_can_read_composite_type(self): # CompositeType string literals are split on ':' chars s.execute("INSERT INTO composites (a, b) VALUES (0, 'abc:123')") result = s.execute("SELECT * FROM composites WHERE a = 0").one() - self.assertEqual(0, result.a) - self.assertEqual(('abc', 123), result.b) + assert 0 == result.a + assert ('abc', 123) == result.b # CompositeType values can omit elements at the end s.execute("INSERT INTO composites (a, b) VALUES (0, 'abc')") result = s.execute("SELECT * FROM composites WHERE a = 0").one() - self.assertEqual(0, result.a) - self.assertEqual(('abc',), result.b) + assert 0 == result.a + assert ('abc',) == result.b @notprotocolv1 def test_special_float_cql_encoding(self): @@ -818,8 +818,8 @@ def verify_insert_select(ins_statement, sel_statement): self.assertTrue(math.isnan(row.f)) self.assertTrue(math.isnan(row.d)) else: - self.assertEqual(row.f, f) - self.assertEqual(row.d, f) + assert row.f == f + assert row.d == f # cql encoding verify_insert_select('INSERT INTO float_cql_encoding (f, d) VALUES (%s, %s)', @@ -891,8 +891,7 @@ def test_smoke_duration_values(self): results = self.session.execute("SELECT * FROM duration_smoke") v = results.one()[1] - self.assertEqual(Duration(month_day_value, month_day_value, nanosecond_value), v, - "Error encoding value {0},{0},{1}".format(month_day_value, nanosecond_value)) + assert Duration(month_day_value, month_day_value, nanosecond_value) == v, "Error encoding value {0},{0},{1}".format(month_day_value, nanosecond_value) self.assertRaises(ValueError, self.session.execute, prepared, (1, Duration(0, 0, int("8FFFFFFFFFFFFFF0", 16)))) @@ -948,16 +947,16 @@ def read_inserts_at_level(self, proto_ver): session = TestCluster(protocol_version=proto_ver).connect(self.keyspace_name) try: results = session.execute('select * from t').one() - self.assertEqual("[SortedSet([1, 2]), SortedSet([3, 5])]", str(results.v)) + assert "[SortedSet([1, 2]), SortedSet([3, 5])]" == str(results.v) results = session.execute('select * from u').one() - self.assertEqual("SortedSet([[1, 2], [3, 5]])", str(results.v)) + assert "SortedSet([[1, 2], [3, 5]])" == str(results.v) results = session.execute('select * from v').one() - self.assertEqual("{SortedSet([1, 2]): [1, 2, 3], SortedSet([3, 5]): [4, 5, 6]}", str(results.v)) + assert "{SortedSet([1, 2]): [1, 2, 3], SortedSet([3, 5]): [4, 5, 6]}" == str(results.v) results = session.execute('select * from w').one() - self.assertEqual("typ(v0=OrderedMapSerializedKey([(1, [1, 2, 3]), (2, [4, 5, 6])]), v1=[7, 8, 9])", str(results.v)) + assert "typ(v0=OrderedMapSerializedKey([(1, [1, 2, 3]), (2, [4, 5, 6])]), v1=[7, 8, 9])" == str(results.v) finally: session.cluster.shutdown() @@ -985,7 +984,7 @@ class TypeTestsVector(BasicSharedKeyspaceUnitTestCase): def _get_first_j(self, rs): rows = rs.all() - self.assertEqual(len(rows), 1) + assert len(rows) == 1 return rows[0].j def _get_row_simple(self, idx, table_name): @@ -1131,8 +1130,8 @@ def _random_tuple(): def test_round_trip_udts(self): def _udt_equal_test_fn(udt1, udt2): - self.assertEqual(udt1.a, udt2.a) - self.assertEqual(udt1.b, udt2.b) + assert udt1.a == udt2.a + assert udt1.b == udt2.b self.session.execute("create type {}.fixed_type (a int, b int)".format(self.keyspace_name)) self.session.execute("create type {}.mixed_type_one (a int, b varint)".format(self.keyspace_name)) diff --git a/tests/integration/standard/test_udts.py b/tests/integration/standard/test_udts.py index 7188bf3eb8..3165782ab5 100644 --- a/tests/integration/standard/test_udts.py +++ b/tests/integration/standard/test_udts.py @@ -86,8 +86,8 @@ def test_can_insert_unprepared_registered_udts(self): s.execute("INSERT INTO mytable (a, b) VALUES (%s, %s)", (0, User(42, 'bob'))) result = s.execute("SELECT b FROM mytable WHERE a=0") row = result.one() - self.assertEqual(42, row.b.age) - self.assertEqual('bob', row.b.name) + assert 42 == row.b.age + assert 'bob' == row.b.name self.assertTrue(type(row.b) is User) # use the same UDT name in a different keyspace @@ -105,8 +105,8 @@ def test_can_insert_unprepared_registered_udts(self): s.execute("INSERT INTO mytable (a, b) VALUES (%s, %s)", (0, User('Texas', True))) result = s.execute("SELECT b FROM mytable WHERE a=0") row = result.one() - self.assertEqual('Texas', row.b.state) - self.assertEqual(True, row.b.is_cool) + assert 'Texas' == row.b.state + assert True == row.b.is_cool self.assertTrue(type(row.b) is User) s.execute("DROP KEYSPACE udt_test_unprepared_registered2") @@ -151,16 +151,16 @@ def test_can_register_udt_before_connecting(self): s.execute("INSERT INTO udt_test_register_before_connecting.mytable (a, b) VALUES (%s, %s)", (0, User1(42, 'bob'))) result = s.execute("SELECT b FROM udt_test_register_before_connecting.mytable WHERE a=0") row = result.one() - self.assertEqual(42, row.b.age) - self.assertEqual('bob', row.b.name) + assert 42 == row.b.age + assert 'bob' == row.b.name self.assertTrue(type(row.b) is User1) # use the same UDT name in a different keyspace s.execute("INSERT INTO udt_test_register_before_connecting2.mytable (a, b) VALUES (%s, %s)", (0, User2('Texas', True))) result = s.execute("SELECT b FROM udt_test_register_before_connecting2.mytable WHERE a=0") row = result.one() - self.assertEqual('Texas', row.b.state) - self.assertEqual(True, row.b.is_cool) + assert 'Texas' == row.b.state + assert True == row.b.is_cool self.assertTrue(type(row.b) is User2) s.execute("DROP KEYSPACE udt_test_register_before_connecting") @@ -186,8 +186,8 @@ def test_can_insert_prepared_unregistered_udts(self): select = s.prepare("SELECT b FROM mytable WHERE a=?") result = s.execute(select, (0,)) row = result.one() - self.assertEqual(42, row.b.age) - self.assertEqual('bob', row.b.name) + assert 42 == row.b.age + assert 'bob' == row.b.name # use the same UDT name in a different keyspace s.execute(""" @@ -205,8 +205,8 @@ def test_can_insert_prepared_unregistered_udts(self): select = s.prepare("SELECT b FROM mytable WHERE a=?") result = s.execute(select, (0,)) row = result.one() - self.assertEqual('Texas', row.b.state) - self.assertEqual(True, row.b.is_cool) + assert 'Texas' == row.b.state + assert True == row.b.is_cool s.execute("DROP KEYSPACE udt_test_prepared_unregistered2") @@ -232,8 +232,8 @@ def test_can_insert_prepared_registered_udts(self): select = s.prepare("SELECT b FROM mytable WHERE a=?") result = s.execute(select, (0,)) row = result.one() - self.assertEqual(42, row.b.age) - self.assertEqual('bob', row.b.name) + assert 42 == row.b.age + assert 'bob' == row.b.name self.assertTrue(type(row.b) is User) # use the same UDT name in a different keyspace @@ -254,8 +254,8 @@ def test_can_insert_prepared_registered_udts(self): select = s.prepare("SELECT b FROM mytable WHERE a=?") result = s.execute(select, (0,)) row = result.one() - self.assertEqual('Texas', row.b.state) - self.assertEqual(True, row.b.is_cool) + assert 'Texas' == row.b.state + assert True == row.b.is_cool self.assertTrue(type(row.b) is User) s.execute("DROP KEYSPACE udt_test_prepared_registered2") @@ -280,15 +280,15 @@ def test_can_insert_udts_with_nulls(self): s.execute(insert, [User(None, None, None, None)]) results = s.execute("SELECT b FROM mytable WHERE a=0") - self.assertEqual((None, None, None, None), results.one().b) + assert (None, None, None, None) == results.one().b select = s.prepare("SELECT b FROM mytable WHERE a=0") - self.assertEqual((None, None, None, None), s.execute(select).one().b) + assert (None, None, None, None) == s.execute(select).one().b # also test empty strings s.execute(insert, [User('', None, None, bytes())]) results = s.execute("SELECT b FROM mytable WHERE a=0") - self.assertEqual(('', None, None, bytes()), results.one().b) + assert ('', None, None, bytes()) == results.one().b c.shutdown() @@ -328,7 +328,7 @@ def test_can_insert_udts_with_varying_lengths(self): # verify udt was written and read correctly, increase timeout to avoid the query failure on slow systems result = s.execute("SELECT v FROM mytable WHERE k=0").one() - self.assertEqual(created_udt, result.v) + assert created_udt == result.v c.shutdown() @@ -366,7 +366,7 @@ def nested_udt_verification_helper(self, session, max_nesting_depth, udts): # verify udt was written and read correctly result = session.execute("SELECT v_{0} FROM mytable WHERE k=0".format(i)).one() - self.assertEqual(udt, result["v_{0}".format(i)]) + assert udt == result["v_{0}".format(i)] # write udt via prepared statement insert = session.prepare("INSERT INTO mytable (k, v_{0}) VALUES (1, ?)".format(i)) @@ -374,7 +374,7 @@ def nested_udt_verification_helper(self, session, max_nesting_depth, udts): # verify udt was written and read correctly result = session.execute("SELECT v_{0} FROM mytable WHERE k=1".format(i)).one() - self.assertEqual(udt, result["v_{0}".format(i)]) + assert udt == result["v_{0}".format(i)] def _cluster_default_dict_factory(self): return TestCluster( @@ -442,7 +442,7 @@ def test_can_insert_nested_unregistered_udts(self): # verify udt was written and read correctly result = s.execute("SELECT v_{0} FROM mytable WHERE k=0".format(i)).one() - self.assertEqual(udt, result["v_{0}".format(i)]) + assert udt == result["v_{0}".format(i)] def test_can_insert_nested_registered_udts_with_different_namedtuples(self): """ @@ -534,7 +534,7 @@ def test_can_insert_udt_all_datatypes(self): row = results.one().b for expected, actual in zip(params, row): - self.assertEqual(expected, actual) + assert expected == actual c.shutdown() @@ -592,7 +592,7 @@ def test_can_insert_udt_all_collection_datatypes(self): row = results.one().b for expected, actual in zip(params, row): - self.assertEqual(expected, actual) + assert expected == actual c.shutdown() @@ -600,7 +600,7 @@ def insert_select_column(self, session, table_name, column_name, value): insert = session.prepare("INSERT INTO %s (k, %s) VALUES (?, ?)" % (table_name, column_name)) session.execute(insert, (0, value)) result = session.execute("SELECT %s FROM %s WHERE k=%%s" % (column_name, table_name), (0,)).one()[0] - self.assertEqual(result, value) + assert result == value def test_can_insert_nested_collections(self): """ @@ -669,15 +669,15 @@ def test_non_alphanum_identifiers(self): row = s.execute('SELECT * FROM %s' % (self.table_name,)).one() k, v = row.non_alphanum_type_map.popitem() - self.assertEqual(v, 0) - self.assertEqual(k.__class__, tuple) - self.assertEqual(k[0], 'nonalphanum') + assert v == 0 + assert k.__class__ == tuple + assert k[0] == 'nonalphanum' k, v = row.alphanum_type_map.popitem() - self.assertEqual(v, 1) + assert v == 1 self.assertNotEqual(k.__class__, tuple) # should be the namedtuple type - self.assertEqual(k[0], 'alphanum') - self.assertEqual(k.field_0_, 'alphanum') # named tuple with positional field name + assert k[0] == 'alphanum' + assert k.field_0_ == 'alphanum' # named tuple with positional field name @lessthancass30 def test_type_alteration(self): @@ -696,24 +696,24 @@ def test_type_alteration(self): s.cluster.register_user_type('udttests', type_name, dict) val = s.execute('SELECT v FROM %s' % self.table_name).one()[0] - self.assertEqual(val['v0'], 1) + assert val['v0'] == 1 # add field s.execute('ALTER TYPE %s ADD v1 text' % (type_name,)) val = s.execute('SELECT v FROM %s' % self.table_name).one()[0] - self.assertEqual(val['v0'], 1) + assert val['v0'] == 1 self.assertIsNone(val['v1']) s.execute("INSERT INTO %s (k, v) VALUES (0, {v0 : 2, v1 : 'sometext'})" % (self.table_name,)) val = s.execute('SELECT v FROM %s' % self.table_name).one()[0] - self.assertEqual(val['v0'], 2) - self.assertEqual(val['v1'], 'sometext') + assert val['v0'] == 2 + assert val['v1'] == 'sometext' # alter field type s.execute('ALTER TYPE %s ALTER v1 TYPE blob' % (type_name,)) s.execute("INSERT INTO %s (k, v) VALUES (0, {v0 : 3, v1 : 0xdeadbeef})" % (self.table_name,)) val = s.execute('SELECT v FROM %s' % self.table_name).one()[0] - self.assertEqual(val['v0'], 3) - self.assertEqual(val['v1'], b'\xde\xad\xbe\xef') + assert val['v0'] == 3 + assert val['v1'] == b'\xde\xad\xbe\xef' @lessthancass30 def test_alter_udt(self): diff --git a/tests/integration/upgrade/test_upgrade.py b/tests/integration/upgrade/test_upgrade.py index 25d14427f2..0fd6adfb66 100644 --- a/tests/integration/upgrade/test_upgrade.py +++ b/tests/integration/upgrade/test_upgrade.py @@ -57,9 +57,9 @@ def test_can_write(self): time.sleep(0.0001) total_number_of_inserted = self.session.execute("SELECT COUNT(*) from test3rf.test", execution_profile="all").one()[0] - self.assertEqual(total_number_of_inserted, next(c)) + assert total_number_of_inserted == next(c) - self.assertEqual(self.logger_handler.get_message_count("error", ""), 0) + assert self.logger_handler.get_message_count("error", "") == 0 @two_to_three_path def test_can_connect(self): @@ -80,9 +80,9 @@ def connect_and_shutdown(): for _ in range(10): results = session.execute("SELECT * from system.local WHERE key='local'") self.assertGreater(len(results.current_rows), 0) - self.assertEqual(len(results.response_future.attempted_hosts), 1) + assert len(results.response_future.attempted_hosts) == 1 queried_hosts.add(results.response_future.attempted_hosts[0]) - self.assertEqual(len(queried_hosts), 3) + assert len(queried_hosts) == 3 cluster.shutdown() connect_and_shutdown() @@ -116,9 +116,9 @@ def test_can_write(self): time.sleep(0.0001) total_number_of_inserted = self.session.execute("SELECT COUNT(*) from test3rf.test", execution_profile="all").one()[0] - self.assertEqual(total_number_of_inserted, next(c)) + assert total_number_of_inserted == next(c) - self.assertEqual(self.logger_handler.get_message_count("error", ""), 0) + assert self.logger_handler.get_message_count("error", "") == 0 @two_to_three_path def test_schema_metadata_gets_refreshed(self): @@ -177,9 +177,9 @@ def test_schema_nodes_gets_refreshed(self): def _assert_same_token_map(self, original, new): self.assertIsNot(original, new) - self.assertEqual(original.tokens_to_hosts_by_ks, new.tokens_to_hosts_by_ks) - self.assertEqual(original.token_to_host_owner, new.token_to_host_owner) - self.assertEqual(original.ring, new.ring) + assert original.tokens_to_hosts_by_ks == new.tokens_to_hosts_by_ks + assert original.token_to_host_owner == new.token_to_host_owner + assert original.ring == new.ring two_to_three_with_auth_path = upgrade_paths([ @@ -244,9 +244,9 @@ def connect_and_shutdown(self, auth_provider): for _ in range(10): results = session.execute("SELECT * from system.local WHERE key='local'") self.assertGreater(len(results.current_rows), 0) - self.assertEqual(len(results.response_future.attempted_hosts), 1) + assert len(results.response_future.attempted_hosts) == 1 queried_hosts.add(results.response_future.attempted_hosts[0]) - self.assertEqual(len(queried_hosts), 3) + assert len(queried_hosts) == 3 cluster.shutdown() @@ -280,6 +280,6 @@ def test_can_write_speculative(self): time.sleep(0.0001) total_number_of_inserted = session.execute("SELECT COUNT(*) from test3rf.test", execution_profile="all").one()[0] - self.assertEqual(total_number_of_inserted, next(c)) + assert total_number_of_inserted == next(c) - self.assertEqual(self.logger_handler.get_message_count("error", ""), 0) + assert self.logger_handler.get_message_count("error", "") == 0 diff --git a/tests/unit/advanced/test_execution_profile.py b/tests/unit/advanced/test_execution_profile.py index 478322f95b..bc51388c00 100644 --- a/tests/unit/advanced/test_execution_profile.py +++ b/tests/unit/advanced/test_execution_profile.py @@ -23,9 +23,9 @@ class GraphExecutionProfileTest(unittest.TestCase): def test_graph_source_can_be_set_with_graph_execution_profile(self): options = GraphOptions(graph_source='a') ep = GraphExecutionProfile(graph_options=options) - self.assertEqual(ep.graph_options.graph_source, b'a') + assert ep.graph_options.graph_source == b'a' def test_graph_source_is_preserve_with_graph_analytics_execution_profile(self): options = GraphOptions(graph_source='doesnt_matter') ep = GraphAnalyticsExecutionProfile(graph_options=options) - self.assertEqual(ep.graph_options.graph_source, b'a') # graph source is set automatically + assert ep.graph_options.graph_source == b'a' # graph source is set automatically diff --git a/tests/unit/advanced/test_geometry.py b/tests/unit/advanced/test_geometry.py index d85f1bc293..1360bfde05 100644 --- a/tests/unit/advanced/test_geometry.py +++ b/tests/unit/advanced/test_geometry.py @@ -35,12 +35,12 @@ def test_marshal_platform(self): for proto_ver in protocol_versions: for geo in self.samples: cql_type = lookup_casstype(geo.__class__.__name__ + 'Type') - self.assertEqual(cql_type.from_binary(cql_type.to_binary(geo, proto_ver), proto_ver), geo) + assert cql_type.from_binary(cql_type.to_binary(geo, proto_ver), proto_ver) == geo def _verify_both_endian(self, typ, body_fmt, params, expected): for proto_ver in protocol_versions: - self.assertEqual(typ.from_binary(struct.pack(">BI" + body_fmt, wkb_be, *params), proto_ver), expected) - self.assertEqual(typ.from_binary(struct.pack("BI" + body_fmt, wkb_be, *params), proto_ver) == expected + assert typ.from_binary(struct.pack(" base map base = GraphOptions(**self.api_params) - self.assertEqual(GraphOptions().get_options_map(base), base._graph_options) + assert GraphOptions().get_options_map(base) == base._graph_options # something set overrides kwargs = self.api_params.copy() # this test concept got strange after we added default values for a couple GraphOption attrs @@ -276,9 +276,9 @@ def test_get_options(self): other = GraphOptions(**kwargs) options = base.get_options_map(other) updated = self.opt_mapping['graph_name'] - self.assertEqual(options[updated], b'unit_test') + assert options[updated] == b'unit_test' for name in (n for n in self.opt_mapping.values() if n != updated): - self.assertEqual(options[name], base._graph_options[name]) + assert options[name] == base._graph_options[name] # base unchanged self._verify_api_params(base, self.api_params) @@ -286,24 +286,24 @@ def test_get_options(self): def test_set_attr(self): expected = 'test@@@@' opts = GraphOptions(graph_name=expected) - self.assertEqual(opts.graph_name, expected.encode()) + assert opts.graph_name == expected.encode() expected = 'somethingelse####' opts.graph_name = expected - self.assertEqual(opts.graph_name, expected.encode()) + assert opts.graph_name == expected.encode() # will update options with set value another = GraphOptions() self.assertIsNone(another.graph_name) another.update(opts) - self.assertEqual(another.graph_name, expected.encode()) + assert another.graph_name == expected.encode() opts.graph_name = None self.assertIsNone(opts.graph_name) # will not update another with its set-->unset value another.update(opts) - self.assertEqual(another.graph_name, expected.encode()) # remains unset + assert another.graph_name == expected.encode() # remains unset opt_map = another.get_options_map(opts) - self.assertEqual(opt_map, another._graph_options) + assert opt_map == another._graph_options def test_del_attr(self): opts = GraphOptions(**self.api_params) @@ -313,14 +313,14 @@ def test_del_attr(self): self._verify_api_params(opts, test_params) def _verify_api_params(self, opts, api_params): - self.assertEqual(len(opts._graph_options), len(api_params)) + assert len(opts._graph_options) == len(api_params) for name, value in api_params.items(): try: value = value.encode() except: pass # already bytes - self.assertEqual(getattr(opts, name), value) - self.assertEqual(opts._graph_options[self.opt_mapping[name]], value) + assert getattr(opts, name) == value + assert opts._graph_options[self.opt_mapping[name]] == value def test_consistency_levels(self): read_cl = ConsistencyLevel.ONE @@ -328,13 +328,13 @@ def test_consistency_levels(self): # set directly opts = GraphOptions(graph_read_consistency_level=read_cl, graph_write_consistency_level=write_cl) - self.assertEqual(opts.graph_read_consistency_level, read_cl) - self.assertEqual(opts.graph_write_consistency_level, write_cl) + assert opts.graph_read_consistency_level == read_cl + assert opts.graph_write_consistency_level == write_cl # mapping from base opt_map = opts.get_options_map() - self.assertEqual(opt_map['graph-read-consistency'], ConsistencyLevel.value_to_name[read_cl].encode()) - self.assertEqual(opt_map['graph-write-consistency'], ConsistencyLevel.value_to_name[write_cl].encode()) + assert opt_map['graph-read-consistency'] == ConsistencyLevel.value_to_name[read_cl].encode() + assert opt_map['graph-write-consistency'] == ConsistencyLevel.value_to_name[write_cl].encode() # empty by default new_opts = GraphOptions() @@ -344,12 +344,12 @@ def test_consistency_levels(self): # set from other opt_map = new_opts.get_options_map(opts) - self.assertEqual(opt_map['graph-read-consistency'], ConsistencyLevel.value_to_name[read_cl].encode()) - self.assertEqual(opt_map['graph-write-consistency'], ConsistencyLevel.value_to_name[write_cl].encode()) + assert opt_map['graph-read-consistency'] == ConsistencyLevel.value_to_name[read_cl].encode() + assert opt_map['graph-write-consistency'] == ConsistencyLevel.value_to_name[write_cl].encode() def test_graph_source_convenience_attributes(self): opts = GraphOptions() - self.assertEqual(opts.graph_source, b'g') + assert opts.graph_source == b'g' self.assertFalse(opts.is_analytics_source) self.assertTrue(opts.is_graph_source) self.assertFalse(opts.is_default_source) @@ -396,7 +396,7 @@ class GraphRowFactoryTests(unittest.TestCase): def test_object_row_factory(self): col_names = [] # unused rows = [object() for _ in range(10)] - self.assertEqual(single_object_row_factory(col_names, ((o,) for o in rows)), rows) + assert single_object_row_factory(col_names, ((o,) for o in rows)) == rows def test_graph_result_row_factory(self): col_names = [] # unused @@ -404,4 +404,4 @@ def test_graph_result_row_factory(self): results = graph_result_row_factory(col_names, ((o,) for o in rows)) for i, res in enumerate(results): self.assertIsInstance(res, Result) - self.assertEqual(res.value, i) + assert res.value == i diff --git a/tests/unit/advanced/test_insights.py b/tests/unit/advanced/test_insights.py index 4047fe12b8..066b591f64 100644 --- a/tests/unit/advanced/test_insights.py +++ b/tests/unit/advanced/test_insights.py @@ -66,15 +66,13 @@ class NoConfAsDict(object): # no default # ... as a policy - self.assertEqual(insights_registry.serialize(obj, policy=True), - {'type': 'NoConfAsDict', - 'namespace': ns, - 'options': {}}) + assert insights_registry.serialize(obj, policy=True) == {'type': 'NoConfAsDict', + 'namespace': ns, + 'options': {}} # ... not as a policy (default) - self.assertEqual(insights_registry.serialize(obj), - {'type': 'NoConfAsDict', - 'namespace': ns, - }) + assert insights_registry.serialize(obj) == {'type': 'NoConfAsDict', + 'namespace': ns, + } # with default self.assertIs(insights_registry.serialize(obj, default=sentinel.attr_err_default), sentinel.attr_err_default) @@ -116,190 +114,145 @@ def test_graph_options(self): log.debug(go._graph_options) - self.assertEqual( - insights_registry.serialize(go), - {'source': 'source_for_test', - 'language': 'lang_for_test', - 'graphProtocol': 'protocol_for_test', - # no graph_invalid_option - } - ) + assert insights_registry.serialize(go) == {'source': 'source_for_test', + 'language': 'lang_for_test', + 'graphProtocol': 'protocol_for_test', + # no graph_invalid_option + } # cluster.py def test_execution_profile(self): self.maxDiff = None - self.assertEqual( - insights_registry.serialize(ExecutionProfile()), - {'consistency': 'LOCAL_ONE', - 'continuousPagingOptions': None, - 'loadBalancing': {'namespace': 'cassandra.policies', - 'options': {'child_policy': {'namespace': 'cassandra.policies', - 'options': {'local_dc': '', - 'used_hosts_per_remote_dc': 0}, - 'type': 'DCAwareRoundRobinPolicy'}, - 'shuffle_replicas': False}, - 'type': 'TokenAwarePolicy'}, - 'readTimeout': 10.0, - 'retry': {'namespace': 'cassandra.policies', 'options': {}, 'type': 'RetryPolicy'}, - 'serialConsistency': None, - 'speculativeExecution': {'namespace': 'cassandra.policies', - 'options': {}, 'type': 'NoSpeculativeExecutionPolicy'}, - 'graphOptions': None - } - ) + assert insights_registry.serialize(ExecutionProfile()) == {'consistency': 'LOCAL_ONE', + 'continuousPagingOptions': None, + 'loadBalancing': {'namespace': 'cassandra.policies', + 'options': {'child_policy': {'namespace': 'cassandra.policies', + 'options': {'local_dc': '', + 'used_hosts_per_remote_dc': 0}, + 'type': 'DCAwareRoundRobinPolicy'}, + 'shuffle_replicas': False}, + 'type': 'TokenAwarePolicy'}, + 'readTimeout': 10.0, + 'retry': {'namespace': 'cassandra.policies', 'options': {}, 'type': 'RetryPolicy'}, + 'serialConsistency': None, + 'speculativeExecution': {'namespace': 'cassandra.policies', + 'options': {}, 'type': 'NoSpeculativeExecutionPolicy'}, + 'graphOptions': None + } def test_graph_execution_profile(self): self.maxDiff = None - self.assertEqual( - insights_registry.serialize(GraphExecutionProfile()), - {'consistency': 'LOCAL_ONE', - 'continuousPagingOptions': None, - 'loadBalancing': {'namespace': 'cassandra.policies', - 'options': {'child_policy': {'namespace': 'cassandra.policies', - 'options': {'local_dc': '', - 'used_hosts_per_remote_dc': 0}, - 'type': 'DCAwareRoundRobinPolicy'}, - 'shuffle_replicas': False}, - 'type': 'TokenAwarePolicy'}, - 'readTimeout': 30.0, - 'retry': {'namespace': 'cassandra.policies', 'options': {}, 'type': 'NeverRetryPolicy'}, - 'serialConsistency': None, - 'speculativeExecution': {'namespace': 'cassandra.policies', - 'options': {}, 'type': 'NoSpeculativeExecutionPolicy'}, - 'graphOptions': {'graphProtocol': None, - 'language': 'gremlin-groovy', - 'source': 'g'}, - } - ) + assert insights_registry.serialize(GraphExecutionProfile()) == {'consistency': 'LOCAL_ONE', + 'continuousPagingOptions': None, + 'loadBalancing': {'namespace': 'cassandra.policies', + 'options': {'child_policy': {'namespace': 'cassandra.policies', + 'options': {'local_dc': '', + 'used_hosts_per_remote_dc': 0}, + 'type': 'DCAwareRoundRobinPolicy'}, + 'shuffle_replicas': False}, + 'type': 'TokenAwarePolicy'}, + 'readTimeout': 30.0, + 'retry': {'namespace': 'cassandra.policies', 'options': {}, 'type': 'NeverRetryPolicy'}, + 'serialConsistency': None, + 'speculativeExecution': {'namespace': 'cassandra.policies', + 'options': {}, 'type': 'NoSpeculativeExecutionPolicy'}, + 'graphOptions': {'graphProtocol': None, + 'language': 'gremlin-groovy', + 'source': 'g'}, + } def test_graph_analytics_execution_profile(self): self.maxDiff = None - self.assertEqual( - insights_registry.serialize(GraphAnalyticsExecutionProfile()), - {'consistency': 'LOCAL_ONE', - 'continuousPagingOptions': None, - 'loadBalancing': {'namespace': 'cassandra.policies', - 'options': {'child_policy': {'namespace': 'cassandra.policies', - 'options': {'child_policy': {'namespace': 'cassandra.policies', - 'options': {'local_dc': '', - 'used_hosts_per_remote_dc': 0}, - 'type': 'DCAwareRoundRobinPolicy'}, - 'shuffle_replicas': False}, - 'type': 'TokenAwarePolicy'}}, - 'type': 'DefaultLoadBalancingPolicy'}, - 'readTimeout': 604800.0, - 'retry': {'namespace': 'cassandra.policies', 'options': {}, 'type': 'NeverRetryPolicy'}, - 'serialConsistency': None, - 'speculativeExecution': {'namespace': 'cassandra.policies', - 'options': {}, 'type': 'NoSpeculativeExecutionPolicy'}, - 'graphOptions': {'graphProtocol': None, - 'language': 'gremlin-groovy', - 'source': 'a'}, - } - ) + assert insights_registry.serialize(GraphAnalyticsExecutionProfile()) == {'consistency': 'LOCAL_ONE', + 'continuousPagingOptions': None, + 'loadBalancing': {'namespace': 'cassandra.policies', + 'options': {'child_policy': {'namespace': 'cassandra.policies', + 'options': {'child_policy': {'namespace': 'cassandra.policies', + 'options': {'local_dc': '', + 'used_hosts_per_remote_dc': 0}, + 'type': 'DCAwareRoundRobinPolicy'}, + 'shuffle_replicas': False}, + 'type': 'TokenAwarePolicy'}}, + 'type': 'DefaultLoadBalancingPolicy'}, + 'readTimeout': 604800.0, + 'retry': {'namespace': 'cassandra.policies', 'options': {}, 'type': 'NeverRetryPolicy'}, + 'serialConsistency': None, + 'speculativeExecution': {'namespace': 'cassandra.policies', + 'options': {}, 'type': 'NoSpeculativeExecutionPolicy'}, + 'graphOptions': {'graphProtocol': None, + 'language': 'gremlin-groovy', + 'source': 'a'}, + } # policies.py def test_DC_aware_round_robin_policy(self): - self.assertEqual( - insights_registry.serialize(DCAwareRoundRobinPolicy()), - {'namespace': 'cassandra.policies', - 'options': {'local_dc': '', 'used_hosts_per_remote_dc': 0}, - 'type': 'DCAwareRoundRobinPolicy'} - ) - self.assertEqual( - insights_registry.serialize(DCAwareRoundRobinPolicy(local_dc='fake_local_dc', - used_hosts_per_remote_dc=15)), - {'namespace': 'cassandra.policies', - 'options': {'local_dc': 'fake_local_dc', 'used_hosts_per_remote_dc': 15}, - 'type': 'DCAwareRoundRobinPolicy'} - ) + assert insights_registry.serialize(DCAwareRoundRobinPolicy()) == {'namespace': 'cassandra.policies', + 'options': {'local_dc': '', 'used_hosts_per_remote_dc': 0}, + 'type': 'DCAwareRoundRobinPolicy'} + assert insights_registry.serialize(DCAwareRoundRobinPolicy(local_dc='fake_local_dc', + used_hosts_per_remote_dc=15)) == {'namespace': 'cassandra.policies', + 'options': {'local_dc': 'fake_local_dc', 'used_hosts_per_remote_dc': 15}, + 'type': 'DCAwareRoundRobinPolicy'} def test_token_aware_policy(self): - self.assertEqual( - insights_registry.serialize(TokenAwarePolicy(child_policy=LoadBalancingPolicy())), - {'namespace': 'cassandra.policies', - 'options': {'child_policy': {'namespace': 'cassandra.policies', - 'options': {}, - 'type': 'LoadBalancingPolicy'}, - 'shuffle_replicas': False}, - 'type': 'TokenAwarePolicy'} - ) + assert insights_registry.serialize(TokenAwarePolicy(child_policy=LoadBalancingPolicy())) == {'namespace': 'cassandra.policies', + 'options': {'child_policy': {'namespace': 'cassandra.policies', + 'options': {}, + 'type': 'LoadBalancingPolicy'}, + 'shuffle_replicas': False}, + 'type': 'TokenAwarePolicy'} def test_whitelist_round_robin_policy(self): - self.assertEqual( - insights_registry.serialize(WhiteListRoundRobinPolicy(['127.0.0.3'])), - {'namespace': 'cassandra.policies', - 'options': {'allowed_hosts': ('127.0.0.3',)}, - 'type': 'WhiteListRoundRobinPolicy'} - ) + assert insights_registry.serialize(WhiteListRoundRobinPolicy(['127.0.0.3'])) == {'namespace': 'cassandra.policies', + 'options': {'allowed_hosts': ('127.0.0.3',)}, + 'type': 'WhiteListRoundRobinPolicy'} def test_host_filter_policy(self): def my_predicate(s): return False - self.assertEqual( - insights_registry.serialize(HostFilterPolicy(LoadBalancingPolicy(), my_predicate)), - {'namespace': 'cassandra.policies', - 'options': {'child_policy': {'namespace': 'cassandra.policies', - 'options': {}, - 'type': 'LoadBalancingPolicy'}, - 'predicate': 'my_predicate'}, - 'type': 'HostFilterPolicy'} - ) + assert insights_registry.serialize(HostFilterPolicy(LoadBalancingPolicy(), my_predicate)) == {'namespace': 'cassandra.policies', + 'options': {'child_policy': {'namespace': 'cassandra.policies', + 'options': {}, + 'type': 'LoadBalancingPolicy'}, + 'predicate': 'my_predicate'}, + 'type': 'HostFilterPolicy'} def test_constant_reconnection_policy(self): - self.assertEqual( - insights_registry.serialize(ConstantReconnectionPolicy(3, 200)), - {'type': 'ConstantReconnectionPolicy', - 'namespace': 'cassandra.policies', - 'options': {'delay': 3, 'max_attempts': 200} - } - ) + assert insights_registry.serialize(ConstantReconnectionPolicy(3, 200)) == {'type': 'ConstantReconnectionPolicy', + 'namespace': 'cassandra.policies', + 'options': {'delay': 3, 'max_attempts': 200} + } def test_exponential_reconnection_policy(self): - self.assertEqual( - insights_registry.serialize(ExponentialReconnectionPolicy(4, 100, 10)), - {'type': 'ExponentialReconnectionPolicy', - 'namespace': 'cassandra.policies', - 'options': {'base_delay': 4, 'max_delay': 100, 'max_attempts': 10} - } - ) + assert insights_registry.serialize(ExponentialReconnectionPolicy(4, 100, 10)) == {'type': 'ExponentialReconnectionPolicy', + 'namespace': 'cassandra.policies', + 'options': {'base_delay': 4, 'max_delay': 100, 'max_attempts': 10} + } def test_retry_policy(self): - self.assertEqual( - insights_registry.serialize(RetryPolicy()), - {'type': 'RetryPolicy', - 'namespace': 'cassandra.policies', - 'options': {} - } - ) + assert insights_registry.serialize(RetryPolicy()) == {'type': 'RetryPolicy', + 'namespace': 'cassandra.policies', + 'options': {} + } def test_spec_exec_policy(self): - self.assertEqual( - insights_registry.serialize(SpeculativeExecutionPolicy()), - {'type': 'SpeculativeExecutionPolicy', - 'namespace': 'cassandra.policies', - 'options': {} - } - ) + assert insights_registry.serialize(SpeculativeExecutionPolicy()) == {'type': 'SpeculativeExecutionPolicy', + 'namespace': 'cassandra.policies', + 'options': {} + } def test_constant_spec_exec_policy(self): - self.assertEqual( - insights_registry.serialize(ConstantSpeculativeExecutionPolicy(100, 101)), - {'type': 'ConstantSpeculativeExecutionPolicy', - 'namespace': 'cassandra.policies', - 'options': {'delay': 100, - 'max_attempts': 101} - } - ) + assert insights_registry.serialize(ConstantSpeculativeExecutionPolicy(100, 101)) == {'type': 'ConstantSpeculativeExecutionPolicy', + 'namespace': 'cassandra.policies', + 'options': {'delay': 100, + 'max_attempts': 101} + } def test_wrapper_policy(self): - self.assertEqual( - insights_registry.serialize(WrapperPolicy(LoadBalancingPolicy())), - {'namespace': 'cassandra.policies', - 'options': {'child_policy': {'namespace': 'cassandra.policies', - 'options': {}, - 'type': 'LoadBalancingPolicy'} - }, - 'type': 'WrapperPolicy'} - ) + assert insights_registry.serialize(WrapperPolicy(LoadBalancingPolicy())) == {'namespace': 'cassandra.policies', + 'options': {'child_policy': {'namespace': 'cassandra.policies', + 'options': {}, + 'type': 'LoadBalancingPolicy'} + }, + 'type': 'WrapperPolicy'} diff --git a/tests/unit/advanced/test_metadata.py b/tests/unit/advanced/test_metadata.py index 20f80b4da4..0ded90d974 100644 --- a/tests/unit/advanced/test_metadata.py +++ b/tests/unit/advanced/test_metadata.py @@ -48,7 +48,7 @@ def _create_table_metadata(self, with_vertex=False, with_edge=False): def test_keyspace_no_graph_engine(self): km = self._create_keyspace_metadata(None) - self.assertEqual(km.graph_engine, None) + assert km.graph_engine == None self.assertNotIn( "graph_engine", km.as_cql_query() @@ -57,7 +57,7 @@ def test_keyspace_no_graph_engine(self): def test_keyspace_with_graph_engine(self): graph_engine = 'Core' km = self._create_keyspace_metadata(graph_engine) - self.assertEqual(km.graph_engine, graph_engine) + assert km.graph_engine == graph_engine cql = km.as_cql_query() self.assertIn( "graph_engine", diff --git a/tests/unit/advanced/test_policies.py b/tests/unit/advanced/test_policies.py index 553e7dba87..d41791959f 100644 --- a/tests/unit/advanced/test_policies.py +++ b/tests/unit/advanced/test_policies.py @@ -37,7 +37,7 @@ def test_no_target(self): policy.populate(Mock(metadata=ClusterMetaMock()), hosts) for _ in range(node_count): query_plan = list(policy.make_query_plan(None, Mock(target_host=None))) - self.assertEqual(sorted(query_plan), hosts) + assert sorted(query_plan) == hosts def test_status_updates(self): node_count = 4 @@ -49,7 +49,7 @@ def test_status_updates(self): policy.on_up(4) policy.on_add(5) query_plan = list(policy.make_query_plan()) - self.assertEqual(sorted(query_plan), [2, 3, 4, 5]) + assert sorted(query_plan) == [2, 3, 4, 5] def test_no_live_nodes(self): hosts = [0, 1, 2, 3] @@ -60,7 +60,7 @@ def test_no_live_nodes(self): policy.on_down(i) query_plan = list(policy.make_query_plan()) - self.assertEqual(query_plan, []) + assert query_plan == [] def test_target_no_host(self): node_count = 4 @@ -68,7 +68,7 @@ def test_target_no_host(self): policy = DSELoadBalancingPolicy(RoundRobinPolicy()) policy.populate(Mock(metadata=ClusterMetaMock()), hosts) query_plan = list(policy.make_query_plan(None, Mock(target_host='127.0.0.1'))) - self.assertEqual(sorted(query_plan), hosts) + assert sorted(query_plan) == hosts def test_target_host_down(self): node_count = 4 @@ -78,7 +78,7 @@ def test_target_host_down(self): policy = DSELoadBalancingPolicy(RoundRobinPolicy()) policy.populate(Mock(metadata=ClusterMetaMock({'127.0.0.1': target_host})), hosts) query_plan = list(policy.make_query_plan(None, Mock(target_host='127.0.0.1'))) - self.assertEqual(sorted(query_plan), hosts) + assert sorted(query_plan) == hosts target_host.is_up = False policy.on_down(target_host) @@ -95,5 +95,5 @@ def test_target_host_nominal(self): policy.populate(Mock(metadata=ClusterMetaMock({'127.0.0.1': target_host})), hosts) for _ in range(10): query_plan = list(policy.make_query_plan(None, Mock(target_host='127.0.0.1'))) - self.assertEqual(sorted(query_plan), hosts) - self.assertEqual(query_plan[0], target_host) + assert sorted(query_plan) == hosts + assert query_plan[0] == target_host diff --git a/tests/unit/column_encryption/test_policies.py b/tests/unit/column_encryption/test_policies.py index 27e7c62ce7..8c6b57b3cf 100644 --- a/tests/unit/column_encryption/test_policies.py +++ b/tests/unit/column_encryption/test_policies.py @@ -33,7 +33,7 @@ def _test_round_trip(self, bytes): policy = AES256ColumnEncryptionPolicy() policy.add_column(coldesc, self._random_key(), "blob") encrypted_bytes = policy.encrypt(coldesc, bytes) - self.assertEqual(bytes, policy.decrypt(coldesc, encrypted_bytes)) + assert bytes == policy.decrypt(coldesc, encrypted_bytes) def test_no_padding_necessary(self): self._test_round_trip(self._random_block()) @@ -157,14 +157,14 @@ def test_cache_info(self): for _ in range(10): policy.encrypt(coldesc1, self._random_block()) cache_info = policy.cache_info() - self.assertEqual(cache_info.hits, 9) - self.assertEqual(cache_info.misses, 1) - self.assertEqual(cache_info.maxsize, 128) + assert cache_info.hits == 9 + assert cache_info.misses == 1 + assert cache_info.maxsize == 128 # Important note: we're measuring the size of the cache of ciphers, NOT stored # keys. We won't have a cipher here until we actually encrypt something - self.assertEqual(cache_info.currsize, 1) + assert cache_info.currsize == 1 policy.encrypt(coldesc2, self._random_block()) - self.assertEqual(policy.cache_info().currsize, 2) + assert policy.cache_info().currsize == 2 policy.encrypt(coldesc3, self._random_block()) - self.assertEqual(policy.cache_info().currsize, 3) + assert policy.cache_info().currsize == 3 diff --git a/tests/unit/cqlengine/test_columns.py b/tests/unit/cqlengine/test_columns.py index a7bf74ec23..6696337350 100644 --- a/tests/unit/cqlengine/test_columns.py +++ b/tests/unit/cqlengine/test_columns.py @@ -22,14 +22,14 @@ class ColumnTest(unittest.TestCase): def test_comparisons(self): c0 = Column() c1 = Column() - self.assertEqual(c1.position - c0.position, 1) + assert c1.position - c0.position == 1 # __ne__ self.assertNotEqual(c0, c1) self.assertNotEqual(c0, object()) # __eq__ - self.assertEqual(c0, c0) + assert c0 == c0 self.assertFalse(c0 == object()) # __lt__ @@ -64,5 +64,5 @@ def test_comparisons(self): def test_hash(self): c0 = Column() - self.assertEqual(id(c0), c0.__hash__()) + assert id(c0) == c0.__hash__() diff --git a/tests/unit/io/test_twistedreactor.py b/tests/unit/io/test_twistedreactor.py index fd17d8454f..2b89c5edf0 100644 --- a/tests/unit/io/test_twistedreactor.py +++ b/tests/unit/io/test_twistedreactor.py @@ -144,18 +144,17 @@ def test_handle_read__incomplete(self): Verify that handle_read() processes incomplete messages properly. """ self.obj_ut.process_msg = Mock() - self.assertEqual(self.obj_ut._iobuf.getvalue(), b'') # buf starts empty + assert self.obj_ut._iobuf.getvalue() == b'' # buf starts empty # incomplete header self.obj_ut._iobuf.write(b'\x84\x00\x00\x00\x00') self.obj_ut.handle_read() - self.assertEqual(self.obj_ut._io_buffer.cql_frame_buffer.getvalue(), b'\x84\x00\x00\x00\x00') + assert self.obj_ut._io_buffer.cql_frame_buffer.getvalue() == b'\x84\x00\x00\x00\x00' # full header, but incomplete body self.obj_ut._iobuf.write(b'\x00\x00\x00\x15') self.obj_ut.handle_read() - self.assertEqual(self.obj_ut._io_buffer.cql_frame_buffer.getvalue(), - b'\x84\x00\x00\x00\x00\x00\x00\x00\x15') - self.assertEqual(self.obj_ut._current_frame.end_pos, 30) + assert self.obj_ut._io_buffer.cql_frame_buffer.getvalue() == b'\x84\x00\x00\x00\x00\x00\x00\x00\x15' + assert self.obj_ut._current_frame.end_pos == 30 # verify we never attempted to process the incomplete message self.assertFalse(self.obj_ut.process_msg.called) @@ -165,7 +164,7 @@ def test_handle_read__fullmessage(self): Verify that handle_read() processes complete messages properly. """ self.obj_ut.process_msg = Mock() - self.assertEqual(self.obj_ut._iobuf.getvalue(), b'') # buf starts empty + assert self.obj_ut._iobuf.getvalue() == b'' # buf starts empty # write a complete message, plus 'NEXT' (to simulate next message) # assumes protocol v3+ as default Connection.protocol_version @@ -174,7 +173,7 @@ def test_handle_read__fullmessage(self): self.obj_ut._iobuf.write( b'\x84\x01\x00\x02\x03\x00\x00\x00\x15' + body + extra) self.obj_ut.handle_read() - self.assertEqual(self.obj_ut._io_buffer.cql_frame_buffer.getvalue(), extra) + assert self.obj_ut._io_buffer.cql_frame_buffer.getvalue() == extra self.obj_ut.process_msg.assert_called_with( _Frame(version=4, flags=1, stream=2, opcode=3, body_offset=9, end_pos=9 + len(body)), body) diff --git a/tests/unit/io/utils.py b/tests/unit/io/utils.py index fa9017ffa2..34d676dd9f 100644 --- a/tests/unit/io/utils.py +++ b/tests/unit/io/utils.py @@ -310,7 +310,7 @@ def chunk(size): # Ensure the message size is the good one and that the # message has been processed if it is non-empty - self.assertEqual(c._io_buffer.io_buffer.tell(), expected_size) + assert c._io_buffer.io_buffer.tell() == expected_size if expected_size == 0: c.process_io_buffer.assert_not_called() else: @@ -400,9 +400,8 @@ def test_partial_send(self): size_mod = msg_size % write_size last_write_size = size_mod if size_mod else write_size self.assertFalse(c.is_defunct) - self.assertEqual(expected_writes, self.get_socket(c).send.call_count) - self.assertEqual(last_write_size, - len(self.get_socket(c).send.call_args[0][0])) + assert expected_writes == self.get_socket(c).send.call_count + assert last_write_size == len(self.get_socket(c).send.call_args[0][0]) def test_socket_error_on_read(self): c = self.make_connection() @@ -429,11 +428,11 @@ def test_partial_header_read(self): self.get_socket(c).recv.return_value = message[0:1] c.handle_read(*self.null_handle_function_args) - self.assertEqual(c._io_buffer.cql_frame_buffer.getvalue(), message[0:1]) + assert c._io_buffer.cql_frame_buffer.getvalue() == message[0:1] self.get_socket(c).recv.return_value = message[1:] c.handle_read(*self.null_handle_function_args) - self.assertEqual(bytes(), c._io_buffer.io_buffer.getvalue()) + assert bytes() == c._io_buffer.io_buffer.getvalue() # let it write out a StartupMessage c.handle_write(*self.null_handle_function_args) @@ -455,12 +454,12 @@ def test_partial_message_read(self): # read in the first nine bytes self.get_socket(c).recv.return_value = message[:9] c.handle_read(*self.null_handle_function_args) - self.assertEqual(c._io_buffer.cql_frame_buffer.getvalue(), message[:9]) + assert c._io_buffer.cql_frame_buffer.getvalue() == message[:9] # ... then read in the rest self.get_socket(c).recv.return_value = message[9:] c.handle_read(*self.null_handle_function_args) - self.assertEqual(bytes(), c._io_buffer.io_buffer.getvalue()) + assert bytes() == c._io_buffer.io_buffer.getvalue() # let it write out a StartupMessage c.handle_write(*self.null_handle_function_args) diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py index 0a2427c7ff..776cbd6973 100644 --- a/tests/unit/test_auth.py +++ b/tests/unit/test_auth.py @@ -22,7 +22,4 @@ class TestPlainTextAuthenticator(unittest.TestCase): def test_evaluate_challenge_with_unicode_data(self): authenticator = PlainTextAuthenticator("johnӁ", "doeӁ") - self.assertEqual( - authenticator.evaluate_challenge(b'PLAIN-START'), - "\x00johnӁ\x00doeӁ".encode('utf-8') - ) + assert authenticator.evaluate_challenge(b'PLAIN-START') == "\x00johnӁ\x00doeӁ".encode('utf-8') diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index e656dad005..5d0860251a 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -92,12 +92,12 @@ def test_tuple_for_contact_points(self): localhost_addr = set([addr[0] for addr in [t for (_,_,_,_,t) in socket.getaddrinfo("localhost",80)]]) for cp in cluster.endpoints_resolved: if cp.address in localhost_addr: - self.assertEqual(cp.port, 9045) + assert cp.port == 9045 elif cp.address == '127.0.0.2': - self.assertEqual(cp.port, 9046) + assert cp.port == 9046 else: - self.assertEqual(cp.address, '127.0.0.3') - self.assertEqual(cp.port, 9999) + assert cp.address == '127.0.0.3' + assert cp.port == 9999 def test_invalid_contact_point_types(self): with self.assertRaises(ValueError): @@ -110,7 +110,7 @@ def test_port_str(self): cluster = Cluster(contact_points=['127.0.0.1'], port='1111') for cp in cluster.endpoints_resolved: if cp.address in ('::1', '127.0.0.1'): - self.assertEqual(cp.port, 1111) + assert cp.port == 1111 with self.assertRaises(ValueError): cluster = Cluster(contact_points=['127.0.0.1'], port='string') @@ -166,13 +166,13 @@ def test_default_serial_consistency_level_ep(self, *_): # default is passed through f = s.execute_async(query='') - self.assertEqual(f.message.serial_consistency_level, cl) + assert f.message.serial_consistency_level == cl # any non-None statement setting takes precedence for cl_override in (ConsistencyLevel.LOCAL_SERIAL, ConsistencyLevel.SERIAL): f = s.execute_async(SimpleStatement(query_string='', serial_consistency_level=cl_override)) - self.assertEqual(default_profile.serial_consistency_level, cl) - self.assertEqual(f.message.serial_consistency_level, cl_override) + assert default_profile.serial_consistency_level == cl + assert f.message.serial_consistency_level == cl_override @mock_session_pools def test_default_serial_consistency_level_legacy(self, *_): @@ -200,23 +200,23 @@ def test_default_serial_consistency_level_legacy(self, *_): # any non-None statement setting takes precedence for cl_override in (ConsistencyLevel.LOCAL_SERIAL, ConsistencyLevel.SERIAL): f = s.execute_async(SimpleStatement(query_string='', serial_consistency_level=cl_override)) - self.assertEqual(s.default_serial_consistency_level, cl) - self.assertEqual(f.message.serial_consistency_level, cl_override) + assert s.default_serial_consistency_level == cl + assert f.message.serial_consistency_level == cl_override class ProtocolVersionTests(unittest.TestCase): def test_protocol_downgrade_test(self): lower = ProtocolVersion.get_lower_supported(ProtocolVersion.DSE_V2) - self.assertEqual(ProtocolVersion.DSE_V1, lower) + assert ProtocolVersion.DSE_V1 == lower lower = ProtocolVersion.get_lower_supported(ProtocolVersion.DSE_V1) - self.assertEqual(ProtocolVersion.V5,lower) + assert ProtocolVersion.V5 == lower lower = ProtocolVersion.get_lower_supported(ProtocolVersion.V5) - self.assertEqual(ProtocolVersion.V4,lower) + assert ProtocolVersion.V4 == lower lower = ProtocolVersion.get_lower_supported(ProtocolVersion.V4) - self.assertEqual(ProtocolVersion.V3,lower) + assert ProtocolVersion.V3 == lower lower = ProtocolVersion.get_lower_supported(ProtocolVersion.V3) - self.assertEqual(0, lower) + assert 0 == lower self.assertTrue(ProtocolVersion.uses_error_code_map(ProtocolVersion.DSE_V1)) self.assertTrue(ProtocolVersion.uses_int_query_flags(ProtocolVersion.DSE_V1)) @@ -232,35 +232,35 @@ def setUp(self): connection_class.initialize_reactor() def _verify_response_future_profile(self, rf, prof): - self.assertEqual(rf._load_balancer, prof.load_balancing_policy) - self.assertEqual(rf._retry_policy, prof.retry_policy) - self.assertEqual(rf.message.consistency_level, prof.consistency_level) - self.assertEqual(rf.message.serial_consistency_level, prof.serial_consistency_level) - self.assertEqual(rf.timeout, prof.request_timeout) - self.assertEqual(rf.row_factory, prof.row_factory) + assert rf._load_balancer == prof.load_balancing_policy + assert rf._retry_policy == prof.retry_policy + assert rf.message.consistency_level == prof.consistency_level + assert rf.message.serial_consistency_level == prof.serial_consistency_level + assert rf.timeout == prof.request_timeout + assert rf.row_factory == prof.row_factory @mock_session_pools def test_default_exec_parameters(self): cluster = Cluster() - self.assertEqual(cluster._config_mode, _ConfigMode.UNCOMMITTED) - self.assertEqual(cluster.load_balancing_policy.__class__, default_lbp_factory().__class__) - self.assertEqual(cluster.profile_manager.default.load_balancing_policy.__class__, default_lbp_factory().__class__) - self.assertEqual(cluster.default_retry_policy.__class__, RetryPolicy) - self.assertEqual(cluster.profile_manager.default.retry_policy.__class__, RetryPolicy) + assert cluster._config_mode == _ConfigMode.UNCOMMITTED + assert cluster.load_balancing_policy.__class__ == default_lbp_factory().__class__ + assert cluster.profile_manager.default.load_balancing_policy.__class__ == default_lbp_factory().__class__ + assert cluster.default_retry_policy.__class__ == RetryPolicy + assert cluster.profile_manager.default.retry_policy.__class__ == RetryPolicy session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) - self.assertEqual(session.default_timeout, 10.0) - self.assertEqual(cluster.profile_manager.default.request_timeout, 10.0) - self.assertEqual(session.default_consistency_level, ConsistencyLevel.LOCAL_ONE) - self.assertEqual(cluster.profile_manager.default.consistency_level, ConsistencyLevel.LOCAL_ONE) - self.assertEqual(session.default_serial_consistency_level, None) - self.assertEqual(cluster.profile_manager.default.serial_consistency_level, None) - self.assertEqual(session.row_factory, named_tuple_factory) - self.assertEqual(cluster.profile_manager.default.row_factory, named_tuple_factory) + assert session.default_timeout == 10.0 + assert cluster.profile_manager.default.request_timeout == 10.0 + assert session.default_consistency_level == ConsistencyLevel.LOCAL_ONE + assert cluster.profile_manager.default.consistency_level == ConsistencyLevel.LOCAL_ONE + assert session.default_serial_consistency_level == None + assert cluster.profile_manager.default.serial_consistency_level == None + assert session.row_factory == named_tuple_factory + assert cluster.profile_manager.default.row_factory == named_tuple_factory @mock_session_pools def test_default_legacy(self): cluster = Cluster(load_balancing_policy=RoundRobinPolicy(), default_retry_policy=DowngradingConsistencyRetryPolicy()) - self.assertEqual(cluster._config_mode, _ConfigMode.LEGACY) + assert cluster._config_mode == _ConfigMode.LEGACY session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) session.default_timeout = 3.7 session.default_consistency_level = ConsistencyLevel.ALL @@ -277,7 +277,7 @@ def test_default_profile(self): cluster = Cluster(execution_profiles={'non-default': non_default_profile}) session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) - self.assertEqual(cluster._config_mode, _ConfigMode.PROFILES) + assert cluster._config_mode == _ConfigMode.PROFILES default_profile = cluster.profile_manager.profiles[EXEC_PROFILE_DEFAULT] rf = session.execute_async("query") @@ -287,7 +287,7 @@ def test_default_profile(self): self._verify_response_future_profile(rf, non_default_profile) for name, ep in cluster.profile_manager.profiles.items(): - self.assertEqual(ep, session.get_execution_profile(name)) + assert ep == session.get_execution_profile(name) # invalid ep with self.assertRaises(ValueError): @@ -307,7 +307,7 @@ def test_serial_consistency_level_validation(self): @mock_session_pools def test_statement_params_override_legacy(self): cluster = Cluster(load_balancing_policy=RoundRobinPolicy(), default_retry_policy=DowngradingConsistencyRetryPolicy()) - self.assertEqual(cluster._config_mode, _ConfigMode.LEGACY) + assert cluster._config_mode == _ConfigMode.LEGACY session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) ss = SimpleStatement("query", retry_policy=DowngradingConsistencyRetryPolicy(), @@ -331,7 +331,7 @@ def test_statement_params_override_profile(self): cluster = Cluster(execution_profiles={'non-default': non_default_profile}) session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) - self.assertEqual(cluster._config_mode, _ConfigMode.PROFILES) + assert cluster._config_mode == _ConfigMode.PROFILES rf = session.execute_async("query", execution_profile='non-default') @@ -399,7 +399,7 @@ def test_profile_name_value(self): internalized_profile = ExecutionProfile(RoundRobinPolicy(), *[object() for _ in range(2)]) cluster = Cluster(execution_profiles={'by-name': internalized_profile}) session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) - self.assertEqual(cluster._config_mode, _ConfigMode.PROFILES) + assert cluster._config_mode == _ConfigMode.PROFILES rf = session.execute_async("query", execution_profile='by-name') self._verify_response_future_profile(rf, internalized_profile) @@ -431,7 +431,7 @@ def test_exec_profile_clone(self): all_updated = session.execution_profile_clone_update(clone, **profile_attrs) self.assertIsNot(all_updated, clone) for attr, value in profile_attrs.items(): - self.assertEqual(getattr(clone, attr), getattr(active, attr)) + assert getattr(clone, attr) == getattr(active, attr) if attr in reference_attributes: self.assertIs(getattr(clone, attr), getattr(active, attr)) self.assertNotEqual(getattr(all_updated, attr), getattr(active, attr)) diff --git a/tests/unit/test_concurrent.py b/tests/unit/test_concurrent.py index bdfd08126e..e3f313e249 100644 --- a/tests/unit/test_concurrent.py +++ b/tests/unit/test_concurrent.py @@ -251,7 +251,7 @@ def test_recursion_limited(self): self.assertRaises(TypeError, execute_concurrent_with_args, s, "doesn't matter", [('param',)] * max_recursion, raise_on_first_error=True) results = execute_concurrent_with_args(s, "doesn't matter", [('param',)] * max_recursion, raise_on_first_error=False) # previously - self.assertEqual(len(results), max_recursion) + assert len(results) == max_recursion for r in results: self.assertFalse(r[0]) self.assertIsInstance(r[1], TypeError) diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 17b23b5ed0..8df7609bfc 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -67,17 +67,17 @@ def make_msg(self, header, body=""): def test_connection_endpoint(self): endpoint = DefaultEndPoint('1.2.3.4') c = Connection(endpoint) - self.assertEqual(c.endpoint, endpoint) - self.assertEqual(c.endpoint.address, endpoint.address) + assert c.endpoint == endpoint + assert c.endpoint.address == endpoint.address c = Connection(host=endpoint) # kwarg - self.assertEqual(c.endpoint, endpoint) - self.assertEqual(c.endpoint.address, endpoint.address) + assert c.endpoint == endpoint + assert c.endpoint.address == endpoint.address c = Connection('10.0.0.1') endpoint = DefaultEndPoint('10.0.0.1') - self.assertEqual(c.endpoint, endpoint) - self.assertEqual(c.endpoint.address, endpoint.address) + assert c.endpoint == endpoint + assert c.endpoint.address == endpoint.address def test_bad_protocol_version(self, *args): c = self.make_connection() @@ -155,7 +155,7 @@ def test_prefer_lz4_compression(self, *args): c.process_msg(_Frame(version=4, flags=0, stream=0, opcode=SupportedMessage.opcode, body_offset=9, end_pos=9 + len(options)), options) - self.assertEqual(c.decompressor, locally_supported_compressions['lz4'][1]) + assert c.decompressor == locally_supported_compressions['lz4'][1] def test_requested_compression_not_available(self, *args): c = self.make_connection() @@ -206,7 +206,7 @@ def test_use_requested_compression(self, *args): c.process_msg(_Frame(version=4, flags=0, stream=0, opcode=SupportedMessage.opcode, body_offset=9, end_pos=9 + len(options)), options) - self.assertEqual(c.decompressor, locally_supported_compressions['snappy'][1]) + assert c.decompressor == locally_supported_compressions['snappy'][1] def test_disable_compression(self, *args): c = self.make_connection() @@ -234,7 +234,7 @@ def test_disable_compression(self, *args): message = self.make_msg(header, options) c.process_msg(message, len(message) - 8) - self.assertEqual(c.decompressor, None) + assert c.decompressor == None def test_not_implemented(self): """ @@ -246,17 +246,17 @@ def test_not_implemented(self): def test_set_keyspace_blocking(self): c = self.make_connection() - self.assertEqual(c.keyspace, None) + assert c.keyspace == None c.set_keyspace_blocking(None) - self.assertEqual(c.keyspace, None) + assert c.keyspace == None c.keyspace = 'ks' c.set_keyspace_blocking('ks') - self.assertEqual(c.keyspace, 'ks') + assert c.keyspace == 'ks' def test_set_connection_class(self): cluster = Cluster(connection_class='test') - self.assertEqual('test', cluster.connection_class) + assert 'test' == cluster.connection_class @patch('cassandra.connection.ConnectionHeartbeat._raise_if_stopped') @@ -315,11 +315,11 @@ def send_msg(msg, req_id, msg_callback): self.run_heartbeat(get_holders) holder.get_connections.assert_has_calls([call()] * get_holders.call_count) - self.assertEqual(idle_connection.in_flight, 0) - self.assertEqual(non_idle_connection.in_flight, 0) + assert idle_connection.in_flight == 0 + assert non_idle_connection.in_flight == 0 idle_connection.send_msg.assert_has_calls([call(ANY, request_id, ANY)] * get_holders.call_count) - self.assertEqual(non_idle_connection.send_msg.call_count, 0) + assert non_idle_connection.send_msg.call_count == 0 def test_closed_defunct(self, *args): get_holders = self.make_get_holders(1) @@ -332,10 +332,10 @@ def test_closed_defunct(self, *args): self.run_heartbeat(get_holders) holder.get_connections.assert_has_calls([call()] * get_holders.call_count) - self.assertEqual(closed_connection.in_flight, 0) - self.assertEqual(defunct_connection.in_flight, 0) - self.assertEqual(closed_connection.send_msg.call_count, 0) - self.assertEqual(defunct_connection.send_msg.call_count, 0) + assert closed_connection.in_flight == 0 + assert defunct_connection.in_flight == 0 + assert closed_connection.send_msg.call_count == 0 + assert defunct_connection.send_msg.call_count == 0 def test_no_req_ids(self, *args): in_flight = 3 @@ -351,9 +351,9 @@ def test_no_req_ids(self, *args): self.run_heartbeat(get_holders) holder.get_connections.assert_has_calls([call()] * get_holders.call_count) - self.assertEqual(max_connection.in_flight, in_flight) - self.assertEqual(max_connection.send_msg.call_count, 0) - self.assertEqual(max_connection.send_msg.call_count, 0) + assert max_connection.in_flight == in_flight + assert max_connection.send_msg.call_count == 0 + assert max_connection.send_msg.call_count == 0 max_connection.defunct.assert_has_calls([call(ANY)] * get_holders.call_count) holder.return_connection.assert_has_calls( [call(max_connection)] * get_holders.call_count) @@ -378,7 +378,7 @@ def send_msg(msg, req_id, msg_callback): self.run_heartbeat(get_holders) - self.assertEqual(connection.in_flight, get_holders.call_count) + assert connection.in_flight == get_holders.call_count connection.send_msg.assert_has_calls([call(ANY, request_id, ANY)] * get_holders.call_count) connection.defunct.assert_has_calls([call(ANY)] * get_holders.call_count) exc = connection.defunct.call_args_list[0][0][0] @@ -408,13 +408,13 @@ def send_msg(msg, req_id, msg_callback): self.run_heartbeat(get_holders) - self.assertEqual(connection.in_flight, get_holders.call_count) + assert connection.in_flight == get_holders.call_count connection.send_msg.assert_has_calls([call(ANY, request_id, ANY)] * get_holders.call_count) connection.defunct.assert_has_calls([call(ANY)] * get_holders.call_count) exc = connection.defunct.call_args_list[0][0][0] self.assertIsInstance(exc, OperationTimedOut) - self.assertEqual(exc.errors, 'Connection heartbeat timeout after 0.05 seconds') - self.assertEqual(exc.last_host, DefaultEndPoint('localhost')) + assert exc.errors == 'Connection heartbeat timeout after 0.05 seconds' + assert exc.last_host == DefaultEndPoint('localhost') holder.return_connection.assert_has_calls( [call(connection)] * get_holders.call_count) @@ -439,25 +439,19 @@ class DefaultEndPointTest(unittest.TestCase): def test_default_endpoint_properties(self): endpoint = DefaultEndPoint('10.0.0.1') - self.assertEqual(endpoint.address, '10.0.0.1') - self.assertEqual(endpoint.port, 9042) - self.assertEqual(str(endpoint), '10.0.0.1:9042') + assert endpoint.address == '10.0.0.1' + assert endpoint.port == 9042 + assert str(endpoint) == '10.0.0.1:9042' endpoint = DefaultEndPoint('10.0.0.1', 8888) - self.assertEqual(endpoint.address, '10.0.0.1') - self.assertEqual(endpoint.port, 8888) - self.assertEqual(str(endpoint), '10.0.0.1:8888') + assert endpoint.address == '10.0.0.1' + assert endpoint.port == 8888 + assert str(endpoint) == '10.0.0.1:8888' def test_endpoint_equality(self): - self.assertEqual( - DefaultEndPoint('10.0.0.1'), - DefaultEndPoint('10.0.0.1') - ) + assert DefaultEndPoint('10.0.0.1') == DefaultEndPoint('10.0.0.1') - self.assertEqual( - DefaultEndPoint('10.0.0.1'), - DefaultEndPoint('10.0.0.1', 9042) - ) + assert DefaultEndPoint('10.0.0.1') == DefaultEndPoint('10.0.0.1', 9042) self.assertNotEqual( DefaultEndPoint('10.0.0.1'), @@ -470,15 +464,9 @@ def test_endpoint_equality(self): ) def test_endpoint_resolve(self): - self.assertEqual( - DefaultEndPoint('10.0.0.1').resolve(), - ('10.0.0.1', 9042) - ) + assert DefaultEndPoint('10.0.0.1').resolve() == ('10.0.0.1', 9042) - self.assertEqual( - DefaultEndPoint('10.0.0.1', 3232).resolve(), - ('10.0.0.1', 3232) - ) + assert DefaultEndPoint('10.0.0.1', 3232).resolve() == ('10.0.0.1', 3232) class TestShardawarePortGenerator(unittest.TestCase): @@ -489,7 +477,7 @@ def test_generate_ports_basic(self, mock_randrange): ports = list(itertools.islice(gen.generate(shard_id=1, total_shards=3), 5)) # Starting from aligned 10005 + shard_id (1), step by 3 - self.assertEqual(ports, [10006, 10009, 10012, 10015, 10018]) + assert ports == [10006, 10009, 10012, 10015, 10018] @patch('random.randrange') def test_wraps_around_to_start(self, mock_randrange): @@ -498,7 +486,7 @@ def test_wraps_around_to_start(self, mock_randrange): ports = list(itertools.islice(gen.generate(shard_id=2, total_shards=4), 5)) # Expected wrap-around from start_port after end_port is exceeded - self.assertEqual(ports, [10010, 10014, 10018, 10002, 10006]) + assert ports == [10010, 10014, 10018, 10002, 10006] @patch('random.randrange') def test_all_ports_have_correct_modulo(self, mock_randrange): @@ -508,7 +496,7 @@ def test_all_ports_have_correct_modulo(self, mock_randrange): gen = ShardAwarePortGenerator(10000, 10020) for port in gen.generate(shard_id=shard_id, total_shards=total_shards): - self.assertEqual(port % total_shards, shard_id) + assert port % total_shards == shard_id @patch('random.randrange') def test_generate_is_repeatable_with_same_mock(self, mock_randrange): @@ -518,4 +506,4 @@ def test_generate_is_repeatable_with_same_mock(self, mock_randrange): first_run = list(itertools.islice(gen.generate(0, 2), 5)) second_run = list(itertools.islice(gen.generate(0, 2), 5)) - self.assertEqual(first_run, second_run) \ No newline at end of file + assert first_run == second_run diff --git a/tests/unit/test_control_connection.py b/tests/unit/test_control_connection.py index 71a6b024cd..92f9c80763 100644 --- a/tests/unit/test_control_connection.py +++ b/tests/unit/test_control_connection.py @@ -212,7 +212,7 @@ def test_wait_for_schema_agreement(self): """ self.assertTrue(self.control_connection.wait_for_schema_agreement()) # the control connection should not have slept at all - self.assertEqual(self.time.clock, 0) + assert self.time.clock == 0 def test_wait_for_schema_agreement_uses_preloaded_results_if_given(self): """ @@ -221,9 +221,9 @@ def test_wait_for_schema_agreement_uses_preloaded_results_if_given(self): preloaded_results = self._matching_schema_preloaded_results self.assertTrue(self.control_connection.wait_for_schema_agreement(preloaded_results=preloaded_results)) # the control connection should not have slept at all - self.assertEqual(self.time.clock, 0) + assert self.time.clock == 0 # the connection should not have made any queries if given preloaded results - self.assertEqual(self.connection.wait_for_responses.call_count, 0) + assert self.connection.wait_for_responses.call_count == 0 def test_wait_for_schema_agreement_falls_back_to_querying_if_schemas_dont_match_preloaded_result(self): """ @@ -232,8 +232,8 @@ def test_wait_for_schema_agreement_falls_back_to_querying_if_schemas_dont_match_ preloaded_results = self._nonmatching_schema_preloaded_results self.assertTrue(self.control_connection.wait_for_schema_agreement(preloaded_results=preloaded_results)) # the control connection should not have slept at all - self.assertEqual(self.time.clock, 0) - self.assertEqual(self.connection.wait_for_responses.call_count, 1) + assert self.time.clock == 0 + assert self.connection.wait_for_responses.call_count == 1 def test_wait_for_schema_agreement_fails(self): """ @@ -263,7 +263,7 @@ def test_wait_for_schema_agreement_skipping(self): self.cluster.metadata.get_host(DefaultEndPoint('192.168.1.1')).is_up = False self.assertTrue(self.control_connection.wait_for_schema_agreement()) - self.assertEqual(self.time.clock, 0) + assert self.time.clock == 0 def test_wait_for_schema_agreement_rpc_lookup(self): """ @@ -280,7 +280,7 @@ def test_wait_for_schema_agreement_rpc_lookup(self): # even though the new host has a different schema version, it's # marked as down, so the control connection shouldn't care self.assertTrue(self.control_connection.wait_for_schema_agreement()) - self.assertEqual(self.time.clock, 0) + assert self.time.clock == 0 # but once we mark it up, the control connection will care host.is_up = True @@ -290,27 +290,27 @@ def test_wait_for_schema_agreement_rpc_lookup(self): def test_refresh_nodes_and_tokens(self): self.control_connection.refresh_node_list_and_token_map() meta = self.cluster.metadata - self.assertEqual(meta.partitioner, 'Murmur3Partitioner') - self.assertEqual(meta.cluster_name, 'foocluster') + assert meta.partitioner == 'Murmur3Partitioner' + assert meta.cluster_name == 'foocluster' # check token map - self.assertEqual(sorted(meta.all_hosts()), sorted(meta.token_map.keys())) + assert sorted(meta.all_hosts()) == sorted(meta.token_map.keys()) for token_list in meta.token_map.values(): - self.assertEqual(3, len(token_list)) + assert 3 == len(token_list) # check datacenter/rack for host in meta.all_hosts(): - self.assertEqual(host.datacenter, "dc1") - self.assertEqual(host.rack, "rack1") + assert host.datacenter == "dc1" + assert host.rack == "rack1" - self.assertEqual(self.connection.wait_for_responses.call_count, 1) + assert self.connection.wait_for_responses.call_count == 1 def test_refresh_nodes_and_tokens_with_invalid_peers(self): def refresh_and_validate_added_hosts(): self.connection.wait_for_responses = Mock(return_value=_node_meta_results( self.connection.local_results, self.connection.peer_results)) self.control_connection.refresh_node_list_and_token_map() - self.assertEqual(1, len(self.cluster.added_hosts)) # only one valid peer found + assert 1 == len(self.cluster.added_hosts) # only one valid peer found # peersV1 del self.connection.peer_results[:] @@ -357,12 +357,12 @@ def test_change_ip(self): self.connection.local_results, self.connection.peer_results)) self.control_connection.refresh_node_list_and_token_map() # all peers are updated - self.assertEqual(0, len(self.cluster.added_hosts)) + assert 0 == len(self.cluster.added_hosts) assert self.cluster.metadata.get_host('192.168.1.5') assert self.cluster.metadata.get_host('192.168.1.6') - self.assertEqual(3, len(self.cluster.metadata.all_hosts())) + assert 3 == len(self.cluster.metadata.all_hosts()) def test_refresh_nodes_and_tokens_uses_preloaded_results_if_given(self): @@ -372,21 +372,21 @@ def test_refresh_nodes_and_tokens_uses_preloaded_results_if_given(self): preloaded_results = self._matching_schema_preloaded_results self.control_connection._refresh_node_list_and_token_map(self.connection, preloaded_results=preloaded_results) meta = self.cluster.metadata - self.assertEqual(meta.partitioner, 'Murmur3Partitioner') - self.assertEqual(meta.cluster_name, 'foocluster') + assert meta.partitioner == 'Murmur3Partitioner' + assert meta.cluster_name == 'foocluster' # check token map - self.assertEqual(sorted(meta.all_hosts()), sorted(meta.token_map.keys())) + assert sorted(meta.all_hosts()) == sorted(meta.token_map.keys()) for token_list in meta.token_map.values(): - self.assertEqual(3, len(token_list)) + assert 3 == len(token_list) # check datacenter/rack for host in meta.all_hosts(): - self.assertEqual(host.datacenter, "dc1") - self.assertEqual(host.rack, "rack1") + assert host.datacenter == "dc1" + assert host.rack == "rack1" # the connection should not have made any queries if given preloaded results - self.assertEqual(self.connection.wait_for_responses.call_count, 0) + assert self.connection.wait_for_responses.call_count == 0 def test_refresh_nodes_and_tokens_no_partitioner(self): """ @@ -396,8 +396,8 @@ def test_refresh_nodes_and_tokens_no_partitioner(self): self.connection.local_results[1][0][5] = None self.control_connection.refresh_node_list_and_token_map() meta = self.cluster.metadata - self.assertEqual(meta.partitioner, None) - self.assertEqual(meta.token_map, {}) + assert meta.partitioner == None + assert meta.token_map == {} def test_refresh_nodes_and_tokens_add_host(self): self.connection.peer_results[1].append( @@ -405,22 +405,22 @@ def test_refresh_nodes_and_tokens_add_host(self): ) self.cluster.scheduler.schedule = lambda delay, f, *args, **kwargs: f(*args, **kwargs) self.control_connection.refresh_node_list_and_token_map() - self.assertEqual(1, len(self.cluster.added_hosts)) - self.assertEqual(self.cluster.added_hosts[0].address, "192.168.1.3") - self.assertEqual(self.cluster.added_hosts[0].datacenter, "dc1") - self.assertEqual(self.cluster.added_hosts[0].rack, "rack1") - self.assertEqual(self.cluster.added_hosts[0].host_id, "uuid4") + assert 1 == len(self.cluster.added_hosts) + assert self.cluster.added_hosts[0].address == "192.168.1.3" + assert self.cluster.added_hosts[0].datacenter == "dc1" + assert self.cluster.added_hosts[0].rack == "rack1" + assert self.cluster.added_hosts[0].host_id == "uuid4" def test_refresh_nodes_and_tokens_remove_host(self): del self.connection.peer_results[1][1] self.control_connection.refresh_node_list_and_token_map() - self.assertEqual(1, len(self.cluster.metadata.removed_hosts)) - self.assertEqual(self.cluster.metadata.removed_hosts[0].address, "192.168.1.2") + assert 1 == len(self.cluster.metadata.removed_hosts) + assert self.cluster.metadata.removed_hosts[0].address == "192.168.1.2" def test_refresh_nodes_and_tokens_timeout(self): def bad_wait_for_responses(*args, **kwargs): - self.assertEqual(kwargs['timeout'], self.control_connection._timeout) + assert kwargs['timeout'] == self.control_connection._timeout raise OperationTimedOut() self.connection.wait_for_responses = bad_wait_for_responses @@ -435,8 +435,8 @@ def bad_wait_for_responses(*args, **kwargs): self.connection.wait_for_responses = Mock(side_effect=bad_wait_for_responses) self.control_connection.refresh_schema() - self.assertEqual(self.connection.wait_for_responses.call_count, self.cluster.max_schema_agreement_wait / self.control_connection._timeout) - self.assertEqual(self.connection.wait_for_responses.call_args[1]['timeout'], self.control_connection._timeout) + assert self.connection.wait_for_responses.call_count == self.cluster.max_schema_agreement_wait / self.control_connection._timeout + assert self.connection.wait_for_responses.call_args[1]['timeout'] == self.control_connection._timeout def test_handle_topology_change(self): event = { @@ -579,15 +579,15 @@ def test_refresh_nodes_and_tokens_add_host_detects_port(self): self.connection.local_results, self.connection.peer_results)) self.cluster.scheduler.schedule = lambda delay, f, *args, **kwargs: f(*args, **kwargs) self.control_connection.refresh_node_list_and_token_map() - self.assertEqual(1, len(self.cluster.added_hosts)) - self.assertEqual(self.cluster.added_hosts[0].endpoint.address, "192.168.1.3") - self.assertEqual(self.cluster.added_hosts[0].endpoint.port, 555) - self.assertEqual(self.cluster.added_hosts[0].broadcast_rpc_address, "192.168.1.3") - self.assertEqual(self.cluster.added_hosts[0].broadcast_rpc_port, 555) - self.assertEqual(self.cluster.added_hosts[0].broadcast_address, "10.0.0.3") - self.assertEqual(self.cluster.added_hosts[0].broadcast_port, 666) - self.assertEqual(self.cluster.added_hosts[0].datacenter, "dc1") - self.assertEqual(self.cluster.added_hosts[0].rack, "rack1") + assert 1 == len(self.cluster.added_hosts) + assert self.cluster.added_hosts[0].endpoint.address == "192.168.1.3" + assert self.cluster.added_hosts[0].endpoint.port == 555 + assert self.cluster.added_hosts[0].broadcast_rpc_address == "192.168.1.3" + assert self.cluster.added_hosts[0].broadcast_rpc_port == 555 + assert self.cluster.added_hosts[0].broadcast_address == "10.0.0.3" + assert self.cluster.added_hosts[0].broadcast_port == 666 + assert self.cluster.added_hosts[0].datacenter == "dc1" + assert self.cluster.added_hosts[0].rack == "rack1" def test_refresh_nodes_and_tokens_add_host_detects_invalid_port(self): del self.connection.peer_results[:] @@ -599,15 +599,15 @@ def test_refresh_nodes_and_tokens_add_host_detects_invalid_port(self): self.connection.local_results, self.connection.peer_results)) self.cluster.scheduler.schedule = lambda delay, f, *args, **kwargs: f(*args, **kwargs) self.control_connection.refresh_node_list_and_token_map() - self.assertEqual(1, len(self.cluster.added_hosts)) - self.assertEqual(self.cluster.added_hosts[0].endpoint.address, "192.168.1.3") - self.assertEqual(self.cluster.added_hosts[0].endpoint.port, 9042) # fallback default - self.assertEqual(self.cluster.added_hosts[0].broadcast_rpc_address, "192.168.1.3") - self.assertEqual(self.cluster.added_hosts[0].broadcast_rpc_port, None) - self.assertEqual(self.cluster.added_hosts[0].broadcast_address, "10.0.0.3") - self.assertEqual(self.cluster.added_hosts[0].broadcast_port, None) - self.assertEqual(self.cluster.added_hosts[0].datacenter, "dc1") - self.assertEqual(self.cluster.added_hosts[0].rack, "rack1") + assert 1 == len(self.cluster.added_hosts) + assert self.cluster.added_hosts[0].endpoint.address == "192.168.1.3" + assert self.cluster.added_hosts[0].endpoint.port == 9042 # fallback default + assert self.cluster.added_hosts[0].broadcast_rpc_address == "192.168.1.3" + assert self.cluster.added_hosts[0].broadcast_rpc_port == None + assert self.cluster.added_hosts[0].broadcast_address == "10.0.0.3" + assert self.cluster.added_hosts[0].broadcast_port == None + assert self.cluster.added_hosts[0].datacenter == "dc1" + assert self.cluster.added_hosts[0].rack == "rack1" class EventTimingTest(unittest.TestCase): diff --git a/tests/unit/test_endpoints.py b/tests/unit/test_endpoints.py index b0841962ca..d0553b3a60 100644 --- a/tests/unit/test_endpoints.py +++ b/tests/unit/test_endpoints.py @@ -31,10 +31,10 @@ class SniEndPointTest(unittest.TestCase): def test_sni_endpoint_properties(self): endpoint = self.endpoint_factory.create_from_sni('test') - self.assertEqual(endpoint.address, 'proxy.datastax.com') - self.assertEqual(endpoint.port, 30002) - self.assertEqual(endpoint._server_name, 'test') - self.assertEqual(str(endpoint), 'proxy.datastax.com:30002:test') + assert endpoint.address == 'proxy.datastax.com' + assert endpoint.port == 30002 + assert endpoint._server_name == 'test' + assert str(endpoint) == 'proxy.datastax.com:30002:test' def test_endpoint_equality(self): self.assertNotEqual( @@ -42,10 +42,7 @@ def test_endpoint_equality(self): self.endpoint_factory.create_from_sni('10.0.0.1') ) - self.assertEqual( - self.endpoint_factory.create_from_sni('10.0.0.1'), - self.endpoint_factory.create_from_sni('10.0.0.1') - ) + assert self.endpoint_factory.create_from_sni('10.0.0.1') == self.endpoint_factory.create_from_sni('10.0.0.1') self.assertNotEqual( self.endpoint_factory.create_from_sni('10.0.0.1'), @@ -64,4 +61,4 @@ def test_endpoint_resolve(self): endpoint = self.endpoint_factory.create_from_sni('test') for i in range(10): (address, _) = endpoint.resolve() - self.assertEqual(address, next(it)) + assert address == next(it) diff --git a/tests/unit/test_exception.py b/tests/unit/test_exception.py index b39b22239c..6bddd96a4b 100644 --- a/tests/unit/test_exception.py +++ b/tests/unit/test_exception.py @@ -37,17 +37,17 @@ def test_timeout_consistency(self): Verify that Timeout exception object translates consistency from input value to correct output string """ consistency_str = self.extract_consistency(repr(Timeout("Timeout Message", consistency=None))) - self.assertEqual(consistency_str, 'Not Set') + assert consistency_str == 'Not Set' for c in ConsistencyLevel.value_to_name.keys(): consistency_str = self.extract_consistency(repr(Timeout("Timeout Message", consistency=c))) - self.assertEqual(consistency_str, ConsistencyLevel.value_to_name[c]) + assert consistency_str == ConsistencyLevel.value_to_name[c] def test_unavailable_consistency(self): """ Verify that Unavailable exception object translates consistency from input value to correct output string """ consistency_str = self.extract_consistency(repr(Unavailable("Unavailable Message", consistency=None))) - self.assertEqual(consistency_str, 'Not Set') + assert consistency_str == 'Not Set' for c in ConsistencyLevel.value_to_name.keys(): consistency_str = self.extract_consistency(repr(Unavailable("Timeout Message", consistency=c))) - self.assertEqual(consistency_str, ConsistencyLevel.value_to_name[c]) + assert consistency_str == ConsistencyLevel.value_to_name[c] diff --git a/tests/unit/test_host_connection_pool.py b/tests/unit/test_host_connection_pool.py index 252ccb49ca..be27909be0 100644 --- a/tests/unit/test_host_connection_pool.py +++ b/tests/unit/test_host_connection_pool.py @@ -53,11 +53,11 @@ def test_borrow_and_return(self): c, request_id = pool.borrow_connection(timeout=0.01) self.assertIs(c, conn) - self.assertEqual(1, conn.in_flight) + assert 1 == conn.in_flight conn.set_keyspace_blocking.assert_called_once_with('foobarkeyspace') pool.return_connection(conn) - self.assertEqual(0, conn.in_flight) + assert 0 == conn.in_flight if not self.uses_single_connection: self.assertNotIn(conn, pool._trash) @@ -71,7 +71,7 @@ def test_failed_wait_for_connection(self): session.cluster.connection_factory.assert_called_once_with(host.endpoint, on_orphaned_stream_released=pool.on_orphaned_stream_released) pool.borrow_connection(timeout=0.01) - self.assertEqual(1, conn.in_flight) + assert 1 == conn.in_flight conn.in_flight = conn.max_request_id @@ -90,7 +90,7 @@ def test_successful_wait_for_connection(self): session.cluster.connection_factory.assert_called_once_with(host.endpoint, on_orphaned_stream_released=pool.on_orphaned_stream_released) pool.borrow_connection(timeout=0.01) - self.assertEqual(1, conn.in_flight) + assert 1 == conn.in_flight def get_second_conn(): c, request_id = pool.borrow_connection(1.0) @@ -102,7 +102,7 @@ def get_second_conn(): pool.return_connection(conn) t.join() - self.assertEqual(0, conn.in_flight) + assert 0 == conn.in_flight def test_spawn_when_at_max(self): host = Mock(spec=Host, address='ip1') @@ -118,7 +118,7 @@ def test_spawn_when_at_max(self): session.cluster.connection_factory.assert_called_once_with(host.endpoint, on_orphaned_stream_released=pool.on_orphaned_stream_released) pool.borrow_connection(timeout=0.01) - self.assertEqual(1, conn.in_flight) + assert 1 == conn.in_flight # make this conn full conn.in_flight = conn.max_request_id @@ -217,7 +217,7 @@ def test_host_equality(self): b = Host('127.0.0.1', SimpleConvictionPolicy) c = Host('127.0.0.2', SimpleConvictionPolicy) - self.assertEqual(a, b, 'Two Host instances should be equal when sharing.') + assert a == b, 'Two Host instances should be equal when sharing.' self.assertNotEqual(a, c, 'Two Host instances should NOT be equal when using two different addresses.') self.assertNotEqual(b, c, 'Two Host instances should NOT be equal when using two different addresses.') diff --git a/tests/unit/test_marshalling.py b/tests/unit/test_marshalling.py index 9c368860f3..e4b415ac69 100644 --- a/tests/unit/test_marshalling.py +++ b/tests/unit/test_marshalling.py @@ -112,27 +112,19 @@ def test_unmarshalling(self): for serializedval, valtype, nativeval in marshalled_value_pairs: unmarshaller = lookup_casstype(valtype) whatwegot = unmarshaller.from_binary(serializedval, 3) - self.assertEqual(whatwegot, nativeval, - msg='Unmarshaller for %s (%s) failed: unmarshal(%r) got %r instead of %r' - % (valtype, unmarshaller, serializedval, whatwegot, nativeval)) - self.assertEqual(type(whatwegot), type(nativeval), - msg='Unmarshaller for %s (%s) gave wrong type (%s instead of %s)' - % (valtype, unmarshaller, type(whatwegot), type(nativeval))) + assert whatwegot == nativeval, 'Unmarshaller for %s (%s) failed: unmarshal(%r) got %r instead of %r' % (valtype, unmarshaller, serializedval, whatwegot, nativeval) + assert type(whatwegot) == type(nativeval), 'Unmarshaller for %s (%s) gave wrong type (%s instead of %s)' % (valtype, unmarshaller, type(whatwegot), type(nativeval)) def test_marshalling(self): for serializedval, valtype, nativeval in marshalled_value_pairs: marshaller = lookup_casstype(valtype) whatwegot = marshaller.to_binary(nativeval, 3) - self.assertEqual(whatwegot, serializedval, - msg='Marshaller for %s (%s) failed: marshal(%r) got %r instead of %r' - % (valtype, marshaller, nativeval, whatwegot, serializedval)) - self.assertEqual(type(whatwegot), type(serializedval), - msg='Marshaller for %s (%s) gave wrong type (%s instead of %s)' - % (valtype, marshaller, type(whatwegot), type(serializedval))) + assert whatwegot == serializedval, 'Marshaller for %s (%s) failed: marshal(%r) got %r instead of %r' % (valtype, marshaller, nativeval, whatwegot, serializedval) + assert type(whatwegot) == type(serializedval), 'Marshaller for %s (%s) gave wrong type (%s instead of %s)' % (valtype, marshaller, type(whatwegot), type(serializedval)) def test_date(self): # separate test because it will deserialize as datetime - self.assertEqual(DateType.from_binary(DateType.to_binary(date(2015, 11, 2), 3), 3), datetime(2015, 11, 2)) + assert DateType.from_binary(DateType.to_binary(date(2015, 11, 2), 3), 3) == datetime(2015, 11, 2) def test_decimal(self): # testing implicit numeric conversion @@ -142,4 +134,4 @@ def test_decimal(self): for proto_ver in range(3, ProtocolVersion.MAX_SUPPORTED + 1): for n in converted_types: expected = Decimal(n) - self.assertEqual(DecimalType.from_binary(DecimalType.to_binary(n, proto_ver), proto_ver), expected) + assert DecimalType.from_binary(DecimalType.to_binary(n, proto_ver), proto_ver) == expected diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index bc5a93bf89..ccdee2fa1c 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -42,16 +42,16 @@ class ReplicationFactorTest(unittest.TestCase): def test_replication_factor_parsing(self): rf = ReplicationFactor.create('3') - self.assertEqual(rf.all_replicas, 3) - self.assertEqual(rf.full_replicas, 3) - self.assertEqual(rf.transient_replicas, None) - self.assertEqual(str(rf), '3') + assert rf.all_replicas == 3 + assert rf.full_replicas == 3 + assert rf.transient_replicas == None + assert str(rf) == '3' rf = ReplicationFactor.create('3/1') - self.assertEqual(rf.all_replicas, 3) - self.assertEqual(rf.full_replicas, 2) - self.assertEqual(rf.transient_replicas, 1) - self.assertEqual(str(rf), '3/1') + assert rf.all_replicas == 3 + assert rf.full_replicas == 2 + assert rf.transient_replicas == 1 + assert str(rf) == '3/1' self.assertRaises(ValueError, ReplicationFactor.create, '3/') self.assertRaises(ValueError, ReplicationFactor.create, 'a/1') @@ -59,8 +59,8 @@ def test_replication_factor_parsing(self): self.assertRaises(ValueError, ReplicationFactor.create, '3/a') def test_replication_factor_equality(self): - self.assertEqual(ReplicationFactor.create('3/1'), ReplicationFactor.create('3/1')) - self.assertEqual(ReplicationFactor.create('3'), ReplicationFactor.create('3')) + assert ReplicationFactor.create('3/1') == ReplicationFactor.create('3/1') + assert ReplicationFactor.create('3') == ReplicationFactor.create('3') self.assertNotEqual(ReplicationFactor.create('3'), ReplicationFactor.create('3/1')) self.assertNotEqual(ReplicationFactor.create('3'), ReplicationFactor.create('3/1')) @@ -82,16 +82,15 @@ def test_replication_strategy(self): rs = ReplicationStrategy() - self.assertEqual(rs.create('OldNetworkTopologyStrategy', None), _UnknownStrategy('OldNetworkTopologyStrategy', None)) + assert rs.create('OldNetworkTopologyStrategy', None) == _UnknownStrategy('OldNetworkTopologyStrategy', None) fake_options_map = {'options': 'map'} uks = rs.create('OldNetworkTopologyStrategy', fake_options_map) - self.assertEqual(uks, _UnknownStrategy('OldNetworkTopologyStrategy', fake_options_map)) - self.assertEqual(uks.make_token_replica_map({}, []), {}) + assert uks == _UnknownStrategy('OldNetworkTopologyStrategy', fake_options_map) + assert uks.make_token_replica_map({}, []) == {} fake_options_map = {'dc1': '3'} self.assertIsInstance(rs.create('NetworkTopologyStrategy', fake_options_map), NetworkTopologyStrategy) - self.assertEqual(rs.create('NetworkTopologyStrategy', fake_options_map).dc_replication_factors, - NetworkTopologyStrategy(fake_options_map).dc_replication_factors) + assert rs.create('NetworkTopologyStrategy', fake_options_map).dc_replication_factors == NetworkTopologyStrategy(fake_options_map).dc_replication_factors fake_options_map = {'options': 'map'} self.assertIsNone(rs.create('SimpleStrategy', fake_options_map)) @@ -101,10 +100,9 @@ def test_replication_strategy(self): fake_options_map = {'options': 'map', 'replication_factor': 3} self.assertIsInstance(rs.create('SimpleStrategy', fake_options_map), SimpleStrategy) - self.assertEqual(rs.create('SimpleStrategy', fake_options_map).replication_factor, - SimpleStrategy(fake_options_map).replication_factor) + assert rs.create('SimpleStrategy', fake_options_map).replication_factor == SimpleStrategy(fake_options_map).replication_factor - self.assertEqual(rs.create('xxxxxxxx', fake_options_map), _UnknownStrategy('xxxxxxxx', fake_options_map)) + assert rs.create('xxxxxxxx', fake_options_map) == _UnknownStrategy('xxxxxxxx', fake_options_map) self.assertRaises(NotImplementedError, rs.make_token_replica_map, None, None) self.assertRaises(NotImplementedError, rs.export_for_schema) @@ -116,25 +114,22 @@ def test_simple_replication_type_parsing(self): simple_int = rs.create('SimpleStrategy', {'replication_factor': 3}) simple_str = rs.create('SimpleStrategy', {'replication_factor': '3'}) - self.assertEqual(simple_int.export_for_schema(), simple_str.export_for_schema()) - self.assertEqual(simple_int, simple_str) + assert simple_int.export_for_schema() == simple_str.export_for_schema() + assert simple_int == simple_str # make token replica map ring = [MD5Token(0), MD5Token(1), MD5Token(2)] hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy) for host in range(3)] token_to_host = dict(zip(ring, hosts)) - self.assertEqual( - simple_int.make_token_replica_map(token_to_host, ring), - simple_str.make_token_replica_map(token_to_host, ring) - ) + assert simple_int.make_token_replica_map(token_to_host, ring) == simple_str.make_token_replica_map(token_to_host, ring) def test_transient_replication_parsing(self): """ Test that we can PARSE a transient replication factor for SimpleStrategy """ rs = ReplicationStrategy() simple_transient = rs.create('SimpleStrategy', {'replication_factor': '3/1'}) - self.assertEqual(simple_transient.replication_factor_info, ReplicationFactor(3, 1)) - self.assertEqual(simple_transient.replication_factor, 2) + assert simple_transient.replication_factor_info == ReplicationFactor(3, 1) + assert simple_transient.replication_factor == 2 self.assertIn("'replication_factor': '3/1'", simple_transient.export_for_schema()) simple_str = rs.create('SimpleStrategy', {'replication_factor': '2'}) @@ -144,10 +139,7 @@ def test_transient_replication_parsing(self): ring = [MD5Token(0), MD5Token(1), MD5Token(2)] hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy) for host in range(3)] token_to_host = dict(zip(ring, hosts)) - self.assertEqual( - simple_transient.make_token_replica_map(token_to_host, ring), - simple_str.make_token_replica_map(token_to_host, ring) - ) + assert simple_transient.make_token_replica_map(token_to_host, ring) == simple_str.make_token_replica_map(token_to_host, ring) def test_nts_replication_parsing(self): """ Test equality between passing numeric and string replication factor for NTS """ @@ -156,32 +148,29 @@ def test_nts_replication_parsing(self): nts_int = rs.create('NetworkTopologyStrategy', {'dc1': 3, 'dc2': 5}) nts_str = rs.create('NetworkTopologyStrategy', {'dc1': '3', 'dc2': '5'}) - self.assertEqual(nts_int.dc_replication_factors['dc1'], 3) - self.assertEqual(nts_str.dc_replication_factors['dc1'], 3) - self.assertEqual(nts_int.dc_replication_factors_info['dc1'], ReplicationFactor(3)) - self.assertEqual(nts_str.dc_replication_factors_info['dc1'], ReplicationFactor(3)) + assert nts_int.dc_replication_factors['dc1'] == 3 + assert nts_str.dc_replication_factors['dc1'] == 3 + assert nts_int.dc_replication_factors_info['dc1'] == ReplicationFactor(3) + assert nts_str.dc_replication_factors_info['dc1'] == ReplicationFactor(3) - self.assertEqual(nts_int.export_for_schema(), nts_str.export_for_schema()) - self.assertEqual(nts_int, nts_str) + assert nts_int.export_for_schema() == nts_str.export_for_schema() + assert nts_int == nts_str # make token replica map ring = [MD5Token(0), MD5Token(1), MD5Token(2)] hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy) for host in range(3)] token_to_host = dict(zip(ring, hosts)) - self.assertEqual( - nts_int.make_token_replica_map(token_to_host, ring), - nts_str.make_token_replica_map(token_to_host, ring) - ) + assert nts_int.make_token_replica_map(token_to_host, ring) == nts_str.make_token_replica_map(token_to_host, ring) def test_nts_transient_parsing(self): """ Test that we can PARSE a transient replication factor for NTS """ rs = ReplicationStrategy() nts_transient = rs.create('NetworkTopologyStrategy', {'dc1': '3/1', 'dc2': '5/1'}) - self.assertEqual(nts_transient.dc_replication_factors_info['dc1'], ReplicationFactor(3, 1)) - self.assertEqual(nts_transient.dc_replication_factors_info['dc2'], ReplicationFactor(5, 1)) - self.assertEqual(nts_transient.dc_replication_factors['dc1'], 2) - self.assertEqual(nts_transient.dc_replication_factors['dc2'], 4) + assert nts_transient.dc_replication_factors_info['dc1'] == ReplicationFactor(3, 1) + assert nts_transient.dc_replication_factors_info['dc2'] == ReplicationFactor(5, 1) + assert nts_transient.dc_replication_factors['dc1'] == 2 + assert nts_transient.dc_replication_factors['dc2'] == 4 self.assertIn("'dc1': '3/1', 'dc2': '5/1'", nts_transient.export_for_schema()) nts_str = rs.create('NetworkTopologyStrategy', {'dc1': '3', 'dc2': '5'}) @@ -191,10 +180,7 @@ def test_nts_transient_parsing(self): ring = [MD5Token(0), MD5Token(1), MD5Token(2)] hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy) for host in range(3)] token_to_host = dict(zip(ring, hosts)) - self.assertEqual( - nts_transient.make_token_replica_map(token_to_host, ring), - nts_str.make_token_replica_map(token_to_host, ring) - ) + assert nts_transient.make_token_replica_map(token_to_host, ring) == nts_str.make_token_replica_map(token_to_host, ring) def test_nts_make_token_replica_map(self): token_to_host_owner = {} @@ -320,12 +306,11 @@ def test_nts_make_token_replica_map_empty_dc(self): nts = NetworkTopologyStrategy({'dc1': 1, 'dc2': 0}) replica_map = nts.make_token_replica_map(token_to_host_owner, ring) - self.assertEqual(set(replica_map[MD5Token(0)]), set([host])) + assert set(replica_map[MD5Token(0)]) == set([host]) def test_nts_export_for_schema(self): strategy = NetworkTopologyStrategy({'dc1': '1', 'dc2': '2'}) - self.assertEqual("{'class': 'NetworkTopologyStrategy', 'dc1': '1', 'dc2': '2'}", - strategy.export_for_schema()) + assert "{'class': 'NetworkTopologyStrategy', 'dc1': '1', 'dc2': '2'}" == strategy.export_for_schema() def test_simple_strategy_make_token_replica_map(self): host1 = Host('1', SimpleConvictionPolicy) @@ -363,58 +348,57 @@ def test_protect_name(self): """ Test cassandra.metadata.protect_name output """ - self.assertEqual(protect_name('tests'), 'tests') - self.assertEqual(protect_name('test\'s'), '"test\'s"') - self.assertEqual(protect_name('test\'s'), "\"test's\"") - self.assertEqual(protect_name('tests ?!@#$%^&*()'), '"tests ?!@#$%^&*()"') - self.assertEqual(protect_name('1'), '"1"') - self.assertEqual(protect_name('1test'), '"1test"') + assert protect_name('tests') == 'tests' + assert protect_name('test\'s') == '"test\'s"' + assert protect_name('test\'s') == "\"test's\"" + assert protect_name('tests ?!@#$%^&*()') == '"tests ?!@#$%^&*()"' + assert protect_name('1') == '"1"' + assert protect_name('1test') == '"1test"' def test_protect_names(self): """ Test cassandra.metadata.protect_names output """ - self.assertEqual(protect_names(['tests']), ['tests']) - self.assertEqual(protect_names( + assert protect_names(['tests']) == ['tests'] + assert protect_names( [ 'tests', 'test\'s', 'tests ?!@#$%^&*()', '1' - ]), - [ - 'tests', - "\"test's\"", - '"tests ?!@#$%^&*()"', - '"1"' - ]) + ]) == [ + 'tests', + "\"test's\"", + '"tests ?!@#$%^&*()"', + '"1"' + ] def test_protect_value(self): """ Test cassandra.metadata.protect_value output """ - self.assertEqual(protect_value(True), "true") - self.assertEqual(protect_value(False), "false") - self.assertEqual(protect_value(3.14), '3.14') - self.assertEqual(protect_value(3), '3') - self.assertEqual(protect_value('test'), "'test'") - self.assertEqual(protect_value('test\'s'), "'test''s'") - self.assertEqual(protect_value(None), 'NULL') + assert protect_value(True) == "true" + assert protect_value(False) == "false" + assert protect_value(3.14) == '3.14' + assert protect_value(3) == '3' + assert protect_value('test') == "'test'" + assert protect_value('test\'s') == "'test''s'" + assert protect_value(None) == 'NULL' def test_is_valid_name(self): """ Test cassandra.metadata.is_valid_name output """ - self.assertEqual(is_valid_name(None), False) - self.assertEqual(is_valid_name('test'), True) - self.assertEqual(is_valid_name('Test'), False) - self.assertEqual(is_valid_name('t_____1'), True) - self.assertEqual(is_valid_name('test1'), True) - self.assertEqual(is_valid_name('1test1'), False) + assert is_valid_name(None) == False + assert is_valid_name('test') == True + assert is_valid_name('Test') == False + assert is_valid_name('t_____1') == True + assert is_valid_name('test1') == True + assert is_valid_name('1test1') == False invalid_keywords = cassandra.metadata.cql_keywords - cassandra.metadata.cql_keywords_unreserved for keyword in invalid_keywords: - self.assertEqual(is_valid_name(keyword), False) + assert is_valid_name(keyword) == False class GetReplicasTest(unittest.TestCase): @@ -429,18 +413,18 @@ def _get_replicas(self, token_klass): # tokens match node tokens exactly for token, expected_host in zip(tokens, hosts): replicas = token_map.get_replicas("ks", token) - self.assertEqual(set(replicas), {expected_host}) + assert set(replicas) == {expected_host} # shift the tokens back by one for token, expected_host in zip(tokens, hosts): replicas = token_map.get_replicas("ks", token_klass(token.value - 1)) - self.assertEqual(set(replicas), {expected_host}) + assert set(replicas) == {expected_host} # shift the tokens forward by one for i, token in enumerate(tokens): replicas = token_map.get_replicas("ks", token_klass(token.value + 1)) expected_host = hosts[(i + 1) % len(hosts)] - self.assertEqual(set(replicas), {expected_host}) + assert set(replicas) == {expected_host} def test_murmur3_tokens(self): self._get_replicas(Murmur3Token) @@ -456,7 +440,7 @@ class Murmur3TokensTest(unittest.TestCase): def test_murmur3_init(self): murmur3_token = Murmur3Token(cassandra.metadata.MIN_LONG - 1) - self.assertEqual(str(murmur3_token), '') + assert str(murmur3_token) == '' def test_python_vs_c(self): from cassandra.murmur3 import _murmur3 as mm3_python @@ -467,7 +451,7 @@ def test_python_vs_c(self): for _ in range(iterations): for len in range(0, 32): # zero to one block plus full range of tail lengths key = os.urandom(len) - self.assertEqual(mm3_python(key), mm3_c(key)) + assert mm3_python(key) == mm3_c(key) except ImportError: raise unittest.SkipTest('The cmurmur3 extension is not available') @@ -484,36 +468,36 @@ def test_murmur3_c(self): raise unittest.SkipTest('The cmurmur3 extension is not available') def _verify_hash(self, fn): - self.assertEqual(fn(b'123'), -7468325962851647638) - self.assertEqual(fn(b'\x00\xff\x10\xfa\x99' * 10), 5837342703291459765) - self.assertEqual(fn(b'\xfe' * 8), -8927430733708461935) - self.assertEqual(fn(b'\x10' * 8), 1446172840243228796) - self.assertEqual(fn(str(cassandra.metadata.MAX_LONG).encode()), 7162290910810015547) + assert fn(b'123') == -7468325962851647638 + assert fn(b'\x00\xff\x10\xfa\x99' * 10) == 5837342703291459765 + assert fn(b'\xfe' * 8) == -8927430733708461935 + assert fn(b'\x10' * 8) == 1446172840243228796 + assert fn(str(cassandra.metadata.MAX_LONG).encode()) == 7162290910810015547 class MD5TokensTest(unittest.TestCase): def test_md5_tokens(self): md5_token = MD5Token(cassandra.metadata.MIN_LONG - 1) - self.assertEqual(md5_token.hash_fn('123'), 42767516990368493138776584305024125808) - self.assertEqual(md5_token.hash_fn(str(cassandra.metadata.MAX_LONG)), 28528976619278518853815276204542453639) - self.assertEqual(str(md5_token), '' % -9223372036854775809) + assert md5_token.hash_fn('123') == 42767516990368493138776584305024125808 + assert md5_token.hash_fn(str(cassandra.metadata.MAX_LONG)) == 28528976619278518853815276204542453639 + assert str(md5_token) == '' % -9223372036854775809 class BytesTokensTest(unittest.TestCase): def test_bytes_tokens(self): bytes_token = BytesToken(unhexlify(b'01')) - self.assertEqual(bytes_token.value, b'\x01') - self.assertEqual(str(bytes_token), "" % bytes_token.value) - self.assertEqual(bytes_token.hash_fn('123'), '123') - self.assertEqual(bytes_token.hash_fn(123), 123) - self.assertEqual(bytes_token.hash_fn(str(cassandra.metadata.MAX_LONG)), str(cassandra.metadata.MAX_LONG)) + assert bytes_token.value == b'\x01' + assert str(bytes_token) == "" % bytes_token.value + assert bytes_token.hash_fn('123') == '123' + assert bytes_token.hash_fn(123) == 123 + assert bytes_token.hash_fn(str(cassandra.metadata.MAX_LONG)) == str(cassandra.metadata.MAX_LONG) def test_from_string(self): from_unicode = BytesToken.from_string('0123456789abcdef') from_bin = BytesToken.from_string(b'0123456789abcdef') - self.assertEqual(from_unicode, from_bin) + assert from_unicode == from_bin self.assertIsInstance(from_unicode.value, bytes) self.assertIsInstance(from_bin.value, bytes) @@ -541,7 +525,7 @@ def test_export_as_string_user_types(self): keyspace.user_types['c'] = UserType(keyspace_name, 'c', ['one'], ['int']) keyspace.user_types['d'] = UserType(keyspace_name, 'd', ['one'], ['c']) - self.assertEqual("""CREATE KEYSPACE test WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'} AND durable_writes = true; + assert """CREATE KEYSPACE test WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'} AND durable_writes = true; CREATE TYPE test.c ( one int @@ -560,7 +544,7 @@ def test_export_as_string_user_types(self): one d, two int, three a -);""", keyspace.export_as_string()) +);""" == keyspace.export_as_string() class UserTypesTest(unittest.TestCase): @@ -568,17 +552,17 @@ class UserTypesTest(unittest.TestCase): def test_as_cql_query(self): field_types = ['varint', 'ascii', 'frozen>'] udt = UserType("ks1", "mytype", ["a", "b", "c"], field_types) - self.assertEqual("CREATE TYPE ks1.mytype (a varint, b ascii, c frozen>)", udt.as_cql_query(formatted=False)) + assert "CREATE TYPE ks1.mytype (a varint, b ascii, c frozen>)" == udt.as_cql_query(formatted=False) - self.assertEqual("""CREATE TYPE ks1.mytype ( + assert """CREATE TYPE ks1.mytype ( a varint, b ascii, c frozen> -);""", udt.export_as_string()) +);""" == udt.export_as_string() def test_as_cql_query_name_escaping(self): udt = UserType("MyKeyspace", "MyType", ["AbA", "keyspace"], ['ascii', 'ascii']) - self.assertEqual('CREATE TYPE "MyKeyspace"."MyType" ("AbA" ascii, "keyspace" ascii)', udt.as_cql_query(formatted=False)) + assert 'CREATE TYPE "MyKeyspace"."MyType" ("AbA" ascii, "keyspace" ascii)' == udt.as_cql_query(formatted=False) class UserDefinedFunctionTest(unittest.TestCase): @@ -594,7 +578,7 @@ def test_as_cql_query_removes_frozen(self): "LANGUAGE java " "AS $$return 0;$$" ) - self.assertEqual(expected_result, func.as_cql_query(formatted=False)) + assert expected_result == func.as_cql_query(formatted=False) class UserDefinedAggregateTest(unittest.TestCase): @@ -607,7 +591,7 @@ def test_as_cql_query_removes_frozen(self): "FINALFUNC finalfunc " "INITCOND (0)" ) - self.assertEqual(expected_result, aggregate.as_cql_query(formatted=False)) + assert expected_result == aggregate.as_cql_query(formatted=False) class IndexTest(unittest.TestCase): @@ -622,14 +606,12 @@ def test_build_index_as_cql(self): row = {'index_name': 'index_name_here', 'index_type': 'index_type_here'} index_meta = parser._build_index_metadata(column_meta, row) - self.assertEqual(index_meta.as_cql_query(), - 'CREATE INDEX index_name_here ON keyspace_name_here.table_name_here (column_name_here)') + assert index_meta.as_cql_query() == 'CREATE INDEX index_name_here ON keyspace_name_here.table_name_here (column_name_here)' row['index_options'] = '{ "class_name": "class_name_here" }' row['index_type'] = 'CUSTOM' index_meta = parser._build_index_metadata(column_meta, row) - self.assertEqual(index_meta.as_cql_query(), - "CREATE CUSTOM INDEX index_name_here ON keyspace_name_here.table_name_here (column_name_here) USING 'class_name_here'") + assert index_meta.as_cql_query() == "CREATE CUSTOM INDEX index_name_here ON keyspace_name_here.table_name_here (column_name_here) USING 'class_name_here'" class UnicodeIdentifiersTests(unittest.TestCase): @@ -832,12 +814,12 @@ def test_iterate_all_hosts_and_modify(self): metadata.add_or_return_host(Host('dc1.1', SimpleConvictionPolicy)) metadata.add_or_return_host(Host('dc1.2', SimpleConvictionPolicy)) - self.assertEqual(len(metadata.all_hosts()), 2) + assert len(metadata.all_hosts()) == 2 for host in metadata.all_hosts(): # this would previously raise in Py3 metadata.remove_host(host) - self.assertEqual(len(metadata.all_hosts()), 0) + assert len(metadata.all_hosts()) == 0 class MetadataHelpersTest(unittest.TestCase): @@ -856,4 +838,4 @@ def test_strip_frozen(self): ] for argument, expected_result in argument_to_expected_results: result = strip_frozen(argument) - self.assertEqual(result, expected_result, "strip_frozen() arg: {}".format(argument)) + assert result == expected_result, "strip_frozen() arg: {}".format(argument) diff --git a/tests/unit/test_orderedmap.py b/tests/unit/test_orderedmap.py index 318b98a52d..a98ad40919 100644 --- a/tests/unit/test_orderedmap.py +++ b/tests/unit/test_orderedmap.py @@ -23,15 +23,15 @@ def test_init(self): b = OrderedMap([('one', 1), ('three', 3), ('two', 2)]) c = OrderedMap(a) builtin = {'one': 1, 'two': 2, 'three': 3} - self.assertEqual(a, b) - self.assertEqual(a, c) - self.assertEqual(a, builtin) - self.assertEqual(OrderedMap([(1, 1), (1, 2)]), {1: 2}) + assert a == b + assert a == c + assert a == builtin + assert OrderedMap([(1, 1), (1, 2)]) == {1: 2} d = OrderedMap({'': 3}, key1='v1', key2='v2') - self.assertEqual(d[''], 3) - self.assertEqual(d['key1'], 'v1') - self.assertEqual(d['key2'], 'v2') + assert d[''] == 3 + assert d['key1'] == 'v1' + assert d['key2'] == 'v2' with self.assertRaises(TypeError): OrderedMap('too', 'many', 'args') @@ -75,9 +75,9 @@ def test_get(self): om = OrderedMap(zip(keys, range(len(keys)))) for v, k in enumerate(keys): - self.assertEqual(om.get(k), v) - - self.assertEqual(om.get('notthere', 'default'), 'default') + assert om.get(k) == v + + assert om.get('notthere', 'default') == 'default' self.assertIsNone(om.get('notthere')) def test_equal(self): @@ -87,9 +87,9 @@ def test_equal(self): om12 = OrderedMap([('one', 1), ('two', 2)]) om21 = OrderedMap([('two', 2), ('one', 1)]) - self.assertEqual(om1, d1) - self.assertEqual(om12, d12) - self.assertEqual(om21, d12) + assert om1 == d1 + assert om12 == d12 + assert om21 == d12 self.assertNotEqual(om1, om12) self.assertNotEqual(om12, om1) self.assertNotEqual(om12, om21) @@ -104,8 +104,8 @@ def test_getitem(self): om = OrderedMap(zip(keys, range(len(keys)))) for v, k in enumerate(keys): - self.assertEqual(om[k], v) - + assert om[k] == v + with self.assertRaises(KeyError): om['notthere'] @@ -116,16 +116,16 @@ def test_iter(self): om = OrderedMap(items) itr = iter(om) - self.assertEqual(sum([1 for _ in itr]), len(keys)) + assert sum([1 for _ in itr]) == len(keys) self.assertRaises(StopIteration, next, itr) - self.assertEqual(list(iter(om)), keys) - self.assertEqual(list(om.items()), items) - self.assertEqual(list(om.values()), values) + assert list(iter(om)) == keys + assert list(om.items()) == items + assert list(om.values()) == values def test_len(self): - self.assertEqual(len(OrderedMap()), 0) - self.assertEqual(len(OrderedMap([(1, 1)])), 1) + assert len(OrderedMap()) == 0 + assert len(OrderedMap([(1, 1)])) == 1 def test_mutable_keys(self): d = {'1': 1} @@ -136,16 +136,14 @@ def test_strings(self): # changes in 3.x d = {'map': 'inner'} s = set([1, 2, 3]) - self.assertEqual(repr(OrderedMap([('two', 2), ('one', 1), (d, 'value'), (s, 'another')])), - "OrderedMap([('two', 2), ('one', 1), (%r, 'value'), (%r, 'another')])" % (d, s)) + assert repr(OrderedMap([('two', 2), ('one', 1), (d, 'value'), (s, 'another')])) == "OrderedMap([('two', 2), ('one', 1), (%r, 'value'), (%r, 'another')])" % (d, s) - self.assertEqual(str(OrderedMap([('two', 2), ('one', 1), (d, 'value'), (s, 'another')])), - "{'two': 2, 'one': 1, %r: 'value', %r: 'another'}" % (d, s)) + assert str(OrderedMap([('two', 2), ('one', 1), (d, 'value'), (s, 'another')])) == "{'two': 2, 'one': 1, %r: 'value', %r: 'another'}" % (d, s) def test_popitem(self): item = (1, 2) om = OrderedMap((item,)) - self.assertEqual(om.popitem(), item) + assert om.popitem() == item self.assertRaises(KeyError, om.popitem) def test_delitem(self): @@ -154,7 +152,7 @@ def test_delitem(self): self.assertRaises(KeyError, om.__delitem__, 3) del om[1] - self.assertEqual(om, {2: 2}) + assert om == {2: 2} del om[2] self.assertFalse(om) @@ -164,7 +162,7 @@ def test_delitem(self): class OrderedMapSerializedKeyTest(unittest.TestCase): def test_init(self): om = OrderedMapSerializedKey(UTF8Type, 3) - self.assertEqual(om, {}) + assert om == {} def test_normalized_lookup(self): key_type = lookup_casstype('MapType(UTF8Type, Int32Type)') diff --git a/tests/unit/test_parameter_binding.py b/tests/unit/test_parameter_binding.py index 9c557c0208..1230b54786 100644 --- a/tests/unit/test_parameter_binding.py +++ b/tests/unit/test_parameter_binding.py @@ -26,27 +26,27 @@ class ParamBindingTest(unittest.TestCase): def test_bind_sequence(self): result = bind_params("%s %s %s", (1, "a", 2.0), Encoder()) - self.assertEqual(result, "1 'a' 2.0") + assert result == "1 'a' 2.0" def test_bind_map(self): result = bind_params("%(a)s %(b)s %(c)s", dict(a=1, b="a", c=2.0), Encoder()) - self.assertEqual(result, "1 'a' 2.0") + assert result == "1 'a' 2.0" def test_sequence_param(self): result = bind_params("%s", (ValueSequence((1, "a", 2.0)),), Encoder()) - self.assertEqual(result, "(1, 'a', 2.0)") + assert result == "(1, 'a', 2.0)" def test_generator_param(self): result = bind_params("%s", ((i for i in range(3)),), Encoder()) - self.assertEqual(result, "[0, 1, 2]") + assert result == "[0, 1, 2]" def test_none_param(self): result = bind_params("%s", (None,), Encoder()) - self.assertEqual(result, "NULL") + assert result == "NULL" def test_list_collection(self): result = bind_params("%s", (['a', 'b', 'c'],), Encoder()) - self.assertEqual(result, "['a', 'b', 'c']") + assert result == "['a', 'b', 'c']" def test_set_collection(self): result = bind_params("%s", (set(['a', 'b']),), Encoder()) @@ -58,15 +58,15 @@ def test_map_collection(self): vals['b'] = 'b' vals['c'] = 'c' result = bind_params("%s", (vals,), Encoder()) - self.assertEqual(result, "{'a': 'a', 'b': 'b', 'c': 'c'}") + assert result == "{'a': 'a', 'b': 'b', 'c': 'c'}" def test_quote_escaping(self): result = bind_params("%s", ("""'ef''ef"ef""ef'""",), Encoder()) - self.assertEqual(result, """'''ef''''ef"ef""ef'''""") + assert result == """'''ef''''ef"ef""ef'''""" def test_float_precision(self): f = 3.4028234663852886e+38 - self.assertEqual(float(bind_params("%s", (f,), Encoder())), f) + assert float(bind_params("%s", (f,), Encoder())) == f class BoundStatementTestV3(unittest.TestCase): @@ -128,13 +128,13 @@ def test_inherit_fetch_size(self): result_metadata_id=None) prepared_statement.fetch_size = 1234 bound_statement = BoundStatement(prepared_statement=prepared_statement) - self.assertEqual(1234, bound_statement.fetch_size) + assert 1234 == bound_statement.fetch_size def test_too_few_parameters_for_routing_key(self): self.assertRaises(ValueError, self.prepared.bind, (1,)) bound = self.prepared.bind((1, 2)) - self.assertEqual(bound.keyspace, 'keyspace') + assert bound.keyspace == 'keyspace' def test_dict_missing_routing_key(self): self.assertRaises(KeyError, self.bound.bind, {'rk0': 0, 'ck0': 0, 'v0': 0}) @@ -145,7 +145,7 @@ def test_missing_value(self): def test_extra_value(self): self.bound.bind({'rk0': 0, 'rk1': 0, 'ck0': 0, 'v0': 0, 'should_not_be_here': 123}) # okay to have extra keys in dict - self.assertEqual(self.bound.values, [b'\x00' * 4] * 4) # four encoded zeros + assert self.bound.values == [b'\x00' * 4] * 4 # four encoded zeros self.assertRaises(ValueError, self.bound.bind, (0, 0, 0, 0, 123)) def test_values_none(self): @@ -166,12 +166,12 @@ def test_values_none(self): def test_bind_none(self): self.bound.bind({'rk0': 0, 'rk1': 0, 'ck0': 0, 'v0': None}) - self.assertEqual(self.bound.values[-1], None) + assert self.bound.values[-1] == None old_values = self.bound.values self.bound.bind((0, 0, 0, None)) self.assertIsNot(self.bound.values, old_values) - self.assertEqual(self.bound.values[-1], None) + assert self.bound.values[-1] == None def test_unset_value(self): self.assertRaises(ValueError, self.bound.bind, {'rk0': 0, 'rk1': 0, 'ck0': 0, 'v0': UNSET_VALUE}) @@ -190,19 +190,19 @@ def test_dict_missing_routing_key(self): def test_missing_value(self): # in v4 missing values are UNSET_VALUE self.bound.bind({'rk0': 0, 'rk1': 0, 'ck0': 0}) - self.assertEqual(self.bound.values[-1], UNSET_VALUE) + assert self.bound.values[-1] == UNSET_VALUE old_values = self.bound.values self.bound.bind((0, 0, 0)) self.assertIsNot(self.bound.values, old_values) - self.assertEqual(self.bound.values[-1], UNSET_VALUE) + assert self.bound.values[-1] == UNSET_VALUE def test_unset_value(self): self.bound.bind({'rk0': 0, 'rk1': 0, 'ck0': 0, 'v0': UNSET_VALUE}) - self.assertEqual(self.bound.values[-1], UNSET_VALUE) + assert self.bound.values[-1] == UNSET_VALUE self.bound.bind((0, 0, 0, UNSET_VALUE)) - self.assertEqual(self.bound.values[-1], UNSET_VALUE) + assert self.bound.values[-1] == UNSET_VALUE class BoundStatementTestV5(BoundStatementTestV4): diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index e7757aedfc..0ad08d9a0e 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -67,7 +67,7 @@ def test_basic(self): policy = RoundRobinPolicy() policy.populate(None, hosts) qplan = list(policy.make_query_plan()) - self.assertEqual(sorted(qplan), hosts) + assert sorted(qplan) == hosts def test_multiple_query_plans(self): hosts = [0, 1, 2, 3] @@ -75,13 +75,13 @@ def test_multiple_query_plans(self): policy.populate(None, hosts) for i in range(20): qplan = list(policy.make_query_plan()) - self.assertEqual(sorted(qplan), hosts) + assert sorted(qplan) == hosts def test_single_host(self): policy = RoundRobinPolicy() policy.populate(None, [0]) qplan = list(policy.make_query_plan()) - self.assertEqual(qplan, [0]) + assert qplan == [0] def test_status_updates(self): hosts = [0, 1, 2, 3] @@ -92,7 +92,7 @@ def test_status_updates(self): policy.on_up(4) policy.on_add(5) qplan = list(policy.make_query_plan()) - self.assertEqual(sorted(qplan), [2, 3, 4, 5]) + assert sorted(qplan) == [2, 3, 4, 5] def test_thread_safety(self): hosts = range(100) @@ -102,7 +102,7 @@ def test_thread_safety(self): def check_query_plan(): for i in range(100): qplan = list(policy.make_query_plan()) - self.assertEqual(sorted(qplan), list(hosts)) + assert sorted(qplan) == list(hosts) threads = [Thread(target=check_query_plan) for i in range(4)] for t in threads: @@ -176,7 +176,7 @@ def test_no_live_nodes(self): policy.on_down(i) qplan = list(policy.make_query_plan()) - self.assertEqual(qplan, []) + assert qplan == [] @pytest.mark.parametrize("policy_specialization, constructor_args", [(DCAwareRoundRobinPolicy, ("dc1", )), (RackAwareRoundRobinPolicy, ("dc1", "rack1"))]) class TestRackOrDCAwareRoundRobinPolicy: @@ -591,14 +591,14 @@ def get_replicas(keyspace, packed_key): replicas = get_replicas(None, struct.pack('>i', i)) other = set(h for h in hosts if h not in replicas) - self.assertEqual(replicas, qplan[:2]) - self.assertEqual(other, set(qplan[2:])) + assert replicas == qplan[:2] + assert other == set(qplan[2:]) # Should use the secondary policy for i in range(4): qplan = list(policy.make_query_plan()) - self.assertEqual(set(qplan), set(hosts)) + assert set(qplan) == set(hosts) def test_wrap_dc_aware(self): cluster = Mock(spec=Cluster) @@ -632,16 +632,16 @@ def get_replicas(keyspace, packed_key): # first should be the only local replica self.assertIn(qplan[0], replicas) - self.assertEqual(qplan[0].datacenter, "dc1") + assert qplan[0].datacenter == "dc1" # then the local non-replica self.assertNotIn(qplan[1], replicas) - self.assertEqual(qplan[1].datacenter, "dc1") + assert qplan[1].datacenter == "dc1" # then one of the remotes (used_hosts_per_remote_dc is 1, so we # shouldn't see two remotes) - self.assertEqual(qplan[2].datacenter, "dc2") - self.assertEqual(3, len(qplan)) + assert qplan[2].datacenter == "dc2" + assert 3 == len(qplan) class FakeCluster: def __init__(self): @@ -661,20 +661,20 @@ def test_get_distance(self): policy.populate(self.FakeCluster(), [host]) - self.assertEqual(policy.distance(host), HostDistance.LOCAL) + assert policy.distance(host) == HostDistance.LOCAL # used_hosts_per_remote_dc is set to 0, so ignore it remote_host = Host(DefaultEndPoint("ip2"), SimpleConvictionPolicy) remote_host.set_location_info("dc2", "rack1") - self.assertEqual(policy.distance(remote_host), HostDistance.IGNORED) + assert policy.distance(remote_host) == HostDistance.IGNORED # dc2 isn't registered in the policy's live_hosts dict policy._child_policy.used_hosts_per_remote_dc = 1 - self.assertEqual(policy.distance(remote_host), HostDistance.IGNORED) + assert policy.distance(remote_host) == HostDistance.IGNORED # make sure the policy has both dcs registered policy.populate(self.FakeCluster(), [host, remote_host]) - self.assertEqual(policy.distance(remote_host), HostDistance.REMOTE) + assert policy.distance(remote_host) == HostDistance.REMOTE # since used_hosts_per_remote_dc is set to 1, only the first # remote host in dc2 will be REMOTE, the rest are IGNORED @@ -682,7 +682,7 @@ def test_get_distance(self): second_remote_host.set_location_info("dc2", "rack1") policy.populate(self.FakeCluster(), [host, remote_host, second_remote_host]) distances = set([policy.distance(remote_host), policy.distance(second_remote_host)]) - self.assertEqual(distances, set([HostDistance.REMOTE, HostDistance.IGNORED])) + assert distances == set([HostDistance.REMOTE, HostDistance.IGNORED]) def test_status_updates(self): """ @@ -710,21 +710,21 @@ def test_status_updates(self): # we now have two local hosts and two remote hosts in separate dcs qplan = list(policy.make_query_plan()) - self.assertEqual(set(qplan[:2]), set([hosts[1], new_local_host])) - self.assertEqual(set(qplan[2:]), set([hosts[3], new_remote_host])) + assert set(qplan[:2]) == set([hosts[1], new_local_host]) + assert set(qplan[2:]) == set([hosts[3], new_remote_host]) # since we have hosts in dc9000, the distance shouldn't be IGNORED - self.assertEqual(policy.distance(new_remote_host), HostDistance.REMOTE) + assert policy.distance(new_remote_host) == HostDistance.REMOTE policy.on_down(new_local_host) policy.on_down(hosts[1]) qplan = list(policy.make_query_plan()) - self.assertEqual(set(qplan), set([hosts[3], new_remote_host])) + assert set(qplan) == set([hosts[3], new_remote_host]) policy.on_down(new_remote_host) policy.on_down(hosts[3]) qplan = list(policy.make_query_plan()) - self.assertEqual(qplan, []) + assert qplan == [] def test_statement_keyspace(self): hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy) for i in range(4)] @@ -749,8 +749,8 @@ def test_statement_keyspace(self): routing_key = 'routing_key' query = Statement(routing_key=routing_key) qplan = list(policy.make_query_plan(keyspace, query)) - self.assertEqual(hosts, qplan) - self.assertEqual(cluster.metadata.get_replicas.call_count, 0) + assert hosts == qplan + assert cluster.metadata.get_replicas.call_count == 0 child_policy.make_query_plan.assert_called_once_with(keyspace, query) # working keyspace, no statement @@ -759,7 +759,7 @@ def test_statement_keyspace(self): routing_key = 'routing_key' query = Statement(routing_key=routing_key) qplan = list(policy.make_query_plan(keyspace, query)) - self.assertEqual(replicas + hosts[:2], qplan) + assert replicas + hosts[:2] == qplan cluster.metadata.get_replicas.assert_called_with(keyspace, routing_key) # statement keyspace, no working @@ -769,7 +769,7 @@ def test_statement_keyspace(self): routing_key = 'routing_key' query = Statement(routing_key=routing_key, keyspace=statement_keyspace) qplan = list(policy.make_query_plan(working_keyspace, query)) - self.assertEqual(replicas + hosts[:2], qplan) + assert replicas + hosts[:2] == qplan cluster.metadata.get_replicas.assert_called_with(statement_keyspace, routing_key) # both keyspaces set, statement keyspace used for routing @@ -779,7 +779,7 @@ def test_statement_keyspace(self): routing_key = 'routing_key' query = Statement(routing_key=routing_key, keyspace=statement_keyspace) qplan = list(policy.make_query_plan(working_keyspace, query)) - self.assertEqual(replicas + hosts[:2], qplan) + assert replicas + hosts[:2] == qplan cluster.metadata.get_replicas.assert_called_with(statement_keyspace, routing_key) def test_shuffles_if_given_keyspace_and_routing_key(self): @@ -840,15 +840,15 @@ def _assert_shuffle(self, patched_shuffle, keyspace, routing_key): query = Statement(routing_key=routing_key) qplan = list(policy.make_query_plan(keyspace, query)) if keyspace is None or routing_key is None: - self.assertEqual(hosts, qplan) - self.assertEqual(cluster.metadata.get_replicas.call_count, 0) + assert hosts == qplan + assert cluster.metadata.get_replicas.call_count == 0 child_policy.make_query_plan.assert_called_once_with(keyspace, query) - self.assertEqual(patched_shuffle.call_count, 0) + assert patched_shuffle.call_count == 0 else: - self.assertEqual(set(replicas), set(qplan[:2])) - self.assertEqual(hosts[:2], qplan[2:]) + assert set(replicas) == set(qplan[:2]) + assert hosts[:2] == qplan[2:] child_policy.make_query_plan.assert_called_once_with(keyspace, query) - self.assertEqual(patched_shuffle.call_count, 1) + assert patched_shuffle.call_count == 1 class ConvictionPolicyTest(unittest.TestCase): @@ -869,8 +869,8 @@ def test_basic_responses(self): """ conviction_policy = SimpleConvictionPolicy(1) - self.assertEqual(conviction_policy.add_failure(1), True) - self.assertEqual(conviction_policy.reset(), None) + assert conviction_policy.add_failure(1) == True + assert conviction_policy.reset() == None class ReconnectionPolicyTest(unittest.TestCase): @@ -901,9 +901,9 @@ def test_schedule(self): max_attempts = 100 policy = ConstantReconnectionPolicy(delay=delay, max_attempts=max_attempts) schedule = list(policy.new_schedule()) - self.assertEqual(len(schedule), max_attempts) + assert len(schedule) == max_attempts for i, delay in enumerate(schedule): - self.assertEqual(delay, delay) + assert delay == delay def test_schedule_negative_max_attempts(self): """ @@ -925,7 +925,7 @@ def test_schedule_infinite_attempts(self): crp = ConstantReconnectionPolicy(delay=delay, max_attempts=max_attempts) # this is infinite. we'll just verify one more than default for _, d in zip(range(65), crp.new_schedule()): - self.assertEqual(d, delay) + assert d == delay class ExponentialReconnectionPolicyTest(unittest.TestCase): @@ -947,7 +947,7 @@ def test_schedule_no_max(self): sched_slice = list(islice(policy.new_schedule(), 0, test_iter)) self._assert_between(sched_slice[0], base_delay*0.85, base_delay*1.15) self._assert_between(sched_slice[-1], max_delay*0.85, max_delay*1.15) - self.assertEqual(len(sched_slice), test_iter) + assert len(sched_slice) == test_iter def test_schedule_with_max(self): base_delay = 2.0 @@ -955,7 +955,7 @@ def test_schedule_with_max(self): max_attempts = 64 policy = ExponentialReconnectionPolicy(base_delay=base_delay, max_delay=max_delay, max_attempts=max_attempts) schedule = list(policy.new_schedule()) - self.assertEqual(len(schedule), max_attempts) + assert len(schedule) == max_attempts for i, delay in enumerate(schedule): if i == 0: self._assert_between(delay, base_delay*0.85, base_delay*1.15) @@ -972,7 +972,7 @@ def test_schedule_exactly_one_attempt(self): policy = ExponentialReconnectionPolicy( base_delay=base_delay, max_delay=max_delay, max_attempts=max_attempts ) - self.assertEqual(len(list(policy.new_schedule())), 1) + assert len(list(policy.new_schedule())) == 1 def test_schedule_overflow(self): """ @@ -1030,29 +1030,29 @@ def test_read_timeout(self): retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=1, received_responses=2, data_retrieved=True, retry_num=1) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None # if we didn't get enough responses, rethrow retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=2, received_responses=1, data_retrieved=True, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None # if we got enough responses, but also got a data response, rethrow retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=2, received_responses=2, data_retrieved=True, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None # we got enough responses but no data response, so retry retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=2, received_responses=2, data_retrieved=False, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETRY) - self.assertEqual(consistency, ONE) + assert retry == RetryPolicy.RETRY + assert consistency == ONE def test_write_timeout(self): policy = RetryPolicy() @@ -1061,22 +1061,22 @@ def test_write_timeout(self): retry, consistency = policy.on_write_timeout( query=None, consistency=ONE, write_type=WriteType.SIMPLE, required_responses=1, received_responses=2, retry_num=1) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None # if it's not a BATCH_LOG write, don't retry it retry, consistency = policy.on_write_timeout( query=None, consistency=ONE, write_type=WriteType.SIMPLE, required_responses=1, received_responses=2, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None # retry BATCH_LOG writes regardless of received responses retry, consistency = policy.on_write_timeout( query=None, consistency=ONE, write_type=WriteType.BATCH_LOG, required_responses=10000, received_responses=1, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETRY) - self.assertEqual(consistency, ONE) + assert retry == RetryPolicy.RETRY + assert consistency == ONE def test_unavailable(self): """ @@ -1087,20 +1087,20 @@ def test_unavailable(self): retry, consistency = policy.on_unavailable( query=None, consistency=ONE, required_replicas=1, alive_replicas=2, retry_num=1) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None retry, consistency = policy.on_unavailable( query=None, consistency=ONE, required_replicas=1, alive_replicas=2, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETRY_NEXT_HOST) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETRY_NEXT_HOST + assert consistency == None retry, consistency = policy.on_unavailable( query=None, consistency=ONE, required_replicas=10000, alive_replicas=1, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETRY_NEXT_HOST) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETRY_NEXT_HOST + assert consistency == None class FallthroughRetryPolicyTest(unittest.TestCase): @@ -1115,26 +1115,26 @@ def test_read_timeout(self): retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=1, received_responses=2, data_retrieved=True, retry_num=1) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=2, received_responses=1, data_retrieved=True, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=2, received_responses=2, data_retrieved=True, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=2, received_responses=2, data_retrieved=False, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None def test_write_timeout(self): policy = FallthroughRetryPolicy() @@ -1142,20 +1142,20 @@ def test_write_timeout(self): retry, consistency = policy.on_write_timeout( query=None, consistency=ONE, write_type=WriteType.SIMPLE, required_responses=1, received_responses=2, retry_num=1) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None retry, consistency = policy.on_write_timeout( query=None, consistency=ONE, write_type=WriteType.SIMPLE, required_responses=1, received_responses=2, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None retry, consistency = policy.on_write_timeout( query=None, consistency=ONE, write_type=WriteType.BATCH_LOG, required_responses=10000, received_responses=1, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None def test_unavailable(self): policy = FallthroughRetryPolicy() @@ -1163,20 +1163,20 @@ def test_unavailable(self): retry, consistency = policy.on_unavailable( query=None, consistency=ONE, required_replicas=1, alive_replicas=2, retry_num=1) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None retry, consistency = policy.on_unavailable( query=None, consistency=ONE, required_replicas=1, alive_replicas=2, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None retry, consistency = policy.on_unavailable( query=None, consistency=ONE, required_replicas=10000, alive_replicas=1, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None class DowngradingConsistencyRetryPolicyTest(unittest.TestCase): @@ -1188,50 +1188,50 @@ def test_read_timeout(self): retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=1, received_responses=2, data_retrieved=True, retry_num=1) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None # if we didn't get enough responses, retry at a lower consistency retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=4, received_responses=3, data_retrieved=True, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETRY) - self.assertEqual(consistency, ConsistencyLevel.THREE) + assert retry == RetryPolicy.RETRY + assert consistency == ConsistencyLevel.THREE # if we didn't get enough responses, retry at a lower consistency retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=3, received_responses=2, data_retrieved=True, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETRY) - self.assertEqual(consistency, ConsistencyLevel.TWO) + assert retry == RetryPolicy.RETRY + assert consistency == ConsistencyLevel.TWO # retry consistency level goes down based on the # of recv'd responses retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=3, received_responses=1, data_retrieved=True, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETRY) - self.assertEqual(consistency, ConsistencyLevel.ONE) + assert retry == RetryPolicy.RETRY + assert consistency == ConsistencyLevel.ONE # if we got no responses, rethrow retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=3, received_responses=0, data_retrieved=True, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None # if we got enough response but no data, retry retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=3, received_responses=3, data_retrieved=False, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETRY) - self.assertEqual(consistency, ONE) + assert retry == RetryPolicy.RETRY + assert consistency == ONE # if we got enough responses, but also got a data response, rethrow retry, consistency = policy.on_read_timeout( query=None, consistency=ONE, required_responses=2, received_responses=2, data_retrieved=True, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None def test_write_timeout(self): policy = DowngradingConsistencyRetryPolicy() @@ -1240,41 +1240,41 @@ def test_write_timeout(self): retry, consistency = policy.on_write_timeout( query=None, consistency=ONE, write_type=WriteType.SIMPLE, required_responses=1, received_responses=2, retry_num=1) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None for write_type in (WriteType.SIMPLE, WriteType.BATCH, WriteType.COUNTER): # ignore failures if at least one response (replica persisted) retry, consistency = policy.on_write_timeout( query=None, consistency=ONE, write_type=write_type, required_responses=1, received_responses=2, retry_num=0) - self.assertEqual(retry, RetryPolicy.IGNORE) + assert retry == RetryPolicy.IGNORE # retrhow if we can't be sure we have a replica retry, consistency = policy.on_write_timeout( query=None, consistency=ONE, write_type=write_type, required_responses=1, received_responses=0, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) + assert retry == RetryPolicy.RETHROW # downgrade consistency level on unlogged batch writes retry, consistency = policy.on_write_timeout( query=None, consistency=ONE, write_type=WriteType.UNLOGGED_BATCH, required_responses=3, received_responses=1, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETRY) - self.assertEqual(consistency, ConsistencyLevel.ONE) + assert retry == RetryPolicy.RETRY + assert consistency == ConsistencyLevel.ONE # retry batch log writes at the same consistency level retry, consistency = policy.on_write_timeout( query=None, consistency=ONE, write_type=WriteType.BATCH_LOG, required_responses=3, received_responses=1, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETRY) - self.assertEqual(consistency, ONE) + assert retry == RetryPolicy.RETRY + assert consistency == ONE # timeout on an unknown write_type retry, consistency = policy.on_write_timeout( query=None, consistency=ONE, write_type=None, required_responses=1, received_responses=2, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None def test_unavailable(self): policy = DowngradingConsistencyRetryPolicy() @@ -1282,14 +1282,14 @@ def test_unavailable(self): # if this is the second or greater attempt, rethrow retry, consistency = policy.on_unavailable( query=None, consistency=ONE, required_replicas=3, alive_replicas=1, retry_num=1) - self.assertEqual(retry, RetryPolicy.RETHROW) - self.assertEqual(consistency, None) + assert retry == RetryPolicy.RETHROW + assert consistency == None # downgrade consistency on unavailable exceptions retry, consistency = policy.on_unavailable( query=None, consistency=ONE, required_replicas=3, alive_replicas=1, retry_num=0) - self.assertEqual(retry, RetryPolicy.RETRY) - self.assertEqual(consistency, ConsistencyLevel.ONE) + assert retry == RetryPolicy.RETRY + assert consistency == ConsistencyLevel.ONE class ExponentialRetryPolicyTest(unittest.TestCase): @@ -1319,9 +1319,9 @@ def test_hosts_with_hostname(self): policy.populate(None, [host]) qplan = list(policy.make_query_plan()) - self.assertEqual(sorted(qplan), [host]) + assert sorted(qplan) == [host] - self.assertEqual(policy.distance(host), HostDistance.LOCAL) + assert policy.distance(host) == HostDistance.LOCAL def test_hosts_with_socket_hostname(self): hosts = [UnixSocketEndPoint('/tmp/scylla-workdir/cql.m')] @@ -1330,9 +1330,9 @@ def test_hosts_with_socket_hostname(self): policy.populate(None, [host]) qplan = list(policy.make_query_plan()) - self.assertEqual(sorted(qplan), [host]) + assert sorted(qplan) == [host] - self.assertEqual(policy.distance(host), HostDistance.LOCAL) + assert policy.distance(host) == HostDistance.LOCAL class AddressTranslatorTest(unittest.TestCase): @@ -1346,7 +1346,7 @@ def test_ec2_multi_region_translator(self, *_): addr = '127.0.0.1' translated = ec2t.translate(addr) self.assertIsNot(translated, addr) # verifies that the resolver path is followed - self.assertEqual(translated, addr) # and that it resolves to the same address + assert translated == addr # and that it resolves to the same address class HostFilterPolicyInitTest(unittest.TestCase): @@ -1456,8 +1456,7 @@ def setUp(self): self.accepted_host = Host(DefaultEndPoint('acceptme'), conviction_policy_factory=Mock()) def test_ignored_with_filter(self): - self.assertEqual(self.hfp.distance(self.ignored_host), - HostDistance.IGNORED) + assert self.hfp.distance(self.ignored_host) == HostDistance.IGNORED self.assertNotEqual(self.hfp.distance(self.accepted_host), HostDistance.IGNORED) @@ -1467,9 +1466,9 @@ def test_accepted_filter_defers_to_child_policy(self): # getting the distance for an ignored host shouldn't affect subsequent results self.hfp.distance(self.ignored_host) # first call of _child_policy with count() side effect - self.assertEqual(self.hfp.distance(self.accepted_host), distances[0]) + assert self.hfp.distance(self.accepted_host) == distances[0] # second call of _child_policy with count() side effect - self.assertEqual(self.hfp.distance(self.accepted_host), distances[1]) + assert self.hfp.distance(self.accepted_host) == distances[1] class HostFilterPolicyPopulateTest(unittest.TestCase): @@ -1496,10 +1495,7 @@ def test_child_is_populated_with_filtered_hosts(self): ['acceptme0', 'acceptme1']) hfp.populate(mock_cluster, hosts) hfp._child_policy.populate.assert_called_once() - self.assertEqual( - hfp._child_policy.populate.call_args[1]['hosts'], - ['acceptme0', 'acceptme1'] - ) + assert hfp._child_policy.populate.call_args[1]['hosts'] == ['acceptme0', 'acceptme1'] class HostFilterPolicyQueryPlanTest(unittest.TestCase): @@ -1523,7 +1519,7 @@ def test_query_plan_deferred_to_child(self): working_keyspace=working_keyspace, query=query ) - self.assertEqual(qp, hfp._child_policy.make_query_plan.return_value) + assert qp == hfp._child_policy.make_query_plan.return_value def test_wrap_token_aware(self): cluster = Mock(spec=Cluster) @@ -1553,9 +1549,9 @@ def get_replicas(keyspace, packed_key): query_plan = hfp.make_query_plan("keyspace", mocked_query) # First the not filtered replica, and then the rest of the allowed hosts ordered query_plan = list(query_plan) - self.assertEqual(query_plan[0], Host(DefaultEndPoint("127.0.0.2"), SimpleConvictionPolicy)) - self.assertEqual(set(query_plan[1:]),{Host(DefaultEndPoint("127.0.0.3"), SimpleConvictionPolicy), - Host(DefaultEndPoint("127.0.0.5"), SimpleConvictionPolicy)}) + assert query_plan[0] == Host(DefaultEndPoint("127.0.0.2"), SimpleConvictionPolicy) + assert set(query_plan[1:]) == {Host(DefaultEndPoint("127.0.0.3"), SimpleConvictionPolicy), + Host(DefaultEndPoint("127.0.0.5"), SimpleConvictionPolicy)} def test_create_whitelist(self): cluster = Mock(spec=Cluster) @@ -1577,5 +1573,5 @@ def test_create_whitelist(self): mocked_query = Mock() query_plan = hfp.make_query_plan("keyspace", mocked_query) # Only the filtered replicas should be allowed - self.assertEqual(set(query_plan), {Host(DefaultEndPoint("127.0.0.1"), SimpleConvictionPolicy), - Host(DefaultEndPoint("127.0.0.4"), SimpleConvictionPolicy)}) + assert set(query_plan) == {Host(DefaultEndPoint("127.0.0.1"), SimpleConvictionPolicy), + Host(DefaultEndPoint("127.0.0.4"), SimpleConvictionPolicy)} diff --git a/tests/unit/test_protocol.py b/tests/unit/test_protocol.py index 907f62f2bb..5ea7775b94 100644 --- a/tests/unit/test_protocol.py +++ b/tests/unit/test_protocol.py @@ -88,10 +88,7 @@ def test_query_message(self): self._check_calls(io, [(b'\x00\x00\x00\x01',), (b'a',), (b'\x00\x03',), (b'\x00\x00\x00\x00',)]) def _check_calls(self, io, expected): - self.assertEqual( - tuple(c[1] for c in io.write.mock_calls), - tuple(expected) - ) + assert tuple(c[1] for c in io.write.mock_calls) == tuple(expected) def test_continuous_paging(self): """ @@ -118,16 +115,16 @@ def test_continuous_paging(self): message.send_body(io, ProtocolVersion.DSE_V1) # continuous paging adds two write calls to the buffer - self.assertEqual(len(io.write.mock_calls), 6) + assert len(io.write.mock_calls) == 6 # Check that the appropriate flag is set to True - self.assertEqual(uint32_unpack(io.write.mock_calls[3][1][0]) & _WITH_SERIAL_CONSISTENCY_FLAG, 0) - self.assertEqual(uint32_unpack(io.write.mock_calls[3][1][0]) & _PAGE_SIZE_FLAG, 0) - self.assertEqual(uint32_unpack(io.write.mock_calls[3][1][0]) & _WITH_PAGING_STATE_FLAG, 0) - self.assertEqual(uint32_unpack(io.write.mock_calls[3][1][0]) & _PAGING_OPTIONS_FLAG, _PAGING_OPTIONS_FLAG) + assert uint32_unpack(io.write.mock_calls[3][1][0]) & _WITH_SERIAL_CONSISTENCY_FLAG == 0 + assert uint32_unpack(io.write.mock_calls[3][1][0]) & _PAGE_SIZE_FLAG == 0 + assert uint32_unpack(io.write.mock_calls[3][1][0]) & _WITH_PAGING_STATE_FLAG == 0 + assert uint32_unpack(io.write.mock_calls[3][1][0]) & _PAGING_OPTIONS_FLAG == _PAGING_OPTIONS_FLAG # Test max_pages and max_pages_per_second are correctly written - self.assertEqual(uint32_unpack(io.write.mock_calls[4][1][0]), max_pages) - self.assertEqual(uint32_unpack(io.write.mock_calls[5][1][0]), max_pages_per_second) + assert uint32_unpack(io.write.mock_calls[4][1][0]) == max_pages + assert uint32_unpack(io.write.mock_calls[5][1][0]) == max_pages_per_second def test_prepare_flag(self): """ @@ -144,9 +141,9 @@ def test_prepare_flag(self): for version in ProtocolVersion.SUPPORTED_VERSIONS: message.send_body(io, version) if ProtocolVersion.uses_prepare_flags(version): - self.assertEqual(len(io.write.mock_calls), 3) + assert len(io.write.mock_calls) == 3 else: - self.assertEqual(len(io.write.mock_calls), 2) + assert len(io.write.mock_calls) == 2 io.reset_mock() def test_prepare_flag_with_keyspace(self): diff --git a/tests/unit/test_protocol_features.py b/tests/unit/test_protocol_features.py index bcf874f68f..89b568ea68 100644 --- a/tests/unit/test_protocol_features.py +++ b/tests/unit/test_protocol_features.py @@ -22,6 +22,6 @@ class OptionsHolder(object): protocol_features = ProtocolFeatures.parse_from_supported(OptionsHolder().options) - self.assertEqual(protocol_features.rate_limit_error, 123) - self.assertEqual(protocol_features.shard_id, 0) - self.assertEqual(protocol_features.sharding_info, None) + assert protocol_features.rate_limit_error == 123 + assert protocol_features.shard_id == 0 + assert protocol_features.sharding_info is None diff --git a/tests/unit/test_query.py b/tests/unit/test_query.py index 8a3f00fa9d..7eaa755256 100644 --- a/tests/unit/test_query.py +++ b/tests/unit/test_query.py @@ -31,9 +31,9 @@ def test_clear(self): batch.add(ss) self.assertTrue(batch._statements_and_parameters) - self.assertEqual(batch.keyspace, keyspace) - self.assertEqual(batch.routing_key, routing_key) - self.assertEqual(batch.custom_payload, custom_payload) + assert batch.keyspace == keyspace + assert batch.routing_key == routing_key + assert batch.custom_payload == custom_payload batch.clear() self.assertFalse(batch._statements_and_parameters) @@ -60,11 +60,11 @@ def test_add_all(self): batch.add_all(statements, parameters) bound_statements = [t[1] for t in batch._statements_and_parameters] str_parameters = [str(i) for i in range(10)] - self.assertEqual(bound_statements, str_parameters) + assert bound_statements == str_parameters def test_len(self): for n in 0, 10, 100: batch = BatchStatement() batch.add_all(statements=['%s'] * n, parameters=[(i,) for i in range(n)]) - self.assertEqual(len(batch), n) + assert len(batch) == n diff --git a/tests/unit/test_response_future.py b/tests/unit/test_response_future.py index 8226cea440..7f1f14553f 100644 --- a/tests/unit/test_response_future.py +++ b/tests/unit/test_response_future.py @@ -81,7 +81,7 @@ def test_result_message(self): expected_result = (object(), object()) rf._set_result(None, None, None, self.make_mock_response(expected_result[0], expected_result[1])) result = rf.result()[0] - self.assertEqual(result, expected_result) + assert result == expected_result def test_unknown_result_class(self): session = self.make_session() @@ -126,7 +126,7 @@ def test_other_result_message_kind(self): rf.send_request() result = Mock(spec=ResultMessage, kind=999, results=[1, 2, 3]) rf._set_result(None, None, None, result) - self.assertEqual(rf.result()[0], result) + assert rf.result()[0] == result def test_heartbeat_defunct_deadlock(self): """ @@ -264,7 +264,7 @@ def test_retry_policy_says_retry(self): rf._set_result(host, None, None, result) rf.session.cluster.scheduler.schedule.assert_called_once_with(ANY, rf._retry_task, True, host) - self.assertEqual(1, rf._query_retries) + assert 1 == rf._query_retries connection = Mock(spec=Connection) pool.borrow_connection.return_value = (connection, 2) @@ -292,7 +292,7 @@ def test_retry_with_different_host(self): rf.session._pools.get.assert_called_once_with('ip1') pool.borrow_connection.assert_called_once_with(timeout=ANY, routing_key=ANY, keyspace=ANY, table=ANY) connection.send_msg.assert_called_once_with(rf.message, 1, cb=ANY, encoder=ProtocolHandler.encode_message, decoder=ProtocolHandler.decode_message, result_metadata=[]) - self.assertEqual(ConsistencyLevel.QUORUM, rf.message.consistency_level) + assert ConsistencyLevel.QUORUM == rf.message.consistency_level result = Mock(spec=OverloadedErrorMessage, info={}) host = Mock() @@ -300,7 +300,7 @@ def test_retry_with_different_host(self): rf.session.cluster.scheduler.schedule.assert_called_once_with(ANY, rf._retry_task, False, host) # query_retries does get incremented for Overloaded/Bootstrapping errors (since 3.18) - self.assertEqual(1, rf._query_retries) + assert 1 == rf._query_retries connection = Mock(spec=Connection) pool.borrow_connection.return_value = (connection, 2) @@ -313,7 +313,7 @@ def test_retry_with_different_host(self): connection.send_msg.assert_called_with(rf.message, 2, cb=ANY, encoder=ProtocolHandler.encode_message, decoder=ProtocolHandler.decode_message, result_metadata=[]) # the consistency level should be the same - self.assertEqual(ConsistencyLevel.QUORUM, rf.message.consistency_level) + assert ConsistencyLevel.QUORUM == rf.message.consistency_level def test_all_retries_fail(self): session = self.make_session() @@ -395,7 +395,7 @@ def test_first_pool_shutdown(self): rf._set_result(None, None, None, self.make_mock_response(expected_result[0], expected_result[1])) result = rf.result()[0] - self.assertEqual(result, expected_result) + assert result == expected_result def test_timeout_getting_connection_from_pool(self): session = self.make_basic_session() @@ -418,10 +418,10 @@ def test_timeout_getting_connection_from_pool(self): expected_result = (object(), object()) rf._set_result(None, None, None, self.make_mock_response(expected_result[0], expected_result[1])) - self.assertEqual(rf.result()[0], expected_result) + assert rf.result()[0] == expected_result # make sure the exception is recorded correctly - self.assertEqual(rf._errors, {'ip1': exc}) + assert rf._errors == {'ip1': exc} def test_callback(self): session = self.make_session() @@ -437,7 +437,7 @@ def test_callback(self): rf._set_result(None, None, None, self.make_mock_response(expected_result[0], expected_result[1])) result = rf.result()[0] - self.assertEqual(result, expected_result) + assert result == expected_result callback.assert_called_once_with([expected_result], arg, **kwargs) @@ -487,7 +487,7 @@ def test_multiple_callbacks(self): rf._set_result(None, None, None, self.make_mock_response(expected_result[0], expected_result[1])) result = rf.result()[0] - self.assertEqual(result, expected_result) + assert result == expected_result callback.assert_called_once_with([expected_result], arg, **kwargs) callback2.assert_called_once_with([expected_result], arg2, **kwargs2) @@ -559,7 +559,7 @@ def test_add_callbacks(self): errback=self.assertIsInstance, errback_args=(Exception,)) rf._set_result(None, None, None, self.make_mock_response(expected_result[0], expected_result[1])) - self.assertEqual(rf.result()[0], expected_result) + assert rf.result()[0] == expected_result callback.assert_called_once_with([expected_result], arg, **kwargs) @@ -584,9 +584,9 @@ def test_prepared_query_not_found(self): self.assertTrue(session.submit.call_args) args, kwargs = session.submit.call_args - self.assertEqual(rf._reprepare, args[-5]) + assert rf._reprepare == args[-5] self.assertIsInstance(args[-4], PrepareMessage) - self.assertEqual(args[-4].query, "SELECT * FROM foobar") + assert args[-4].query == "SELECT * FROM foobar" def test_prepared_query_not_found_bad_keyspace(self): session = self.make_session() diff --git a/tests/unit/test_resultset.py b/tests/unit/test_resultset.py index 7ff6352394..20c2b68efd 100644 --- a/tests/unit/test_resultset.py +++ b/tests/unit/test_resultset.py @@ -55,8 +55,8 @@ def test_list_non_paged(self): expected = list(range(10)) rs = ResultSet(Mock(has_more_pages=False), expected) for i in range(10): - self.assertEqual(rs[i], expected[i]) - self.assertEqual(list(rs), expected) + assert rs[i] == expected[i] + assert list(rs) == expected def test_list_paged(self): # list access on RS for backwards-compatibility @@ -66,8 +66,8 @@ def test_list_paged(self): rs = ResultSet(response_future, expected[:5]) # this is brittle, depends on internal impl details. Would like to find a better way type(response_future).has_more_pages = PropertyMock(side_effect=(True, True, True, False)) # First two True are consumed on check entering list mode - self.assertEqual(rs[9], expected[9]) - self.assertEqual(list(rs), expected) + assert rs[9] == expected[9] + assert list(rs) == expected def test_has_more_pages(self): response_future = Mock() @@ -118,7 +118,7 @@ def test_index_list_mode(self): rs = ResultSet(Mock(has_more_pages=False), expected) # index access before iteration causes list to be materialized - self.assertEqual(rs[0], expected[0]) + assert rs[0] == expected[0] # resusable iteration self.assertListEqual(list(rs), expected) @@ -133,8 +133,8 @@ def test_index_list_mode(self): # this is brittle, depends on internal impl details. Would like to find a better way type(response_future).has_more_pages = PropertyMock(side_effect=(True, True, True, False)) # First two True are consumed on check entering list mode # index access before iteration causes list to be materialized - self.assertEqual(rs[0], expected[0]) - self.assertEqual(rs[9], expected[9]) + assert rs[0] == expected[0] + assert rs[9] == expected[9] # resusable iteration self.assertListEqual(list(rs), expected) self.assertListEqual(list(rs), expected) @@ -147,11 +147,11 @@ def test_eq(self): rs = ResultSet(Mock(has_more_pages=False), expected) # eq before iteration causes list to be materialized - self.assertEqual(rs, expected) + assert rs == expected # results can be iterated or indexed once we're materialized self.assertListEqual(list(rs), expected) - self.assertEqual(rs[9], expected[9]) + assert rs[9] == expected[9] self.assertTrue(rs) # pages @@ -160,11 +160,11 @@ def test_eq(self): rs = ResultSet(response_future, expected[:5]) type(response_future).has_more_pages = PropertyMock(side_effect=(True, True, True, False)) # eq before iteration causes list to be materialized - self.assertEqual(rs, expected) + assert rs == expected # results can be iterated or indexed once we're materialized self.assertListEqual(list(rs), expected) - self.assertEqual(rs[9], expected[9]) + assert rs[9] == expected[9] self.assertTrue(rs) def test_bool(self): @@ -190,26 +190,26 @@ def test_was_applied(self): for row_factory in (named_tuple_factory, tuple_factory): for applied in (True, False): rs = ResultSet(Mock(row_factory=row_factory), [(applied,)]) - self.assertEqual(rs.was_applied, applied) + assert rs.was_applied == applied row_factory = dict_factory for applied in (True, False): rs = ResultSet(Mock(row_factory=row_factory), [{'[applied]': applied}]) - self.assertEqual(rs.was_applied, applied) + assert rs.was_applied == applied def test_one(self): # no pages first, second = Mock(), Mock() rs = ResultSet(Mock(has_more_pages=False), [first, second]) - self.assertEqual(rs.one(), first) + assert rs.one() == first def test_all(self): first, second = Mock(), Mock() rs1 = ResultSet(Mock(has_more_pages=False), [first, second]) rs2 = ResultSet(Mock(has_more_pages=False), [first, second]) - self.assertEqual(rs1.all(), list(rs2)) + assert rs1.all() == list(rs2) @patch('cassandra.cluster.warn') def test_indexing_deprecation(self, mocked_warn): @@ -217,8 +217,8 @@ def test_indexing_deprecation(self, mocked_warn): # pre-Py3.0 for some reason first, second = Mock(), Mock() rs = ResultSet(Mock(has_more_pages=False), [first, second]) - self.assertEqual(rs[0], first) - self.assertEqual(len(mocked_warn.mock_calls), 1) + assert rs[0] == first + assert len(mocked_warn.mock_calls) == 1 index_warning_args = tuple(mocked_warn.mock_calls[0])[1] self.assertIn('indexing support will be removed in 4.0', str(index_warning_args[0])) diff --git a/tests/unit/test_row_factories.py b/tests/unit/test_row_factories.py index 70691ad8fd..8d41291095 100644 --- a/tests/unit/test_row_factories.py +++ b/tests/unit/test_row_factories.py @@ -61,13 +61,13 @@ def test_creation_warning_on_long_column_list(self): with warnings.catch_warnings(record=True) as w: rows = named_tuple_factory(self.long_colnames, self.long_rows) - self.assertEqual(len(w), 1) + assert len(w) == 1 warning = w[0] self.assertIn('pseudo_namedtuple_factory', str(warning)) self.assertIn('3.7', str(warning)) for r in rows: - self.assertEqual(r.col0, self.long_rows[0][0]) + assert r.col0 == self.long_rows[0][0] def test_creation_no_warning_on_short_column_list(self): """ @@ -81,7 +81,7 @@ def test_creation_no_warning_on_short_column_list(self): """ with warnings.catch_warnings(record=True) as w: rows = named_tuple_factory(self.short_colnames, self.short_rows) - self.assertEqual(len(w), 0) + assert len(w) == 0 # check that this is a real namedtuple self.assertTrue(hasattr(rows[0], '_fields')) self.assertIsInstance(rows[0], tuple) diff --git a/tests/unit/test_segment.py b/tests/unit/test_segment.py index 0d0f146c16..459b68dd3a 100644 --- a/tests/unit/test_segment.py +++ b/tests/unit/test_segment.py @@ -50,10 +50,8 @@ def _header_to_bits(data): def test_encode_uncompressed_header(self): buffer = BytesIO() segment_codec_no_compression.encode_header(buffer, len(self.small_msg), -1, True) - self.assertEqual(buffer.tell(), 6) - self.assertEqual( - self._header_to_bits(buffer.getvalue()), - "00000000000110010" + "1" + "000000") + assert buffer.tell() == 6 + assert self._header_to_bits(buffer.getvalue()) == "00000000000110010" + "1" + "000000" @unittest.skipUnless(segment_codec_lz4, ' lz4 not installed') def test_encode_compressed_header(self): @@ -61,18 +59,14 @@ def test_encode_compressed_header(self): compressed_length = len(segment_codec_lz4.compress(self.small_msg)) segment_codec_lz4.encode_header(buffer, compressed_length, len(self.small_msg), True) - self.assertEqual(buffer.tell(), 8) - self.assertEqual( - self._header_to_bits(buffer.getvalue()), - "{:017b}".format(compressed_length) + "00000000000110010" + "1" + "00000") + assert buffer.tell() == 8 + assert self._header_to_bits(buffer.getvalue()) == "{:017b}".format(compressed_length) + "00000000000110010" + "1" + "00000" def test_encode_uncompressed_header_with_max_payload(self): buffer = BytesIO() segment_codec_no_compression.encode_header(buffer, len(self.max_msg), -1, True) - self.assertEqual(buffer.tell(), 6) - self.assertEqual( - self._header_to_bits(buffer.getvalue()), - "11111111111111111" + "1" + "000000") + assert buffer.tell() == 6 + assert self._header_to_bits(buffer.getvalue()) == "11111111111111111" + "1" + "000000" def test_encode_header_fails_if_payload_too_big(self): buffer = BytesIO() @@ -84,22 +78,18 @@ def test_encode_uncompressed_header_not_self_contained_msg(self): buffer = BytesIO() # simulate the first chunk with the max size segment_codec_no_compression.encode_header(buffer, len(self.max_msg), -1, False) - self.assertEqual(buffer.tell(), 6) - self.assertEqual( - self._header_to_bits(buffer.getvalue()), - ("11111111111111111" - "0" # not self contained - "000000")) + assert buffer.tell() == 6 + assert self._header_to_bits(buffer.getvalue()) == ("11111111111111111" + "0" # not self contained + "000000") @unittest.skipUnless(segment_codec_lz4, ' lz4 not installed') def test_encode_compressed_header_with_max_payload(self): buffer = BytesIO() compressed_length = len(segment_codec_lz4.compress(self.max_msg)) segment_codec_lz4.encode_header(buffer, compressed_length, len(self.max_msg), True) - self.assertEqual(buffer.tell(), 8) - self.assertEqual( - self._header_to_bits(buffer.getvalue()), - "{:017b}".format(compressed_length) + "11111111111111111" + "1" + "00000") + assert buffer.tell() == 8 + assert self._header_to_bits(buffer.getvalue()) == "{:017b}".format(compressed_length) + "11111111111111111" + "1" + "00000" @unittest.skipUnless(segment_codec_lz4, ' lz4 not installed') def test_encode_compressed_header_not_self_contained_msg(self): @@ -107,22 +97,20 @@ def test_encode_compressed_header_not_self_contained_msg(self): # simulate the first chunk with the max size compressed_length = len(segment_codec_lz4.compress(self.max_msg)) segment_codec_lz4.encode_header(buffer, compressed_length, len(self.max_msg), False) - self.assertEqual(buffer.tell(), 8) - self.assertEqual( - self._header_to_bits(buffer.getvalue()), - ("{:017b}".format(compressed_length) + - "11111111111111111" - "0" # not self contained - "00000")) + assert buffer.tell() == 8 + assert self._header_to_bits(buffer.getvalue()) == ("{:017b}".format(compressed_length) + + "11111111111111111" + "0" # not self contained + "00000") def test_decode_uncompressed_header(self): buffer = BytesIO() segment_codec_no_compression.encode_header(buffer, len(self.small_msg), -1, True) buffer.seek(0) header = segment_codec_no_compression.decode_header(buffer) - self.assertEqual(header.uncompressed_payload_length, -1) - self.assertEqual(header.payload_length, len(self.small_msg)) - self.assertEqual(header.is_self_contained, True) + assert header.uncompressed_payload_length == -1 + assert header.payload_length == len(self.small_msg) + assert header.is_self_contained == True @unittest.skipUnless(segment_codec_lz4, ' lz4 not installed') def test_decode_compressed_header(self): @@ -131,9 +119,9 @@ def test_decode_compressed_header(self): segment_codec_lz4.encode_header(buffer, compressed_length, len(self.small_msg), True) buffer.seek(0) header = segment_codec_lz4.decode_header(buffer) - self.assertEqual(header.uncompressed_payload_length, len(self.small_msg)) - self.assertEqual(header.payload_length, compressed_length) - self.assertEqual(header.is_self_contained, True) + assert header.uncompressed_payload_length == len(self.small_msg) + assert header.payload_length == compressed_length + assert header.is_self_contained == True def test_decode_header_fails_if_corrupted(self): buffer = BytesIO() @@ -154,10 +142,10 @@ def test_decode_uncompressed_self_contained_segment(self): header = segment_codec_no_compression.decode_header(buffer) segment = segment_codec_no_compression.decode(buffer, header) - self.assertEqual(header.is_self_contained, True) - self.assertEqual(header.uncompressed_payload_length, -1) - self.assertEqual(header.payload_length, len(self.small_msg)) - self.assertEqual(segment.payload, self.small_msg) + assert header.is_self_contained == True + assert header.uncompressed_payload_length == -1 + assert header.payload_length == len(self.small_msg) + assert segment.payload == self.small_msg @unittest.skipUnless(segment_codec_lz4, ' lz4 not installed') def test_decode_compressed_self_contained_segment(self): @@ -168,10 +156,10 @@ def test_decode_compressed_self_contained_segment(self): header = segment_codec_lz4.decode_header(buffer) segment = segment_codec_lz4.decode(buffer, header) - self.assertEqual(header.is_self_contained, True) - self.assertEqual(header.uncompressed_payload_length, len(self.small_msg)) + assert header.is_self_contained == True + assert header.uncompressed_payload_length == len(self.small_msg) self.assertGreater(header.uncompressed_payload_length, header.payload_length) - self.assertEqual(segment.payload, self.small_msg) + assert segment.payload == self.small_msg def test_decode_multi_segments(self): buffer = BytesIO() @@ -188,7 +176,7 @@ def test_decode_multi_segments(self): self.assertTrue(all([h.is_self_contained is False for h in headers])) decoded_msg = segments[0].payload + segments[1].payload - self.assertEqual(decoded_msg, self.large_msg) + assert decoded_msg == self.large_msg @unittest.skipUnless(segment_codec_lz4, ' lz4 not installed') def test_decode_fails_if_corrupted(self): @@ -208,6 +196,6 @@ def test_decode_tiny_msg_not_compressed(self): buffer.seek(0) header = segment_codec_lz4.decode_header(buffer) segment = segment_codec_lz4.decode(buffer, header) - self.assertEqual(header.uncompressed_payload_length, 0) - self.assertEqual(header.payload_length, 1) - self.assertEqual(segment.payload, b'b') + assert header.uncompressed_payload_length == 0 + assert header.payload_length == 1 + assert segment.payload == b'b' diff --git a/tests/unit/test_shard_aware.py b/tests/unit/test_shard_aware.py index fe7b95edba..8b34eb2578 100644 --- a/tests/unit/test_shard_aware.py +++ b/tests/unit/test_shard_aware.py @@ -46,12 +46,12 @@ class OptionsHolder(object): } shard_id, shard_info = ProtocolFeatures.parse_sharding_info(OptionsHolder().options) - self.assertEqual(shard_id, 1) - self.assertEqual(shard_info.shard_id_from_token(Murmur3Token.from_key(b"a").value), 4) - self.assertEqual(shard_info.shard_id_from_token(Murmur3Token.from_key(b"b").value), 6) - self.assertEqual(shard_info.shard_id_from_token(Murmur3Token.from_key(b"c").value), 6) - self.assertEqual(shard_info.shard_id_from_token(Murmur3Token.from_key(b"e").value), 4) - self.assertEqual(shard_info.shard_id_from_token(Murmur3Token.from_key(b"100000").value), 2) + assert shard_id == 1 + assert shard_info.shard_id_from_token(Murmur3Token.from_key(b"a").value) == 4 + assert shard_info.shard_id_from_token(Murmur3Token.from_key(b"b").value) == 6 + assert shard_info.shard_id_from_token(Murmur3Token.from_key(b"c").value) == 6 + assert shard_info.shard_id_from_token(Murmur3Token.from_key(b"e").value) == 4 + assert shard_info.shard_id_from_token(Murmur3Token.from_key(b"100000").value) == 2 def test_advanced_shard_aware_port(self): """ diff --git a/tests/unit/test_sortedset.py b/tests/unit/test_sortedset.py index 49c3658df8..887a046b5d 100644 --- a/tests/unit/test_sortedset.py +++ b/tests/unit/test_sortedset.py @@ -25,11 +25,11 @@ def test_init(self): input = [5, 4, 3, 2, 1, 1, 1] expected = sorted(set(input)) ss = sortedset(input) - self.assertEqual(len(ss), len(expected)) - self.assertEqual(list(ss), expected) + assert len(ss) == len(expected) + assert list(ss) == expected def test_repr(self): - self.assertEqual(repr(sortedset([1, 2, 3, 4])), "SortedSet([1, 2, 3, 4])") + assert repr(sortedset([1, 2, 3, 4])) == "SortedSet([1, 2, 3, 4])" def test_contains(self): input = [5, 4, 3, 2, 1, 1, 1] @@ -49,12 +49,12 @@ def test_contains(self): def test_mutable_contents(self): ba = bytearray(b'some data here') ss = sortedset([ba, ba]) - self.assertEqual(list(ss), [ba]) + assert list(ss) == [ba] def test_clear(self): ss = sortedset([1, 2, 3]) ss.clear() - self.assertEqual(len(ss), 0) + assert len(ss) == 0 def test_equal(self): s1 = set([1]) @@ -62,10 +62,10 @@ def test_equal(self): ss1 = sortedset(s1) ss12 = sortedset(s12) - self.assertEqual(ss1, s1) - self.assertEqual(ss12, s12) - self.assertEqual(ss12, s12) - self.assertEqual(ss1.__eq__(None), NotImplemented) + assert ss1 == s1 + assert ss12 == s12 + assert ss12 == s12 + assert ss1.__eq__(None) == NotImplemented self.assertNotEqual(ss1, ss12) self.assertNotEqual(ss12, ss1) self.assertNotEqual(ss1, s12) @@ -145,45 +145,45 @@ def test_union(self): ss12 = sortedset([1, 2]) ss23 = sortedset([2, 3]) - self.assertEqual(sortedset().union(s1), sortedset([1])) - self.assertEqual(ss12.union(s1), sortedset([1, 2])) - self.assertEqual(ss12.union(ss23), sortedset([1, 2, 3])) - self.assertEqual(ss23.union(ss12), sortedset([1, 2, 3])) - self.assertEqual(ss23.union(s1), sortedset([1, 2, 3])) + assert sortedset().union(s1) == sortedset([1]) + assert ss12.union(s1) == sortedset([1, 2]) + assert ss12.union(ss23) == sortedset([1, 2, 3]) + assert ss23.union(ss12) == sortedset([1, 2, 3]) + assert ss23.union(s1) == sortedset([1, 2, 3]) def test_intersection(self): s12 = set([1, 2]) ss23 = sortedset([2, 3]) - self.assertEqual(s12.intersection(ss23), set([2])) - self.assertEqual(ss23.intersection(s12), sortedset([2])) - self.assertEqual(ss23.intersection(s12, [2], (2,)), sortedset([2])) - self.assertEqual(ss23.intersection(s12, [900], (2,)), sortedset()) + assert s12.intersection(ss23) == set([2]) + assert ss23.intersection(s12) == sortedset([2]) + assert ss23.intersection(s12, [2], (2,)) == sortedset([2]) + assert ss23.intersection(s12, [900], (2,)) == sortedset() def test_difference(self): s1 = set([1]) ss12 = sortedset([1, 2]) ss23 = sortedset([2, 3]) - self.assertEqual(sortedset().difference(s1), sortedset()) - self.assertEqual(ss12.difference(s1), sortedset([2])) - self.assertEqual(ss12.difference(ss23), sortedset([1])) - self.assertEqual(ss23.difference(ss12), sortedset([3])) - self.assertEqual(ss23.difference(s1), sortedset([2, 3])) + assert sortedset().difference(s1) == sortedset() + assert ss12.difference(s1) == sortedset([2]) + assert ss12.difference(ss23) == sortedset([1]) + assert ss23.difference(ss12) == sortedset([3]) + assert ss23.difference(s1) == sortedset([2, 3]) def test_symmetric_difference(self): s = set([1, 3, 5]) ss = sortedset([2, 3, 4]) ss2 = sortedset([5, 6, 7]) - self.assertEqual(ss.symmetric_difference(s), sortedset([1, 2, 4, 5])) + assert ss.symmetric_difference(s) == sortedset([1, 2, 4, 5]) self.assertFalse(ss.symmetric_difference(ss)) - self.assertEqual(ss.symmetric_difference(s), sortedset([1, 2, 4, 5])) - self.assertEqual(ss2.symmetric_difference(ss), sortedset([2, 3, 4, 5, 6, 7])) + assert ss.symmetric_difference(s) == sortedset([1, 2, 4, 5]) + assert ss2.symmetric_difference(ss) == sortedset([2, 3, 4, 5, 6, 7]) def test_pop(self): ss = sortedset([2, 1]) - self.assertEqual(ss.pop(), 2) - self.assertEqual(ss.pop(), 1) + assert ss.pop() == 2 + assert ss.pop() == 1 try: ss.pop() self.fail("Error not thrown") @@ -192,11 +192,11 @@ def test_pop(self): def test_remove(self): ss = sortedset([2, 1]) - self.assertEqual(len(ss), 2) + assert len(ss) == 2 self.assertRaises(KeyError, ss.remove, 3) - self.assertEqual(len(ss), 2) + assert len(ss) == 2 ss.remove(1) - self.assertEqual(len(ss), 1) + assert len(ss) == 1 ss.remove(2) self.assertFalse(ss) self.assertRaises(KeyError, ss.remove, 2) @@ -205,7 +205,7 @@ def test_remove(self): def test_getitem(self): ss = sortedset(range(3)) for i in range(len(ss)): - self.assertEqual(ss[i], i) + assert ss[i] == i with self.assertRaises(IndexError): ss[len(ss)] @@ -266,115 +266,115 @@ def test_operators(self): self.assertTrue(ss12 > ss1) # __and__ - self.assertEqual(ss1 & ss12, ss1) - self.assertEqual(ss12 & ss12, ss12) - self.assertEqual(ss12 & set(), sortedset()) + assert ss1 & ss12 == ss1 + assert ss12 & ss12 == ss12 + assert ss12 & set() == sortedset() # __iand__ tmp = sortedset(ss12) tmp &= ss1 - self.assertEqual(tmp, ss1) + assert tmp == ss1 tmp = sortedset(ss1) tmp &= ss12 - self.assertEqual(tmp, ss1) + assert tmp == ss1 tmp = sortedset(ss12) tmp &= ss12 - self.assertEqual(tmp, ss12) + assert tmp == ss12 tmp = sortedset(ss12) tmp &= set() - self.assertEqual(tmp, sortedset()) + assert tmp == sortedset() # __rand__ - self.assertEqual(set([1]) & ss12, ss1) + assert set([1]) & ss12 == ss1 # __or__ - self.assertEqual(ss1 | ss12, ss12) - self.assertEqual(ss12 | ss12, ss12) - self.assertEqual(ss12 | set(), ss12) - self.assertEqual(sortedset() | ss1 | ss12, ss12) + assert ss1 | ss12 == ss12 + assert ss12 | ss12 == ss12 + assert ss12 | set() == ss12 + assert sortedset() | ss1 | ss12 == ss12 # __ior__ tmp = sortedset(ss1) tmp |= ss12 - self.assertEqual(tmp, ss12) + assert tmp == ss12 tmp = sortedset(ss12) tmp |= ss12 - self.assertEqual(tmp, ss12) + assert tmp == ss12 tmp = sortedset(ss12) tmp |= set() - self.assertEqual(tmp, ss12) + assert tmp == ss12 tmp = sortedset() tmp |= ss1 tmp |= ss12 - self.assertEqual(tmp, ss12) + assert tmp == ss12 # __ror__ - self.assertEqual(set([1]) | ss12, ss12) + assert set([1]) | ss12 == ss12 # __sub__ - self.assertEqual(ss1 - ss12, set()) - self.assertEqual(ss12 - ss12, set()) - self.assertEqual(ss12 - set(), ss12) - self.assertEqual(ss12 - ss1, sortedset([2])) + assert ss1 - ss12 == set() + assert ss12 - ss12 == set() + assert ss12 - set() == ss12 + assert ss12 - ss1 == sortedset([2]) # __isub__ tmp = sortedset(ss1) tmp -= ss12 - self.assertEqual(tmp, set()) + assert tmp == set() tmp = sortedset(ss12) tmp -= ss12 - self.assertEqual(tmp, set()) + assert tmp == set() tmp = sortedset(ss12) tmp -= set() - self.assertEqual(tmp, ss12) + assert tmp == ss12 tmp = sortedset(ss12) tmp -= ss1 - self.assertEqual(tmp, sortedset([2])) + assert tmp == sortedset([2]) # __rsub__ - self.assertEqual(set((1,2,3)) - ss12, set((3,))) + assert set((1,2,3)) - ss12 == set((3,)) # __xor__ - self.assertEqual(ss1 ^ ss12, set([2])) - self.assertEqual(ss12 ^ ss1, set([2])) - self.assertEqual(ss12 ^ ss12, set()) - self.assertEqual(ss12 ^ set(), ss12) + assert ss1 ^ ss12 == set([2]) + assert ss12 ^ ss1 == set([2]) + assert ss12 ^ ss12 == set() + assert ss12 ^ set() == ss12 # __ixor__ tmp = sortedset(ss1) tmp ^= ss12 - self.assertEqual(tmp, set([2])) + assert tmp == set([2]) tmp = sortedset(ss12) tmp ^= ss1 - self.assertEqual(tmp, set([2])) + assert tmp == set([2]) tmp = sortedset(ss12) tmp ^= ss12 - self.assertEqual(tmp, set()) + assert tmp == set() tmp = sortedset(ss12) tmp ^= set() - self.assertEqual(tmp, ss12) + assert tmp == ss12 # __rxor__ - self.assertEqual(set([1, 2]) ^ ss1, (set([2]))) + assert set([1, 2]) ^ ss1 == (set([2])) def test_reduce_pickle(self): ss = sortedset((4,3,2,1)) import pickle s = pickle.dumps(ss) - self.assertEqual(pickle.loads(s), ss) + assert pickle.loads(s) == ss def _test_uncomparable_types(self, items): for perm in permutations(items): ss = sortedset(perm) s = set(perm) - self.assertEqual(s, ss) - self.assertEqual(ss, ss.union(s)) + assert s == ss + assert ss == ss.union(s) for x in range(len(ss)): subset = set(s) for _ in range(x): subset.pop() - self.assertEqual(ss.difference(subset), s.difference(subset)) - self.assertEqual(ss.intersection(subset), s.intersection(subset)) + assert ss.difference(subset) == s.difference(subset) + assert ss.intersection(subset) == s.intersection(subset) for x in ss: self.assertIn(x, ss) ss.remove(x) diff --git a/tests/unit/test_tablets.py b/tests/unit/test_tablets.py index 3bbba06918..5e640fa4c9 100644 --- a/tests/unit/test_tablets.py +++ b/tests/unit/test_tablets.py @@ -4,11 +4,11 @@ class TabletsTest(unittest.TestCase): def compare_ranges(self, tablets, ranges): - self.assertEqual(len(tablets), len(ranges)) + assert len(tablets) == len(ranges) for idx, tablet in enumerate(tablets): - self.assertEqual(tablet.first_token, ranges[idx][0], "First token is not correct in tablet: {}".format(tablet)) - self.assertEqual(tablet.last_token, ranges[idx][1], "Last token is not correct in tablet: {}".format(tablet)) + assert tablet.first_token == ranges[idx][0], "First token is not correct in tablet: {}".format(tablet) + assert tablet.last_token == ranges[idx][1], "Last token is not correct in tablet: {}".format(tablet) def test_add_tablet_to_empty_tablets(self): tablets = Tablets({("test_ks", "test_tb"): []}) diff --git a/tests/unit/test_time_util.py b/tests/unit/test_time_util.py index 2605992d1c..9ecebe59f2 100644 --- a/tests/unit/test_time_util.py +++ b/tests/unit/test_time_util.py @@ -24,16 +24,16 @@ class TimeUtilTest(unittest.TestCase): def test_datetime_from_timestamp(self): - self.assertEqual(util.datetime_from_timestamp(0), datetime.datetime(1970, 1, 1)) + assert util.datetime_from_timestamp(0) == datetime.datetime(1970, 1, 1) # large negative; test PYTHON-110 workaround for windows - self.assertEqual(util.datetime_from_timestamp(-62135596800), datetime.datetime(1, 1, 1)) - self.assertEqual(util.datetime_from_timestamp(-62135596199), datetime.datetime(1, 1, 1, 0, 10, 1)) + assert util.datetime_from_timestamp(-62135596800) == datetime.datetime(1, 1, 1) + assert util.datetime_from_timestamp(-62135596199) == datetime.datetime(1, 1, 1, 0, 10, 1) - self.assertEqual(util.datetime_from_timestamp(253402300799), datetime.datetime(9999, 12, 31, 23, 59, 59)) + assert util.datetime_from_timestamp(253402300799) == datetime.datetime(9999, 12, 31, 23, 59, 59) - self.assertEqual(util.datetime_from_timestamp(0.123456), datetime.datetime(1970, 1, 1, 0, 0, 0, 123456)) + assert util.datetime_from_timestamp(0.123456) == datetime.datetime(1970, 1, 1, 0, 0, 0, 123456) - self.assertEqual(util.datetime_from_timestamp(2177403010.123456), datetime.datetime(2038, 12, 31, 10, 10, 10, 123456)) + assert util.datetime_from_timestamp(2177403010.123456) == datetime.datetime(2038, 12, 31, 10, 10, 10, 123456) def test_times_from_uuid1(self): node = uuid.getnode() @@ -55,15 +55,15 @@ def test_uuid_from_time(self): # using AlmostEqual because time precision is different for # some platforms self.assertAlmostEqual(util.unix_time_from_uuid1(u), t, 4) - self.assertEqual(u.node, node) - self.assertEqual(u.clock_seq, seq) + assert u.node == node + assert u.clock_seq == seq # random node u1 = util.uuid_from_time(t, clock_seq=seq) u2 = util.uuid_from_time(t, clock_seq=seq) self.assertAlmostEqual(util.unix_time_from_uuid1(u1), t, 4) self.assertAlmostEqual(util.unix_time_from_uuid1(u2), t, 4) - self.assertEqual(u.clock_seq, seq) + assert u.clock_seq == seq # not impossible, but we shouldn't get the same value twice self.assertNotEqual(u1.node, u2.node) @@ -72,7 +72,7 @@ def test_uuid_from_time(self): u2 = util.uuid_from_time(t, node=node) self.assertAlmostEqual(util.unix_time_from_uuid1(u1), t, 4) self.assertAlmostEqual(util.unix_time_from_uuid1(u2), t, 4) - self.assertEqual(u.node, node) + assert u.node == node # not impossible, but we shouldn't get the same value twice self.assertNotEqual(u1.clock_seq, u2.clock_seq) @@ -88,8 +88,8 @@ def test_uuid_from_time(self): dt = util.datetime_from_timestamp(t) u = util.uuid_from_time(dt, node, seq) self.assertAlmostEqual(util.unix_time_from_uuid1(u), t, 4) - self.assertEqual(u.node, node) - self.assertEqual(u.clock_seq, seq) + assert u.node == node + assert u.clock_seq == seq # 0 1 2 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -106,7 +106,7 @@ def test_min_uuid(self): u = util.min_uuid_from_time(0) # cassandra does a signed comparison of the remaining bytes for i in range(8, 16): - self.assertEqual(marshal.int8_unpack(u.bytes[i:i + 1]), -128) + assert marshal.int8_unpack(u.bytes[i:i + 1]) == -128 def test_max_uuid(self): u = util.max_uuid_from_time(0) @@ -114,6 +114,6 @@ def test_max_uuid(self): # the first non-time byte has the variant in it # This byte is always negative, but should be the smallest negative # number with high-order bits '10' - self.assertEqual(marshal.int8_unpack(u.bytes[8:9]), -65) + assert marshal.int8_unpack(u.bytes[8:9]) == -65 for i in range(9, 16): - self.assertEqual(marshal.int8_unpack(u.bytes[i:i + 1]), 127) + assert marshal.int8_unpack(u.bytes[i:i + 1]) == 127 diff --git a/tests/unit/test_timestamps.py b/tests/unit/test_timestamps.py index 151c004c90..3dfb23270e 100644 --- a/tests/unit/test_timestamps.py +++ b/tests/unit/test_timestamps.py @@ -42,7 +42,7 @@ def _call_and_check_results(self, for expected in expected_timestamps: actual = tsg() if expected is not None: - self.assertEqual(actual, expected) + assert actual == expected # assert we patched timestamps.time.time correctly with self.assertRaises(StopIteration): @@ -102,8 +102,8 @@ def setUp(self): def assertLastCallArgRegex(self, call, pattern): last_warn_args, last_warn_kwargs = call - self.assertEqual(len(last_warn_args), 1) - self.assertEqual(len(last_warn_kwargs), 0) + assert len(last_warn_args) == 1 + assert len(last_warn_kwargs) == 0 self.assertRegex(last_warn_args[0], pattern) def test_basic_log_content(self): @@ -124,10 +124,10 @@ def test_basic_log_content(self): tsg._last_warn = 12 tsg._next_timestamp(20, tsg.last) - self.assertEqual(len(self.patched_timestamp_log.warning.call_args_list), 0) + assert len(self.patched_timestamp_log.warning.call_args_list) == 0 tsg._next_timestamp(16, tsg.last) - self.assertEqual(len(self.patched_timestamp_log.warning.call_args_list), 1) + assert len(self.patched_timestamp_log.warning.call_args_list) == 1 self.assertLastCallArgRegex( self.patched_timestamp_log.warning.call_args, r'Clock skew detected:.*\b16\b.*\b4\b.*\b20\b' @@ -147,7 +147,7 @@ def test_disable_logging(self): no_warn_tsg.last = 100 no_warn_tsg._next_timestamp(99, no_warn_tsg.last) - self.assertEqual(len(self.patched_timestamp_log.warning.call_args_list), 0) + assert len(self.patched_timestamp_log.warning.call_args_list) == 0 def test_warning_threshold_respected_no_logging(self): """ @@ -164,7 +164,7 @@ def test_warning_threshold_respected_no_logging(self): ) tsg.last, tsg._last_warn = 100, 97 tsg._next_timestamp(98, tsg.last) - self.assertEqual(len(self.patched_timestamp_log.warning.call_args_list), 0) + assert len(self.patched_timestamp_log.warning.call_args_list) == 0 def test_warning_threshold_respected_logs(self): """ @@ -182,7 +182,7 @@ def test_warning_threshold_respected_logs(self): ) tsg.last, tsg._last_warn = 100, 97 tsg._next_timestamp(98, tsg.last) - self.assertEqual(len(self.patched_timestamp_log.warning.call_args_list), 1) + assert len(self.patched_timestamp_log.warning.call_args_list) == 1 def test_warning_interval_respected_no_logging(self): """ @@ -200,10 +200,10 @@ def test_warning_interval_respected_no_logging(self): ) tsg.last = 100 tsg._next_timestamp(70, tsg.last) - self.assertEqual(len(self.patched_timestamp_log.warning.call_args_list), 1) + assert len(self.patched_timestamp_log.warning.call_args_list) == 1 tsg._next_timestamp(71, tsg.last) - self.assertEqual(len(self.patched_timestamp_log.warning.call_args_list), 1) + assert len(self.patched_timestamp_log.warning.call_args_list) == 1 def test_warning_interval_respected_logs(self): """ @@ -222,10 +222,10 @@ def test_warning_interval_respected_logs(self): ) tsg.last = 100 tsg._next_timestamp(70, tsg.last) - self.assertEqual(len(self.patched_timestamp_log.warning.call_args_list), 1) + assert len(self.patched_timestamp_log.warning.call_args_list) == 1 tsg._next_timestamp(72, tsg.last) - self.assertEqual(len(self.patched_timestamp_log.warning.call_args_list), 2) + assert len(self.patched_timestamp_log.warning.call_args_list) == 2 class TestTimestampGeneratorMultipleThreads(unittest.TestCase): @@ -266,6 +266,6 @@ def request_time(): for t in threads: t.join() - self.assertEqual(len(generated_timestamps), num_threads * timestamp_to_generate) + assert len(generated_timestamps) == num_threads * timestamp_to_generate for i, timestamp in enumerate(sorted(generated_timestamps)): - self.assertEqual(int(i + 1e6), timestamp) + assert int(i + 1e6) == timestamp diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index aba11d4ced..44f81a24b0 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -54,83 +54,82 @@ def test_lookup_casstype_simple(self): Ensure lookup_casstype_simple returns the correct classes """ - self.assertEqual(lookup_casstype_simple('AsciiType'), cassandra.cqltypes.AsciiType) - self.assertEqual(lookup_casstype_simple('LongType'), cassandra.cqltypes.LongType) - self.assertEqual(lookup_casstype_simple('BytesType'), cassandra.cqltypes.BytesType) - self.assertEqual(lookup_casstype_simple('BooleanType'), cassandra.cqltypes.BooleanType) - self.assertEqual(lookup_casstype_simple('CounterColumnType'), cassandra.cqltypes.CounterColumnType) - self.assertEqual(lookup_casstype_simple('DecimalType'), cassandra.cqltypes.DecimalType) - self.assertEqual(lookup_casstype_simple('DoubleType'), cassandra.cqltypes.DoubleType) - self.assertEqual(lookup_casstype_simple('FloatType'), cassandra.cqltypes.FloatType) - self.assertEqual(lookup_casstype_simple('InetAddressType'), cassandra.cqltypes.InetAddressType) - self.assertEqual(lookup_casstype_simple('Int32Type'), cassandra.cqltypes.Int32Type) - self.assertEqual(lookup_casstype_simple('UTF8Type'), cassandra.cqltypes.UTF8Type) - self.assertEqual(lookup_casstype_simple('DateType'), cassandra.cqltypes.DateType) - self.assertEqual(lookup_casstype_simple('SimpleDateType'), cassandra.cqltypes.SimpleDateType) - self.assertEqual(lookup_casstype_simple('ByteType'), cassandra.cqltypes.ByteType) - self.assertEqual(lookup_casstype_simple('ShortType'), cassandra.cqltypes.ShortType) - self.assertEqual(lookup_casstype_simple('TimeUUIDType'), cassandra.cqltypes.TimeUUIDType) - self.assertEqual(lookup_casstype_simple('TimeType'), cassandra.cqltypes.TimeType) - self.assertEqual(lookup_casstype_simple('UUIDType'), cassandra.cqltypes.UUIDType) - self.assertEqual(lookup_casstype_simple('IntegerType'), cassandra.cqltypes.IntegerType) - self.assertEqual(lookup_casstype_simple('MapType'), cassandra.cqltypes.MapType) - self.assertEqual(lookup_casstype_simple('ListType'), cassandra.cqltypes.ListType) - self.assertEqual(lookup_casstype_simple('SetType'), cassandra.cqltypes.SetType) - self.assertEqual(lookup_casstype_simple('CompositeType'), cassandra.cqltypes.CompositeType) - self.assertEqual(lookup_casstype_simple('ColumnToCollectionType'), cassandra.cqltypes.ColumnToCollectionType) - self.assertEqual(lookup_casstype_simple('ReversedType'), cassandra.cqltypes.ReversedType) - self.assertEqual(lookup_casstype_simple('DurationType'), cassandra.cqltypes.DurationType) - self.assertEqual(lookup_casstype_simple('DateRangeType'), cassandra.cqltypes.DateRangeType) - - self.assertEqual(str(lookup_casstype_simple('unknown')), str(cassandra.cqltypes.mkUnrecognizedType('unknown'))) + assert lookup_casstype_simple('AsciiType') == cassandra.cqltypes.AsciiType + assert lookup_casstype_simple('LongType') == cassandra.cqltypes.LongType + assert lookup_casstype_simple('BytesType') == cassandra.cqltypes.BytesType + assert lookup_casstype_simple('BooleanType') == cassandra.cqltypes.BooleanType + assert lookup_casstype_simple('CounterColumnType') == cassandra.cqltypes.CounterColumnType + assert lookup_casstype_simple('DecimalType') == cassandra.cqltypes.DecimalType + assert lookup_casstype_simple('DoubleType') == cassandra.cqltypes.DoubleType + assert lookup_casstype_simple('FloatType') == cassandra.cqltypes.FloatType + assert lookup_casstype_simple('InetAddressType') == cassandra.cqltypes.InetAddressType + assert lookup_casstype_simple('Int32Type') == cassandra.cqltypes.Int32Type + assert lookup_casstype_simple('UTF8Type') == cassandra.cqltypes.UTF8Type + assert lookup_casstype_simple('DateType') == cassandra.cqltypes.DateType + assert lookup_casstype_simple('SimpleDateType') == cassandra.cqltypes.SimpleDateType + assert lookup_casstype_simple('ByteType') == cassandra.cqltypes.ByteType + assert lookup_casstype_simple('ShortType') == cassandra.cqltypes.ShortType + assert lookup_casstype_simple('TimeUUIDType') == cassandra.cqltypes.TimeUUIDType + assert lookup_casstype_simple('TimeType') == cassandra.cqltypes.TimeType + assert lookup_casstype_simple('UUIDType') == cassandra.cqltypes.UUIDType + assert lookup_casstype_simple('IntegerType') == cassandra.cqltypes.IntegerType + assert lookup_casstype_simple('MapType') == cassandra.cqltypes.MapType + assert lookup_casstype_simple('ListType') == cassandra.cqltypes.ListType + assert lookup_casstype_simple('SetType') == cassandra.cqltypes.SetType + assert lookup_casstype_simple('CompositeType') == cassandra.cqltypes.CompositeType + assert lookup_casstype_simple('ColumnToCollectionType') == cassandra.cqltypes.ColumnToCollectionType + assert lookup_casstype_simple('ReversedType') == cassandra.cqltypes.ReversedType + assert lookup_casstype_simple('DurationType') == cassandra.cqltypes.DurationType + assert lookup_casstype_simple('DateRangeType') == cassandra.cqltypes.DateRangeType + + assert str(lookup_casstype_simple('unknown')) == str(cassandra.cqltypes.mkUnrecognizedType('unknown')) def test_lookup_casstype(self): """ Ensure lookup_casstype returns the correct classes """ - self.assertEqual(lookup_casstype('AsciiType'), cassandra.cqltypes.AsciiType) - self.assertEqual(lookup_casstype('LongType'), cassandra.cqltypes.LongType) - self.assertEqual(lookup_casstype('BytesType'), cassandra.cqltypes.BytesType) - self.assertEqual(lookup_casstype('BooleanType'), cassandra.cqltypes.BooleanType) - self.assertEqual(lookup_casstype('CounterColumnType'), cassandra.cqltypes.CounterColumnType) - self.assertEqual(lookup_casstype('DateType'), cassandra.cqltypes.DateType) - self.assertEqual(lookup_casstype('DecimalType'), cassandra.cqltypes.DecimalType) - self.assertEqual(lookup_casstype('DoubleType'), cassandra.cqltypes.DoubleType) - self.assertEqual(lookup_casstype('FloatType'), cassandra.cqltypes.FloatType) - self.assertEqual(lookup_casstype('InetAddressType'), cassandra.cqltypes.InetAddressType) - self.assertEqual(lookup_casstype('Int32Type'), cassandra.cqltypes.Int32Type) - self.assertEqual(lookup_casstype('UTF8Type'), cassandra.cqltypes.UTF8Type) - self.assertEqual(lookup_casstype('DateType'), cassandra.cqltypes.DateType) - self.assertEqual(lookup_casstype('TimeType'), cassandra.cqltypes.TimeType) - self.assertEqual(lookup_casstype('ByteType'), cassandra.cqltypes.ByteType) - self.assertEqual(lookup_casstype('ShortType'), cassandra.cqltypes.ShortType) - self.assertEqual(lookup_casstype('TimeUUIDType'), cassandra.cqltypes.TimeUUIDType) - self.assertEqual(lookup_casstype('UUIDType'), cassandra.cqltypes.UUIDType) - self.assertEqual(lookup_casstype('IntegerType'), cassandra.cqltypes.IntegerType) - self.assertEqual(lookup_casstype('MapType'), cassandra.cqltypes.MapType) - self.assertEqual(lookup_casstype('ListType'), cassandra.cqltypes.ListType) - self.assertEqual(lookup_casstype('SetType'), cassandra.cqltypes.SetType) - self.assertEqual(lookup_casstype('CompositeType'), cassandra.cqltypes.CompositeType) - self.assertEqual(lookup_casstype('ColumnToCollectionType'), cassandra.cqltypes.ColumnToCollectionType) - self.assertEqual(lookup_casstype('ReversedType'), cassandra.cqltypes.ReversedType) - self.assertEqual(lookup_casstype('DurationType'), cassandra.cqltypes.DurationType) - self.assertEqual(lookup_casstype('DateRangeType'), cassandra.cqltypes.DateRangeType) - - self.assertEqual(str(lookup_casstype('unknown')), str(cassandra.cqltypes.mkUnrecognizedType('unknown'))) + assert lookup_casstype('AsciiType') == cassandra.cqltypes.AsciiType + assert lookup_casstype('LongType') == cassandra.cqltypes.LongType + assert lookup_casstype('BytesType') == cassandra.cqltypes.BytesType + assert lookup_casstype('BooleanType') == cassandra.cqltypes.BooleanType + assert lookup_casstype('CounterColumnType') == cassandra.cqltypes.CounterColumnType + assert lookup_casstype('DateType') == cassandra.cqltypes.DateType + assert lookup_casstype('DecimalType') == cassandra.cqltypes.DecimalType + assert lookup_casstype('DoubleType') == cassandra.cqltypes.DoubleType + assert lookup_casstype('FloatType') == cassandra.cqltypes.FloatType + assert lookup_casstype('InetAddressType') == cassandra.cqltypes.InetAddressType + assert lookup_casstype('Int32Type') == cassandra.cqltypes.Int32Type + assert lookup_casstype('UTF8Type') == cassandra.cqltypes.UTF8Type + assert lookup_casstype('DateType') == cassandra.cqltypes.DateType + assert lookup_casstype('TimeType') == cassandra.cqltypes.TimeType + assert lookup_casstype('ByteType') == cassandra.cqltypes.ByteType + assert lookup_casstype('ShortType') == cassandra.cqltypes.ShortType + assert lookup_casstype('TimeUUIDType') == cassandra.cqltypes.TimeUUIDType + assert lookup_casstype('UUIDType') == cassandra.cqltypes.UUIDType + assert lookup_casstype('IntegerType') == cassandra.cqltypes.IntegerType + assert lookup_casstype('MapType') == cassandra.cqltypes.MapType + assert lookup_casstype('ListType') == cassandra.cqltypes.ListType + assert lookup_casstype('SetType') == cassandra.cqltypes.SetType + assert lookup_casstype('CompositeType') == cassandra.cqltypes.CompositeType + assert lookup_casstype('ColumnToCollectionType') == cassandra.cqltypes.ColumnToCollectionType + assert lookup_casstype('ReversedType') == cassandra.cqltypes.ReversedType + assert lookup_casstype('DurationType') == cassandra.cqltypes.DurationType + assert lookup_casstype('DateRangeType') == cassandra.cqltypes.DateRangeType + + assert str(lookup_casstype('unknown')) == str(cassandra.cqltypes.mkUnrecognizedType('unknown')) self.assertRaises(ValueError, lookup_casstype, 'AsciiType~') def test_casstype_parameterized(self): - self.assertEqual(LongType.cass_parameterized_type_with(()), 'LongType') - self.assertEqual(LongType.cass_parameterized_type_with((), full=True), 'org.apache.cassandra.db.marshal.LongType') - self.assertEqual(SetType.cass_parameterized_type_with([DecimalType], full=True), 'org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.DecimalType)') + assert LongType.cass_parameterized_type_with(()) == 'LongType' + assert LongType.cass_parameterized_type_with((), full=True) == 'org.apache.cassandra.db.marshal.LongType' + assert SetType.cass_parameterized_type_with([DecimalType], full=True) == 'org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.DecimalType)' - self.assertEqual(LongType.cql_parameterized_type(), 'bigint') + assert LongType.cql_parameterized_type() == 'bigint' subtypes = (cassandra.cqltypes.UTF8Type, cassandra.cqltypes.UTF8Type) - self.assertEqual('map', - cassandra.cqltypes.MapType.apply_parameters(subtypes).cql_parameterized_type()) + assert 'map' == cassandra.cqltypes.MapType.apply_parameters(subtypes).cql_parameterized_type() def test_datetype_from_string(self): # Ensure all formats can be parsed, without exception @@ -143,18 +142,18 @@ def test_cql_typename(self): Smoke test cql_typename """ - self.assertEqual(cql_typename('DateType'), 'timestamp') - self.assertEqual(cql_typename('org.apache.cassandra.db.marshal.ListType(IntegerType)'), 'list') + assert cql_typename('DateType') == 'timestamp' + assert cql_typename('org.apache.cassandra.db.marshal.ListType(IntegerType)') == 'list' def test_named_tuple_colname_substitution(self): colnames = ("func(abc)", "[applied]", "func(func(abc))", "foo_bar", "foo_bar_") rows = [(1, 2, 3, 4, 5)] result = named_tuple_factory(colnames, rows)[0] - self.assertEqual(result[0], result.func_abc) - self.assertEqual(result[1], result.applied) - self.assertEqual(result[2], result.func_func_abc) - self.assertEqual(result[3], result.foo_bar) - self.assertEqual(result[4], result.foo_bar_) + assert result[0] == result.func_abc + assert result[1] == result.applied + assert result[2] == result.func_func_abc + assert result[3] == result.foo_bar + assert result[4] == result.foo_bar_ def test_parse_casstype_args(self): class FooType(CassandraType): @@ -178,36 +177,36 @@ class BarType(FooType): '7a6970:org.apache.cassandra.db.marshal.UTF8Type', ')'))) - self.assertEqual(FooType, ctype.__class__) + assert FooType == ctype.__class__ - self.assertEqual(UTF8Type, ctype.subtypes[0]) + assert UTF8Type == ctype.subtypes[0] # middle subtype should be a BarType instance with its own subtypes and names self.assertIsInstance(ctype.subtypes[1], BarType) - self.assertEqual([UTF8Type], ctype.subtypes[1].subtypes) - self.assertEqual([b"address"], ctype.subtypes[1].names) + assert [UTF8Type] == ctype.subtypes[1].subtypes + assert [b"address"] == ctype.subtypes[1].names - self.assertEqual(UTF8Type, ctype.subtypes[2]) - self.assertEqual([b'city', None, b'zip'], ctype.names) + assert UTF8Type == ctype.subtypes[2] + assert [b'city', None, b'zip'] == ctype.names def test_parse_casstype_vector(self): ctype = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType, 3)") self.assertTrue(issubclass(ctype, VectorType)) - self.assertEqual(3, ctype.vector_size) - self.assertEqual(FloatType, ctype.subtype) + assert 3 == ctype.vector_size + assert FloatType == ctype.subtype def test_parse_casstype_vector_of_vectors(self): inner_type = "org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType, 4)" ctype = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(%s, 3)" % (inner_type)) self.assertTrue(issubclass(ctype, VectorType)) - self.assertEqual(3, ctype.vector_size) + assert 3 == ctype.vector_size sub_ctype = ctype.subtype self.assertTrue(issubclass(sub_ctype, VectorType)) - self.assertEqual(4, sub_ctype.vector_size) - self.assertEqual(FloatType, sub_ctype.subtype) + assert 4 == sub_ctype.vector_size + assert FloatType == sub_ctype.subtype def test_empty_value(self): - self.assertEqual(str(EmptyValue()), 'EMPTY') + assert str(EmptyValue()) == 'EMPTY' def test_datetype(self): now_time_seconds = time.time() @@ -217,28 +216,28 @@ def test_datetype(self): now_timestamp = now_time_seconds * 1e3 # same results serialized - self.assertEqual(DateType.serialize(now_datetime, 0), DateType.serialize(now_timestamp, 0)) + assert DateType.serialize(now_datetime, 0) == DateType.serialize(now_timestamp, 0) # deserialize # epoc expected = 0 - self.assertEqual(DateType.deserialize(int64_pack(1000 * expected), 0), datetime.datetime.fromtimestamp(expected, tz=datetime.timezone.utc).replace(tzinfo=None)) + assert DateType.deserialize(int64_pack(1000 * expected), 0) == datetime.datetime.fromtimestamp(expected, tz=datetime.timezone.utc).replace(tzinfo=None) # beyond 32b expected = 2 ** 33 - self.assertEqual(DateType.deserialize(int64_pack(1000 * expected), 0), datetime.datetime(2242, 3, 16, 12, 56, 32, tzinfo=datetime.timezone.utc).replace(tzinfo=None)) + assert DateType.deserialize(int64_pack(1000 * expected), 0) == datetime.datetime(2242, 3, 16, 12, 56, 32, tzinfo=datetime.timezone.utc).replace(tzinfo=None) # less than epoc (PYTHON-119) expected = -770172256 - self.assertEqual(DateType.deserialize(int64_pack(1000 * expected), 0), datetime.datetime(1945, 8, 5, 23, 15, 44, tzinfo=datetime.timezone.utc).replace(tzinfo=None)) + assert DateType.deserialize(int64_pack(1000 * expected), 0) == datetime.datetime(1945, 8, 5, 23, 15, 44, tzinfo=datetime.timezone.utc).replace(tzinfo=None) # work around rounding difference among Python versions (PYTHON-230) expected = 1424817268.274 - self.assertEqual(DateType.deserialize(int64_pack(int(1000 * expected)), 0), datetime.datetime(2015, 2, 24, 22, 34, 28, 274000, tzinfo=datetime.timezone.utc).replace(tzinfo=None)) + assert DateType.deserialize(int64_pack(int(1000 * expected)), 0) == datetime.datetime(2015, 2, 24, 22, 34, 28, 274000, tzinfo=datetime.timezone.utc).replace(tzinfo=None) # Large date overflow (PYTHON-452) expected = 2177403010.123 - self.assertEqual(DateType.deserialize(int64_pack(int(1000 * expected)), 0), datetime.datetime(2038, 12, 31, 10, 10, 10, 123000, tzinfo=datetime.timezone.utc).replace(tzinfo=None)) + assert DateType.deserialize(int64_pack(int(1000 * expected)), 0) == datetime.datetime(2038, 12, 31, 10, 10, 10, 123000, tzinfo=datetime.timezone.utc).replace(tzinfo=None) def test_collection_null_support(self): """ @@ -253,16 +252,10 @@ def test_collection_null_support(self): int32_pack(4) + # size of item2 int32_pack(42) # item2 ) - self.assertEqual( - [None, 42], - int_list.deserialize(value, 3) - ) + assert [None, 42] == int_list.deserialize(value, 3) set_list = SetType.apply_parameters([Int32Type]) - self.assertEqual( - {None, 42}, - set(set_list.deserialize(value, 3)) - ) + assert {None, 42} == set(set_list.deserialize(value, 3)) value = ( int32_pack(2) + # num items @@ -275,49 +268,47 @@ def test_collection_null_support(self): ) map_list = MapType.apply_parameters([Int32Type, Int32Type]) - self.assertEqual( - [(42, None), (None, 42)], - map_list.deserialize(value, 3)._items # OrderedMapSerializedKey - ) + + assert [(42, None), (None, 42)] == map_list.deserialize(value, 3)._items # OrderedMapSerializedKey def test_write_read_string(self): with tempfile.TemporaryFile() as f: value = u'test' write_string(f, value) f.seek(0) - self.assertEqual(read_string(f), value) + assert read_string(f) == value def test_write_read_longstring(self): with tempfile.TemporaryFile() as f: value = u'test' write_longstring(f, value) f.seek(0) - self.assertEqual(read_longstring(f), value) + assert read_longstring(f) == value def test_write_read_stringmap(self): with tempfile.TemporaryFile() as f: value = {'key': 'value'} write_stringmap(f, value) f.seek(0) - self.assertEqual(read_stringmap(f), value) + assert read_stringmap(f) == value def test_write_read_inet(self): with tempfile.TemporaryFile() as f: value = ('192.168.1.1', 9042) write_inet(f, value) f.seek(0) - self.assertEqual(read_inet(f), value) + assert read_inet(f) == value with tempfile.TemporaryFile() as f: value = ('2001:db8:0:f101::1', 9042) write_inet(f, value) f.seek(0) - self.assertEqual(read_inet(f), value) + assert read_inet(f) == value def test_cql_quote(self): - self.assertEqual(cql_quote(u'test'), "'test'") - self.assertEqual(cql_quote('test'), "'test'") - self.assertEqual(cql_quote(0), '0') + assert cql_quote(u'test') == "'test'" + assert cql_quote('test') == "'test'" + assert cql_quote(0) == '0' class VectorTests(unittest.TestCase): @@ -330,29 +321,29 @@ def _round_trip_compare_fn(self, first, second): if isinstance(first, float): self.assertAlmostEqual(first, second, places=5) elif isinstance(first, list): - self.assertEqual(len(first), len(second)) + assert len(first) == len(second) for (felem, selem) in zip(first, second): self._round_trip_compare_fn(felem, selem) elif isinstance(first, set) or isinstance(first, frozenset): - self.assertEqual(len(first), len(second)) + assert len(first) == len(second) first_norm = self._normalize_set(first) second_norm = self._normalize_set(second) - self.assertEqual(first_norm, second_norm) + assert first_norm == second_norm elif isinstance(first, dict): for ((fk,fv), (sk,sv)) in zip(first.items(), second.items()): self._round_trip_compare_fn(fk, sk) self._round_trip_compare_fn(fv, sv) else: - self.assertEqual(first,second) + assert first == second def _round_trip_test(self, data, ctype_str): ctype = parse_casstype_args(ctype_str) data_bytes = ctype.serialize(data, 0) serialized_size = ctype.subtype.serial_size() if serialized_size: - self.assertEqual(serialized_size * len(data), len(data_bytes)) + assert serialized_size * len(data) == len(data_bytes) result = ctype.deserialize(data_bytes, 0) - self.assertEqual(len(data), len(result)) + assert len(data) == len(result) for idx in range(0,len(data)): self._round_trip_compare_fn(data[idx], result[idx]) @@ -460,13 +451,13 @@ def test_round_trip_vector_of_vectors(self): def test_cql_parameterized_type(self): # Base vector functionality ctype = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType, 4)") - self.assertEqual(ctype.cql_parameterized_type(), "org.apache.cassandra.db.marshal.VectorType") + assert ctype.cql_parameterized_type() == "org.apache.cassandra.db.marshal.VectorType" # Test vector-of-vectors inner_type = "org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType, 4)" ctype = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(%s, 3)" % (inner_type)) inner_parsed_type = "org.apache.cassandra.db.marshal.VectorType" - self.assertEqual(ctype.cql_parameterized_type(), "org.apache.cassandra.db.marshal.VectorType<%s, 3>" % (inner_parsed_type)) + assert ctype.cql_parameterized_type() == "org.apache.cassandra.db.marshal.VectorType<%s, 3>" % (inner_parsed_type) def test_serialization_fixed_size_too_small(self): ctype = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType, 5)") @@ -553,7 +544,7 @@ def test_month_rounding_creation_failure(self): dr = DateRange(OPEN_BOUND, DateRangeBound(feb_stamp, DateRangePrecision.MONTH)) dt = datetime_from_timestamp(dr.upper_bound.milliseconds / 1000) - self.assertEqual(dt.day, 28) + assert dt.day == 28 # Leap year feb_stamp_leap_year = ms_timestamp_from_datetime( @@ -562,17 +553,17 @@ def test_month_rounding_creation_failure(self): dr = DateRange(OPEN_BOUND, DateRangeBound(feb_stamp_leap_year, DateRangePrecision.MONTH)) dt = datetime_from_timestamp(dr.upper_bound.milliseconds / 1000) - self.assertEqual(dt.day, 29) + assert dt.day == 29 def test_decode_precision(self): - self.assertEqual(DateRangeType._decode_precision(6), 'MILLISECOND') + assert DateRangeType._decode_precision(6) == 'MILLISECOND' def test_decode_precision_error(self): with self.assertRaises(ValueError): DateRangeType._decode_precision(-1) def test_encode_precision(self): - self.assertEqual(DateRangeType._encode_precision('SECOND'), 5) + assert DateRangeType._encode_precision('SECOND') == 5 def test_encode_precision_error(self): with self.assertRaises(ValueError): @@ -582,12 +573,9 @@ def test_deserialize_single_value(self): serialized = (int8_pack(0) + int64_pack(self.timestamp) + int8_pack(3)) - self.assertEqual( - DateRangeType.deserialize(serialized, 5), - util.DateRange(value=util.DateRangeBound( - value=datetime.datetime(2017, 2, 1, 15, 42, 12, 404000), - precision='HOUR') - ) + assert DateRangeType.deserialize(serialized, 5) == util.DateRange(value=util.DateRangeBound( + value=datetime.datetime(2017, 2, 1, 15, 42, 12, 404000), + precision='HOUR') ) def test_deserialize_closed_range(self): @@ -596,17 +584,14 @@ def test_deserialize_closed_range(self): int8_pack(2) + int64_pack(self.timestamp) + int8_pack(6)) - self.assertEqual( - DateRangeType.deserialize(serialized, 5), - util.DateRange( - lower_bound=util.DateRangeBound( - value=datetime.datetime(2017, 2, 1, 0, 0), - precision='DAY' - ), - upper_bound=util.DateRangeBound( - value=datetime.datetime(2017, 2, 1, 15, 42, 12, 404000), - precision='MILLISECOND' - ) + assert DateRangeType.deserialize(serialized, 5) == util.DateRange( + lower_bound=util.DateRangeBound( + value=datetime.datetime(2017, 2, 1, 0, 0), + precision='DAY' + ), + upper_bound=util.DateRangeBound( + value=datetime.datetime(2017, 2, 1, 15, 42, 12, 404000), + precision='MILLISECOND' ) ) @@ -615,15 +600,12 @@ def test_deserialize_open_high(self): int64_pack(self.timestamp) + int8_pack(3)) deserialized = DateRangeType.deserialize(serialized, 5) - self.assertEqual( - deserialized, - util.DateRange( - lower_bound=util.DateRangeBound( - value=datetime.datetime(2017, 2, 1, 15, 0), - precision='HOUR' - ), - upper_bound=util.OPEN_BOUND - ) + assert deserialized == util.DateRange( + lower_bound=util.DateRangeBound( + value=datetime.datetime(2017, 2, 1, 15, 0), + precision='HOUR' + ), + upper_bound=util.OPEN_BOUND ) def test_deserialize_open_low(self): @@ -631,35 +613,26 @@ def test_deserialize_open_low(self): int64_pack(self.timestamp) + int8_pack(4)) deserialized = DateRangeType.deserialize(serialized, 5) - self.assertEqual( - deserialized, - util.DateRange( - lower_bound=util.OPEN_BOUND, - upper_bound=util.DateRangeBound( - value=datetime.datetime(2017, 2, 1, 15, 42, 20, 1000), - precision='MINUTE' - ) + assert deserialized == util.DateRange( + lower_bound=util.OPEN_BOUND, + upper_bound=util.DateRangeBound( + value=datetime.datetime(2017, 2, 1, 15, 42, 20, 1000), + precision='MINUTE' ) ) def test_deserialize_single_open(self): - self.assertEqual( - util.DateRange(value=util.OPEN_BOUND), - DateRangeType.deserialize(int8_pack(5), 5) - ) + assert util.DateRange(value=util.OPEN_BOUND) == DateRangeType.deserialize(int8_pack(5), 5) def test_serialize_single_value(self): serialized = (int8_pack(0) + int64_pack(self.timestamp) + int8_pack(5)) deserialized = DateRangeType.deserialize(serialized, 5) - self.assertEqual( - deserialized, - util.DateRange( - value=util.DateRangeBound( - value=datetime.datetime(2017, 2, 1, 15, 42, 12), - precision='SECOND' - ) + assert deserialized == util.DateRange( + value=util.DateRangeBound( + value=datetime.datetime(2017, 2, 1, 15, 42, 12), + precision='SECOND' ) ) @@ -670,17 +643,14 @@ def test_serialize_closed_range(self): int64_pack(self.timestamp) + int8_pack(0)) deserialized = DateRangeType.deserialize(serialized, 5) - self.assertEqual( - deserialized, - util.DateRange( - lower_bound=util.DateRangeBound( - value=datetime.datetime(2017, 2, 1, 15, 42, 12), - precision='SECOND' - ), - upper_bound=util.DateRangeBound( - value=datetime.datetime(2017, 12, 31), - precision='YEAR' - ) + assert deserialized == util.DateRange( + lower_bound=util.DateRangeBound( + value=datetime.datetime(2017, 2, 1, 15, 42, 12), + precision='SECOND' + ), + upper_bound=util.DateRangeBound( + value=datetime.datetime(2017, 12, 31), + precision='YEAR' ) ) @@ -689,15 +659,12 @@ def test_serialize_open_high(self): int64_pack(self.timestamp) + int8_pack(2)) deserialized = DateRangeType.deserialize(serialized, 5) - self.assertEqual( - deserialized, - util.DateRange( - lower_bound=util.DateRangeBound( - value=datetime.datetime(2017, 2, 1), - precision='DAY' - ), - upper_bound=util.OPEN_BOUND - ) + assert deserialized == util.DateRange( + lower_bound=util.DateRangeBound( + value=datetime.datetime(2017, 2, 1), + precision='DAY' + ), + upper_bound=util.OPEN_BOUND ) def test_serialize_open_low(self): @@ -705,40 +672,34 @@ def test_serialize_open_low(self): int64_pack(self.timestamp) + int8_pack(3)) deserialized = DateRangeType.deserialize(serialized, 5) - self.assertEqual( - deserialized, - util.DateRange( - lower_bound=util.DateRangeBound( - value=datetime.datetime(2017, 2, 1, 15), - precision='HOUR' - ), - upper_bound=util.OPEN_BOUND - ) + assert deserialized == util.DateRange( + lower_bound=util.DateRangeBound( + value=datetime.datetime(2017, 2, 1, 15), + precision='HOUR' + ), + upper_bound=util.OPEN_BOUND ) def test_deserialize_both_open(self): serialized = (int8_pack(4)) deserialized = DateRangeType.deserialize(serialized, 5) - self.assertEqual( - deserialized, - util.DateRange( - lower_bound=util.OPEN_BOUND, - upper_bound=util.OPEN_BOUND - ) + assert deserialized == util.DateRange( + lower_bound=util.OPEN_BOUND, + upper_bound=util.OPEN_BOUND ) def test_serialize_single_open(self): serialized = DateRangeType.serialize(util.DateRange( value=util.OPEN_BOUND, ), 5) - self.assertEqual(int8_pack(5), serialized) + assert int8_pack(5) == serialized def test_serialize_both_open(self): serialized = DateRangeType.serialize(util.DateRange( lower_bound=util.OPEN_BOUND, upper_bound=util.OPEN_BOUND ), 5) - self.assertEqual(int8_pack(4), serialized) + assert int8_pack(4) == serialized def test_failure_to_serialize_no_value_object(self): self.assertRaises(ValueError, DateRangeType.serialize, object(), 5) @@ -752,10 +713,7 @@ def test_serialized_value_round_trip(self): vals = [b'\x01\x00\x00\x01%\xe9a\xf9\xd1\x06\x00\x00\x01v\xbb>o\xff\x00', b'\x01\x00\x00\x00\xdcm\x03-\xd1\x06\x00\x00\x01v\xbb>o\xff\x00'] for serialized in vals: - self.assertEqual( - serialized, - DateRangeType.serialize(DateRangeType.deserialize(serialized, 0), 0) - ) + assert serialized == DateRangeType.serialize(DateRangeType.deserialize(serialized, 0), 0) def test_serialize_zero_datetime(self): """ @@ -826,8 +784,8 @@ def test_deserialize_date_range_milliseconds(self): upper_value = self.starting_upper_value + i dr = DateRange(DateRangeBound(lower_value, DateRangePrecision.MILLISECOND), DateRangeBound(upper_value, DateRangePrecision.MILLISECOND)) - self.assertEqual(lower_value, dr.lower_bound.milliseconds) - self.assertEqual(upper_value, dr.upper_bound.milliseconds) + assert lower_value == dr.lower_bound.milliseconds + assert upper_value == dr.upper_bound.milliseconds def test_deserialize_date_range_seconds(self): """ @@ -852,9 +810,9 @@ def truncate_last_figures(number, n=3): dr = DateRange(DateRangeBound(lower_value, DateRangePrecision.SECOND), DateRangeBound(upper_value, DateRangePrecision.SECOND)) - self.assertEqual(truncate_last_figures(lower_value), dr.lower_bound.milliseconds) + assert truncate_last_figures(lower_value) == dr.lower_bound.milliseconds upper_value = truncate_last_figures(upper_value) + 999 - self.assertEqual(upper_value, dr.upper_bound.milliseconds) + assert upper_value == dr.upper_bound.milliseconds def test_deserialize_date_range_minutes(self): """ @@ -1021,9 +979,9 @@ def truncate_date(number): DateRangeBound(upper_value, precision)) # We verify that rounded value corresponds with what we would expect - self.assertEqual(truncate_date(lower_value), dr.lower_bound.milliseconds) + assert truncate_date(lower_value) == dr.lower_bound.milliseconds upper_value = round_up_truncated_upper_value(truncate_date(upper_value)) - self.assertEqual(upper_value, dr.upper_bound.milliseconds) + assert upper_value == dr.upper_bound.milliseconds class TestOrdering(unittest.TestCase): diff --git a/tests/unit/test_util_types.py b/tests/unit/test_util_types.py index a2551ba20b..7643654edd 100644 --- a/tests/unit/test_util_types.py +++ b/tests/unit/test_util_types.py @@ -23,39 +23,37 @@ class DateTests(unittest.TestCase): def test_from_datetime(self): expected_date = datetime.date(1492, 10, 12) d = Date(expected_date) - self.assertEqual(str(d), str(expected_date)) + assert str(d) == str(expected_date) def test_from_string(self): expected_date = datetime.date(1492, 10, 12) d = Date(expected_date) sd = Date('1492-10-12') - self.assertEqual(sd, d) + assert sd == d sd = Date('+1492-10-12') - self.assertEqual(sd, d) + assert sd == d def test_from_date(self): expected_date = datetime.date(1492, 10, 12) d = Date(expected_date) - self.assertEqual(d.date(), expected_date) + assert d.date() == expected_date def test_from_days(self): sd = Date(0) - self.assertEqual(sd, Date(datetime.date(1970, 1, 1))) + assert sd == Date(datetime.date(1970, 1, 1)) sd = Date(-1) - self.assertEqual(sd, Date(datetime.date(1969, 12, 31))) + assert sd == Date(datetime.date(1969, 12, 31)) sd = Date(1) - self.assertEqual(sd, Date(datetime.date(1970, 1, 2))) + assert sd == Date(datetime.date(1970, 1, 2)) def test_limits(self): min_builtin = Date(datetime.date(1, 1, 1)) max_builtin = Date(datetime.date(9999, 12, 31)) - self.assertEqual(Date(min_builtin.days_from_epoch), min_builtin) - self.assertEqual(Date(max_builtin.days_from_epoch), max_builtin) + assert Date(min_builtin.days_from_epoch) == min_builtin + assert Date(max_builtin.days_from_epoch) == max_builtin # just proving we can construct with on offset outside buildin range - self.assertEqual(Date(min_builtin.days_from_epoch - 1).days_from_epoch, - min_builtin.days_from_epoch - 1) - self.assertEqual(Date(max_builtin.days_from_epoch + 1).days_from_epoch, - max_builtin.days_from_epoch + 1) + assert Date(min_builtin.days_from_epoch - 1).days_from_epoch == min_builtin.days_from_epoch - 1 + assert Date(max_builtin.days_from_epoch + 1).days_from_epoch == max_builtin.days_from_epoch + 1 def test_invalid_init(self): self.assertRaises(ValueError, Date, '-1999-10-10') @@ -63,17 +61,17 @@ def test_invalid_init(self): def test_str(self): date_str = '2015-03-16' - self.assertEqual(str(Date(date_str)), date_str) + assert str(Date(date_str)) == date_str def test_out_of_range(self): - self.assertEqual(str(Date(2932897)), '2932897') - self.assertEqual(repr(Date(1)), 'Date(1)') + assert str(Date(2932897)) == '2932897' + assert repr(Date(1)) == 'Date(1)' def test_equals(self): - self.assertEqual(Date(1234), 1234) - self.assertEqual(Date(1), datetime.date(1970, 1, 2)) + assert Date(1234) == 1234 + assert Date(1) == datetime.date(1970, 1, 2) self.assertFalse(Date(2932897) == datetime.date(9999, 12, 31)) # date can't represent year > 9999 - self.assertEqual(Date(2932897), 2932897) + assert Date(2932897) == 2932897 class TimeTests(unittest.TestCase): @@ -86,31 +84,31 @@ def test_units_from_string(self): one_hour = 60 * one_minute tt = Time('00:00:00.000000001') - self.assertEqual(tt.nanosecond_time, 1) + assert tt.nanosecond_time == 1 tt = Time('00:00:00.000001') - self.assertEqual(tt.nanosecond_time, one_micro) + assert tt.nanosecond_time == one_micro tt = Time('00:00:00.001') - self.assertEqual(tt.nanosecond_time, one_milli) + assert tt.nanosecond_time == one_milli tt = Time('00:00:01') - self.assertEqual(tt.nanosecond_time, one_second) + assert tt.nanosecond_time == one_second tt = Time('00:01:00') - self.assertEqual(tt.nanosecond_time, one_minute) + assert tt.nanosecond_time == one_minute tt = Time('01:00:00') - self.assertEqual(tt.nanosecond_time, one_hour) + assert tt.nanosecond_time == one_hour tt = Time('01:00:00.') - self.assertEqual(tt.nanosecond_time, one_hour) + assert tt.nanosecond_time == one_hour tt = Time('23:59:59.123456') - self.assertEqual(tt.nanosecond_time, 23 * one_hour + 59 * one_minute + 59 * one_second + 123 * one_milli + 456 * one_micro) + assert tt.nanosecond_time == 23 * one_hour + 59 * one_minute + 59 * one_second + 123 * one_milli + 456 * one_micro tt = Time('23:59:59.1234567') - self.assertEqual(tt.nanosecond_time, 23 * one_hour + 59 * one_minute + 59 * one_second + 123 * one_milli + 456 * one_micro + 700) + assert tt.nanosecond_time == 23 * one_hour + 59 * one_minute + 59 * one_second + 123 * one_milli + 456 * one_micro + 700 tt = Time('23:59:59.12345678') - self.assertEqual(tt.nanosecond_time, 23 * one_hour + 59 * one_minute + 59 * one_second + 123 * one_milli + 456 * one_micro + 780) + assert tt.nanosecond_time == 23 * one_hour + 59 * one_minute + 59 * one_second + 123 * one_milli + 456 * one_micro + 780 tt = Time('23:59:59.123456789') - self.assertEqual(tt.nanosecond_time, 23 * one_hour + 59 * one_minute + 59 * one_second + 123 * one_milli + 456 * one_micro + 789) + assert tt.nanosecond_time == 23 * one_hour + 59 * one_minute + 59 * one_second + 123 * one_milli + 456 * one_micro + 789 def test_micro_precision(self): Time('23:59:59.1') @@ -121,26 +119,26 @@ def test_micro_precision(self): def test_from_int(self): tt = Time(12345678) - self.assertEqual(tt.nanosecond_time, 12345678) + assert tt.nanosecond_time == 12345678 def test_from_time(self): expected_time = datetime.time(12, 1, 2, 3) tt = Time(expected_time) - self.assertEqual(tt, expected_time) + assert tt == expected_time def test_as_time(self): expected_time = datetime.time(12, 1, 2, 3) tt = Time(expected_time) - self.assertEqual(tt.time(), expected_time) + assert tt.time() == expected_time def test_equals(self): # util.Time self equality - self.assertEqual(Time(1234), Time(1234)) + assert Time(1234) == Time(1234) def test_str_repr(self): time_str = '12:13:14.123456789' - self.assertEqual(str(Time(time_str)), time_str) - self.assertEqual(repr(Time(1)), 'Time(1)') + assert str(Time(time_str)) == time_str + assert repr(Time(1)) == 'Time(1)' def test_invalid_init(self): self.assertRaises(ValueError, Time, '1999-10-10 11:11:11.1234') @@ -154,24 +152,24 @@ class DurationTests(unittest.TestCase): def test_valid_format(self): valid = Duration(1, 1, 1) - self.assertEqual(valid.months, 1) - self.assertEqual(valid.days, 1) - self.assertEqual(valid.nanoseconds, 1) + assert valid.months == 1 + assert valid.days == 1 + assert valid.nanoseconds == 1 valid = Duration(nanoseconds=100000) - self.assertEqual(valid.months, 0) - self.assertEqual(valid.days, 0) - self.assertEqual(valid.nanoseconds, 100000) + assert valid.months == 0 + assert valid.days == 0 + assert valid.nanoseconds == 100000 valid = Duration() - self.assertEqual(valid.months, 0) - self.assertEqual(valid.days, 0) - self.assertEqual(valid.nanoseconds, 0) + assert valid.months == 0 + assert valid.days == 0 + assert valid.nanoseconds == 0 valid = Duration(-10, -21, -1000) - self.assertEqual(valid.months, -10) - self.assertEqual(valid.days, -21) - self.assertEqual(valid.nanoseconds, -1000) + assert valid.months == -10 + assert valid.days == -21 + assert valid.nanoseconds == -1000 def test_equality(self): @@ -181,26 +179,26 @@ def test_equality(self): first = Duration(1, 1, 1) second = Duration(1, 1, 1) - self.assertEqual(first, second) + assert first == second first = Duration() second = Duration(0, 0, 0) - self.assertEqual(first, second) + assert first == second first = Duration(1000, 10000, 2345345) second = Duration(1000, 10000, 2345345) - self.assertEqual(first, second) + assert first == second first = Duration(12, 0 , 100) second = Duration(nanoseconds=100, months=12) - self.assertEqual(first, second) + assert first == second def test_str(self): - self.assertEqual(str(Duration(1, 1, 1)), "1mo1d1ns") - self.assertEqual(str(Duration(1, 1, -1)), "-1mo1d1ns") - self.assertEqual(str(Duration(1, 1, 1000000000000000)), "1mo1d1000000000000000ns") - self.assertEqual(str(Duration(52, 23, 564564)), "52mo23d564564ns") + assert str(Duration(1, 1, 1)) == "1mo1d1ns" + assert str(Duration(1, 1, -1)) == "-1mo1d1ns" + assert str(Duration(1, 1, 1000000000000000)) == "1mo1d1000000000000000ns" + assert str(Duration(52, 23, 564564)) == "52mo23d564564ns" class VersionTests(unittest.TestCase): @@ -223,12 +221,12 @@ def test_version_parsing(self): for str_version, expected_result in versions: v = Version(str_version) - self.assertEqual(str_version, str(v)) - self.assertEqual(v.major, expected_result[0]) - self.assertEqual(v.minor, expected_result[1]) - self.assertEqual(v.patch, expected_result[2]) - self.assertEqual(v.build, expected_result[3]) - self.assertEqual(v.prerelease, expected_result[4]) + assert str_version == str(v) + assert v.major == expected_result[0] + assert v.minor == expected_result[1] + assert v.patch == expected_result[2] + assert v.build == expected_result[3] + assert v.prerelease == expected_result[4] # not supported version formats with self.assertRaises(ValueError): @@ -291,11 +289,5 @@ def test_version_compare(self): class FunctionTests(unittest.TestCase): def test_maybe_add_timeout_to_query(self): - self.assertEqual( - "SELECT * FROM HOSTS", - maybe_add_timeout_to_query("SELECT * FROM HOSTS", None) - ) - self.assertEqual( - "SELECT * FROM HOSTS USING TIMEOUT 1000ms", - maybe_add_timeout_to_query("SELECT * FROM HOSTS", datetime.timedelta(seconds=1)) - ) + assert "SELECT * FROM HOSTS" == maybe_add_timeout_to_query("SELECT * FROM HOSTS", None) + assert "SELECT * FROM HOSTS USING TIMEOUT 1000ms" == maybe_add_timeout_to_query("SELECT * FROM HOSTS", datetime.timedelta(seconds=1)) From 03feeeea5da3186d10da39c7abbca25af6423e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 19:41:09 +0200 Subject: [PATCH 043/298] Replace self.assertIsNone with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used command: ``` ast-grep run --pattern 'self.assertIsNone($A)' --rewrite 'assert $A is None' --lang python ./tests -U ``` --- .../cqlengine/columns/test_validation.py | 14 ++--- .../model/test_class_construction.py | 2 +- .../cqlengine/model/test_model_io.py | 6 +- .../integration/cqlengine/model/test_udts.py | 8 +-- .../cqlengine/query/test_batch_query.py | 4 +- .../cqlengine/query/test_queryset.py | 4 +- .../cqlengine/query/test_updates.py | 2 +- .../statements/test_assignment_clauses.py | 56 +++++++++---------- .../cqlengine/test_lwt_conditional.py | 10 ++-- tests/integration/cqlengine/test_ttl.py | 4 +- tests/integration/standard/test_cluster.py | 8 +-- .../standard/test_cython_protocol_handlers.py | 3 +- tests/integration/standard/test_metadata.py | 12 ++-- .../standard/test_prepared_statements.py | 2 +- tests/integration/standard/test_query.py | 2 +- tests/integration/standard/test_udts.py | 2 +- tests/unit/advanced/test_graph.py | 4 +- tests/unit/advanced/test_metadata.py | 8 +-- tests/unit/test_cluster.py | 4 +- tests/unit/test_metadata.py | 2 +- tests/unit/test_orderedmap.py | 2 +- tests/unit/test_query.py | 8 +-- 22 files changed, 84 insertions(+), 83 deletions(-) diff --git a/tests/integration/cqlengine/columns/test_validation.py b/tests/integration/cqlengine/columns/test_validation.py index bd755fa69d..0f167c6758 100644 --- a/tests/integration/cqlengine/columns/test_validation.py +++ b/tests/integration/cqlengine/columns/test_validation.py @@ -83,10 +83,10 @@ def test_datetime_date_support(self): def test_datetime_none(self): dt = self.DatetimeTest.objects.create(test_id=3, created_at=None) dt2 = self.DatetimeTest.objects(test_id=3).first() - self.assertIsNone(dt2.created_at) + assert dt2.created_at is None dts = self.DatetimeTest.objects.filter(test_id=3).values_list('created_at') - self.assertIsNone(dts[0][0]) + assert dts[0][0] is None def test_datetime_invalid(self): dt_value= 'INVALID' @@ -160,7 +160,7 @@ def test_validation_preserves_none(self): test_obj = self.BoolValidationTest(test_id=1) test_obj.validate() - self.assertIsNone(test_obj.bool_column) + assert test_obj.bool_column is None class TestVarInt(BaseCassEngTestCase): @@ -222,10 +222,10 @@ def _check_value_is_correct_in_db(self, value): """ if value is None: result = self.model_class.objects.all().allow_filtering().filter(test_id=0).first() - self.assertIsNone(result.class_param) + assert result.class_param is None result = self.model_class.objects(test_id=0).first() - self.assertIsNone(result.class_param) + assert result.class_param is None else: if not isinstance(value, self.python_klass): @@ -276,10 +276,10 @@ def test_param_none(self): """ self.model_class.objects.create(test_id=1, class_param=None) dt2 = self.model_class.objects(test_id=1).first() - self.assertIsNone(dt2.class_param) + assert dt2.class_param is None dts = self.model_class.objects(test_id=1).values_list('class_param') - self.assertIsNone(dts[0][0]) + assert dts[0][0] is None class TestDate(DataType, BaseCassEngTestCase): diff --git a/tests/integration/cqlengine/model/test_class_construction.py b/tests/integration/cqlengine/model/test_class_construction.py index 1488a6fd11..a6dd530182 100644 --- a/tests/integration/cqlengine/model/test_class_construction.py +++ b/tests/integration/cqlengine/model/test_class_construction.py @@ -48,7 +48,7 @@ class TestModel(Model): self.assertHasAttr(inst, 'id') self.assertHasAttr(inst, 'text') self.assertIsNotNone(inst.id) - self.assertIsNone(inst.text) + assert inst.text is None def test_values_on_instantiation(self): """ diff --git a/tests/integration/cqlengine/model/test_model_io.py b/tests/integration/cqlengine/model/test_model_io.py index 7fe845190d..c376889bc1 100644 --- a/tests/integration/cqlengine/model/test_model_io.py +++ b/tests/integration/cqlengine/model/test_model_io.py @@ -140,7 +140,7 @@ def test_model_deleting_works_properly(self): tm = TestModel.create(count=8, text='123456789') tm.delete() tm2 = TestModel.objects(id=tm.pk).first() - self.assertIsNone(tm2) + assert tm2 is None def test_column_deleting_works_properly(self): """ @@ -221,9 +221,9 @@ def test_can_specify_none_instead_of_default(self): # override default inst = TestModel.create(a_bool=None) - self.assertIsNone(inst.a_bool) + assert inst.a_bool is None queried = TestModel.objects(id=inst.id).first() - self.assertIsNone(queried.a_bool) + assert queried.a_bool is None # letting default be set inst = TestModel.create() diff --git a/tests/integration/cqlengine/model/test_udts.py b/tests/integration/cqlengine/model/test_udts.py index f53e694f4a..c7893d39b3 100644 --- a/tests/integration/cqlengine/model/test_udts.py +++ b/tests/integration/cqlengine/model/test_udts.py @@ -151,7 +151,7 @@ def test_can_update_udts_with_nones(self): created_user.update() john_info = UserModel.objects.first().info - self.assertIsNone(john_info) + assert john_info is None def test_can_create_same_udt_different_keyspaces(self): sync_type(DEFAULT_KEYSPACE, User) @@ -179,15 +179,15 @@ class UserModelGender(Model): john_info = UserModelGender.objects.first().info assert 42 == john_info.age assert "John" == john_info.name - self.assertIsNone(john_info.gender) + assert john_info.gender is None user = UserGender(age=42) UserModelGender.create(id=0, info=user) john_info = UserModelGender.objects.first().info assert 42 == john_info.age - self.assertIsNone(john_info.name) - self.assertIsNone(john_info.gender) + assert john_info.name is None + assert john_info.gender is None def test_can_insert_nested_udts(self): class Depth_0(UserType): diff --git a/tests/integration/cqlengine/query/test_batch_query.py b/tests/integration/cqlengine/query/test_batch_query.py index 1a1deab77b..86bd57fb6b 100644 --- a/tests/integration/cqlengine/query/test_batch_query.py +++ b/tests/integration/cqlengine/query/test_batch_query.py @@ -150,7 +150,7 @@ def test_none_success_case(self): assert q._batch == b q = q.batch(None) - self.assertIsNone(q._batch) + assert q._batch is None @execute_count(0) def test_dml_none_success_case(self): @@ -161,7 +161,7 @@ def test_dml_none_success_case(self): assert q._batch == b q.batch(None) - self.assertIsNone(q._batch) + assert q._batch is None @execute_count(3) def test_batch_execute_on_exception_succeeds(self): diff --git a/tests/integration/cqlengine/query/test_queryset.py b/tests/integration/cqlengine/query/test_queryset.py index d8aad8cf79..84115ba835 100644 --- a/tests/integration/cqlengine/query/test_queryset.py +++ b/tests/integration/cqlengine/query/test_queryset.py @@ -1231,7 +1231,7 @@ def test_basic_crud(self): # delete model.objects(k0=i.k0, k1=i.k1).delete() i = model.objects(k0=i.k0, k1=i.k1).first() - self.assertIsNone(i) + assert i is None i = model.create(**values) i = model.objects(k0=i.k0, k1=i.k1).first() @@ -1239,7 +1239,7 @@ def test_basic_crud(self): i.delete() model.objects(k0=i.k0, k1=i.k1).delete() i = model.objects(k0=i.k0, k1=i.k1).first() - self.assertIsNone(i) + assert i is None @execute_count(21) def test_slice(self): diff --git a/tests/integration/cqlengine/query/test_updates.py b/tests/integration/cqlengine/query/test_updates.py index 1189ed0f5f..9b7d5174df 100644 --- a/tests/integration/cqlengine/query/test_updates.py +++ b/tests/integration/cqlengine/query/test_updates.py @@ -348,4 +348,4 @@ def test_static_deletion(self): sdm = StaticDeleteModel.filter(example_id=5).first() assert 1 == sdm.example_static2 sdm.update(example_static2=None) - self.assertIsNone(sdm.example_static2) + assert sdm.example_static2 is None diff --git a/tests/integration/cqlengine/statements/test_assignment_clauses.py b/tests/integration/cqlengine/statements/test_assignment_clauses.py index 5a7c89aacd..91a99b036b 100644 --- a/tests/integration/cqlengine/statements/test_assignment_clauses.py +++ b/tests/integration/cqlengine/statements/test_assignment_clauses.py @@ -35,8 +35,8 @@ def test_update_from_none(self): c.set_context_id(0) assert c._assignments == set((1, 2)) - self.assertIsNone(c._additions) - self.assertIsNone(c._removals) + assert c._additions is None + assert c._removals is None assert c.get_context_size() == 1 assert str(c) == '"s" = %(0)s' @@ -51,9 +51,9 @@ def test_null_update(self): c._analyze() c.set_context_id(0) - self.assertIsNone(c._assignments) - self.assertIsNone(c._additions) - self.assertIsNone(c._removals) + assert c._assignments is None + assert c._additions is None + assert c._removals is None assert c.get_context_size() == 0 assert str(c) == '' @@ -68,9 +68,9 @@ def test_no_update(self): c._analyze() c.set_context_id(0) - self.assertIsNone(c._assignments) - self.assertIsNone(c._additions) - self.assertIsNone(c._removals) + assert c._assignments is None + assert c._additions is None + assert c._removals is None assert c.get_context_size() == 0 assert str(c) == '' @@ -87,8 +87,8 @@ def test_update_empty_set(self): c.set_context_id(0) assert c._assignments == set() - self.assertIsNone(c._additions) - self.assertIsNone(c._removals) + assert c._additions is None + assert c._removals is None assert c.get_context_size() == 1 assert str(c) == '"s" = %(0)s' @@ -102,9 +102,9 @@ def test_additions(self): c._analyze() c.set_context_id(0) - self.assertIsNone(c._assignments) + assert c._assignments is None assert c._additions == set((3,)) - self.assertIsNone(c._removals) + assert c._removals is None assert c.get_context_size() == 1 assert str(c) == '"s" = "s" + %(0)s' @@ -118,8 +118,8 @@ def test_removals(self): c._analyze() c.set_context_id(0) - self.assertIsNone(c._assignments) - self.assertIsNone(c._additions) + assert c._assignments is None + assert c._additions is None assert c._removals == set((3,)) assert c.get_context_size() == 1 @@ -134,7 +134,7 @@ def test_additions_and_removals(self): c._analyze() c.set_context_id(0) - self.assertIsNone(c._assignments) + assert c._assignments is None assert c._additions == set((3,)) assert c._removals == set((1,)) @@ -154,8 +154,8 @@ def test_update_from_none(self): c.set_context_id(0) assert c._assignments == [1, 2, 3] - self.assertIsNone(c._append) - self.assertIsNone(c._prepend) + assert c._append is None + assert c._prepend is None assert c.get_context_size() == 1 assert str(c) == '"s" = %(0)s' @@ -170,8 +170,8 @@ def test_update_from_empty(self): c.set_context_id(0) assert c._assignments == [1, 2, 3] - self.assertIsNone(c._append) - self.assertIsNone(c._prepend) + assert c._append is None + assert c._prepend is None assert c.get_context_size() == 1 assert str(c) == '"s" = %(0)s' @@ -186,8 +186,8 @@ def test_update_from_different_list(self): c.set_context_id(0) assert c._assignments == [1, 2, 3] - self.assertIsNone(c._append) - self.assertIsNone(c._prepend) + assert c._append is None + assert c._prepend is None assert c.get_context_size() == 1 assert str(c) == '"s" = %(0)s' @@ -201,9 +201,9 @@ def test_append(self): c._analyze() c.set_context_id(0) - self.assertIsNone(c._assignments) + assert c._assignments is None assert c._append == [3, 4] - self.assertIsNone(c._prepend) + assert c._prepend is None assert c.get_context_size() == 1 assert str(c) == '"s" = "s" + %(0)s' @@ -217,8 +217,8 @@ def test_prepend(self): c._analyze() c.set_context_id(0) - self.assertIsNone(c._assignments) - self.assertIsNone(c._append) + assert c._assignments is None + assert c._append is None assert c._prepend == [1, 2] assert c.get_context_size() == 1 @@ -233,7 +233,7 @@ def test_append_and_prepend(self): c._analyze() c.set_context_id(0) - self.assertIsNone(c._assignments) + assert c._assignments is None assert c._append == [5, 6] assert c._prepend == [1, 2] @@ -251,8 +251,8 @@ def test_shrinking_list_update(self): c.set_context_id(0) assert c._assignments == [1, 2, 3] - self.assertIsNone(c._append) - self.assertIsNone(c._prepend) + assert c._append is None + assert c._prepend is None assert c.get_context_size() == 1 assert str(c) == '"s" = %(0)s' diff --git a/tests/integration/cqlengine/test_lwt_conditional.py b/tests/integration/cqlengine/test_lwt_conditional.py index b0d8b4a962..c2890051c5 100644 --- a/tests/integration/cqlengine/test_lwt_conditional.py +++ b/tests/integration/cqlengine/test_lwt_conditional.py @@ -250,7 +250,7 @@ def test_update_to_none(self): t.iff(count=9999).update(text=None) self.assertIsNotNone(TestConditionalModel.objects(id=t.id).first().text) t.iff(count=5).update(text=None) - self.assertIsNone(TestConditionalModel.objects(id=t.id).first().text) + assert TestConditionalModel.objects(id=t.id).first().text is None # QuerySet path t = TestConditionalModel.if_not_exists().create(text='something', count=5) @@ -259,21 +259,21 @@ def test_update_to_none(self): TestConditionalModel.objects(id=t.id).iff(count=9999).update(text=None) self.assertIsNotNone(TestConditionalModel.objects(id=t.id).first().text) TestConditionalModel.objects(id=t.id).iff(count=5).update(text=None) - self.assertIsNone(TestConditionalModel.objects(id=t.id).first().text) + assert TestConditionalModel.objects(id=t.id).first().text is None def test_column_delete_after_update(self): # DML path t = TestConditionalModel.if_not_exists().create(text='something', count=5) t.iff(count=5).update(text=None, count=6) - self.assertIsNone(t.text) + assert t.text is None assert t.count == 6 # QuerySet path t = TestConditionalModel.if_not_exists().create(text='something', count=5) TestConditionalModel.objects(id=t.id).iff(count=5).update(text=None, count=6) - self.assertIsNone(TestConditionalModel.objects(id=t.id).first().text) + assert TestConditionalModel.objects(id=t.id).first().text is None assert TestConditionalModel.objects(id=t.id).first().count == 6 def test_conditional_without_instance(self): @@ -294,5 +294,5 @@ def test_conditional_without_instance(self): TestConditionalModel.iff(count=5).filter(id=uuid).update(text=None, count=6) t = TestConditionalModel.filter(id=uuid).first() - self.assertIsNone(t.text) + assert t.text is None assert t.count == 6 diff --git a/tests/integration/cqlengine/test_ttl.py b/tests/integration/cqlengine/test_ttl.py index 9431cc3717..6837262895 100644 --- a/tests/integration/cqlengine/test_ttl.py +++ b/tests/integration/cqlengine/test_ttl.py @@ -177,7 +177,7 @@ def test_default_ttl_not_set(self): o = TestTTLModel.create(text="some text") tid = o.id - self.assertIsNone(o._ttl) + assert o._ttl is None default_ttl = self.get_default_ttl('test_ttlmodel') assert default_ttl == 0 @@ -195,7 +195,7 @@ def test_default_ttl_set(self): tid = o.id # Should not be set, it's handled by Cassandra - self.assertIsNone(o._ttl) + assert o._ttl is None default_ttl = self.get_default_ttl('test_default_ttlmodel') assert default_ttl == 20 diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 52680459fe..1b74c44bff 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -89,7 +89,7 @@ def test_ignored_host_up(self): if str(host) == "127.0.0.1:9042": self.assertTrue(host.is_up) else: - self.assertIsNone(host.is_up) + assert host.is_up is None cluster.shutdown() @local @@ -566,7 +566,7 @@ def test_trace(self): query = "SELECT * FROM system.local WHERE key='local'" statement = SimpleStatement(query) result = session.execute(statement) - self.assertIsNone(result.get_query_trace()) + assert result.get_query_trace() is None statement2 = SimpleStatement(query) future = session.execute_async(statement2, trace=True) @@ -576,7 +576,7 @@ def test_trace(self): statement2 = SimpleStatement(query) future = session.execute_async(statement2) future.result() - self.assertIsNone(future.get_query_trace()) + assert future.get_query_trace() is None prepared = session.prepare("SELECT * FROM system.local WHERE key='local'") future = session.execute_async(prepared, parameters=(), trace=True) @@ -642,7 +642,7 @@ def test_one_returns_none(self): """ with TestCluster() as cluster: session = cluster.connect() - self.assertIsNone(session.execute("SELECT * from system.local WHERE key='madeup_key'").one()) + assert session.execute("SELECT * from system.local WHERE key='madeup_key'").one() is None def test_string_coverage(self): """ diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index 0f78a45dd1..62a8a4ad87 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -255,6 +255,7 @@ def test_null_types(self): else: had_none = True [self.assertIsNotNone(col_array[i]) for i in mapped_index[:begin_unset]] - [self.assertIsNone(col_array[i]) for i in mapped_index[begin_unset:]] + for i in mapped_index[begin_unset:]: + assert col_array[i] is None self.assertTrue(had_masked) self.assertTrue(had_none) diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 96ed4b33b6..65ffa30a4c 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -1933,12 +1933,12 @@ def test_cql_optional_params(self): encoder = Encoder() # no initial condition, final func - self.assertIsNone(kwargs['initial_condition']) - self.assertIsNone(kwargs['final_func']) + assert kwargs['initial_condition'] is None + assert kwargs['final_func'] is None with self.VerifiedAggregate(self, **kwargs) as va: meta = self.keyspace_aggregate_meta[va.signature] - self.assertIsNone(meta.initial_condition) - self.assertIsNone(meta.final_func) + assert meta.initial_condition is None + assert meta.final_func is None cql = meta.as_cql_query() assert cql.find('INITCOND') == -1 assert cql.find('FINALFUNC') == -1 @@ -1948,7 +1948,7 @@ def test_cql_optional_params(self): with self.VerifiedAggregate(self, **kwargs) as va: meta = self.keyspace_aggregate_meta[va.signature] assert meta.initial_condition == kwargs['initial_condition'] - self.assertIsNone(meta.final_func) + assert meta.final_func is None cql = meta.as_cql_query() search_string = "INITCOND %s" % kwargs['initial_condition'] self.assertGreater(cql.find(search_string), 0, '"%s" search string not found in cql:\n%s' % (search_string, cql)) @@ -1959,7 +1959,7 @@ def test_cql_optional_params(self): kwargs['final_func'] = 'List_As_String' with self.VerifiedAggregate(self, **kwargs) as va: meta = self.keyspace_aggregate_meta[va.signature] - self.assertIsNone(meta.initial_condition) + assert meta.initial_condition is None assert meta.final_func == kwargs['final_func'] cql = meta.as_cql_query() assert cql.find('INITCOND') == -1 diff --git a/tests/integration/standard/test_prepared_statements.py b/tests/integration/standard/test_prepared_statements.py index 478399191d..9b8b328cfe 100644 --- a/tests/integration/standard/test_prepared_statements.py +++ b/tests/integration/standard/test_prepared_statements.py @@ -586,7 +586,7 @@ def _test_updated_conditional(self, session, value): def check_result_and_metadata(expected): assert session.execute(prepared_statement, (value, value, value)).one() == expected assert prepared_statement.result_metadata_id == first_id - self.assertIsNone(prepared_statement.result_metadata) + assert prepared_statement.result_metadata is None # Successful conditional update check_result_and_metadata((True,)) diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index cf103a0612..8ddc191dc9 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -237,7 +237,7 @@ def test_incomplete_query_trace(self): # should get the events with wait False trace.populate(wait_for_complete=False) - self.assertIsNone(trace.duration) + assert trace.duration is None self.assertIsNotNone(trace.trace_id) self.assertIsNotNone(trace.request_type) self.assertIsNotNone(trace.parameters) diff --git a/tests/integration/standard/test_udts.py b/tests/integration/standard/test_udts.py index 3165782ab5..30d1e8068a 100644 --- a/tests/integration/standard/test_udts.py +++ b/tests/integration/standard/test_udts.py @@ -702,7 +702,7 @@ def test_type_alteration(self): s.execute('ALTER TYPE %s ADD v1 text' % (type_name,)) val = s.execute('SELECT v FROM %s' % self.table_name).one()[0] assert val['v0'] == 1 - self.assertIsNone(val['v1']) + assert val['v1'] is None s.execute("INSERT INTO %s (k, v) VALUES (0, {v0 : 2, v1 : 'sometext'})" % (self.table_name,)) val = s.execute('SELECT v FROM %s' % self.table_name).one()[0] assert val['v0'] == 2 diff --git a/tests/unit/advanced/test_graph.py b/tests/unit/advanced/test_graph.py index 9ddaa4e9bc..7388301b9a 100644 --- a/tests/unit/advanced/test_graph.py +++ b/tests/unit/advanced/test_graph.py @@ -293,12 +293,12 @@ def test_set_attr(self): # will update options with set value another = GraphOptions() - self.assertIsNone(another.graph_name) + assert another.graph_name is None another.update(opts) assert another.graph_name == expected.encode() opts.graph_name = None - self.assertIsNone(opts.graph_name) + assert opts.graph_name is None # will not update another with its set-->unset value another.update(opts) assert another.graph_name == expected.encode() # remains unset diff --git a/tests/unit/advanced/test_metadata.py b/tests/unit/advanced/test_metadata.py index 0ded90d974..a336658b00 100644 --- a/tests/unit/advanced/test_metadata.py +++ b/tests/unit/advanced/test_metadata.py @@ -70,8 +70,8 @@ def test_keyspace_with_graph_engine(self): def test_table_no_vertex_or_edge(self): tm = self._create_table_metadata() - self.assertIsNone(tm.vertex) - self.assertIsNone(tm.edge) + assert tm.vertex is None + assert tm.edge is None cql = tm.as_cql_query() self.assertNotIn("VERTEX LABEL", cql) self.assertNotIn("EDGE LABEL", cql) @@ -79,14 +79,14 @@ def test_table_no_vertex_or_edge(self): def test_table_with_vertex(self): tm = self._create_table_metadata(with_vertex=True) self.assertIsInstance(tm.vertex, VertexMetadata) - self.assertIsNone(tm.edge) + assert tm.edge is None cql = tm.as_cql_query() self.assertIn("VERTEX LABEL", cql) self.assertNotIn("EDGE LABEL", cql) def test_table_with_edge(self): tm = self._create_table_metadata(with_edge=True) - self.assertIsNone(tm.vertex) + assert tm.vertex is None self.assertIsInstance(tm.edge, EdgeMetadata) cql = tm.as_cql_query() self.assertNotIn("VERTEX LABEL", cql) diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 5d0860251a..de2668129c 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -159,7 +159,7 @@ def test_default_serial_consistency_level_ep(self, *_): # default is None default_profile = c.profile_manager.default - self.assertIsNone(default_profile.serial_consistency_level) + assert default_profile.serial_consistency_level is None for cl in (None, ConsistencyLevel.LOCAL_SERIAL, ConsistencyLevel.SERIAL): s.get_execution_profile(EXEC_PROFILE_DEFAULT).serial_consistency_level = cl @@ -186,7 +186,7 @@ def test_default_serial_consistency_level_legacy(self, *_): s = Session(c, [Host("127.0.0.1", SimpleConvictionPolicy)]) c.connection_class.initialize_reactor() # default is None - self.assertIsNone(s.default_serial_consistency_level) + assert s.default_serial_consistency_level is None # Should fail with self.assertRaises(ValueError): diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index ccdee2fa1c..854f9b66f4 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -93,7 +93,7 @@ def test_replication_strategy(self): assert rs.create('NetworkTopologyStrategy', fake_options_map).dc_replication_factors == NetworkTopologyStrategy(fake_options_map).dc_replication_factors fake_options_map = {'options': 'map'} - self.assertIsNone(rs.create('SimpleStrategy', fake_options_map)) + assert rs.create('SimpleStrategy', fake_options_map) is None fake_options_map = {'options': 'map'} self.assertIsInstance(rs.create('LocalStrategy', fake_options_map), LocalStrategy) diff --git a/tests/unit/test_orderedmap.py b/tests/unit/test_orderedmap.py index a98ad40919..d1326d5acc 100644 --- a/tests/unit/test_orderedmap.py +++ b/tests/unit/test_orderedmap.py @@ -78,7 +78,7 @@ def test_get(self): assert om.get(k) == v assert om.get('notthere', 'default') == 'default' - self.assertIsNone(om.get('notthere')) + assert om.get('notthere') is None def test_equal(self): d1 = {'one': 1} diff --git a/tests/unit/test_query.py b/tests/unit/test_query.py index 7eaa755256..86f84c562e 100644 --- a/tests/unit/test_query.py +++ b/tests/unit/test_query.py @@ -37,8 +37,8 @@ def test_clear(self): batch.clear() self.assertFalse(batch._statements_and_parameters) - self.assertIsNone(batch.keyspace) - self.assertIsNone(batch.routing_key) + assert batch.keyspace is None + assert batch.routing_key is None self.assertFalse(batch.custom_payload) batch.add(ss) @@ -47,8 +47,8 @@ def test_clear_empty(self): batch = BatchStatement() batch.clear() self.assertFalse(batch._statements_and_parameters) - self.assertIsNone(batch.keyspace) - self.assertIsNone(batch.routing_key) + assert batch.keyspace is None + assert batch.routing_key is None self.assertFalse(batch.custom_payload) batch.add('something') From ffee5a67b13041bb262fb89a5e0d044d6cab1e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 19:43:30 +0200 Subject: [PATCH 044/298] Replace self.assertIn with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used command: ``` ast-grep run --pattern 'self.assertIn($A, $B)' --rewrite 'assert $A in $B' --lang python ./tests -U ``` --- .../columns/test_container_columns.py | 8 +- .../management/test_compaction_settings.py | 2 +- .../cqlengine/management/test_management.py | 22 +-- .../integration/cqlengine/model/test_model.py | 10 +- .../integration/cqlengine/model/test_udts.py | 4 +- .../statements/test_insert_statement.py | 2 +- .../statements/test_select_statement.py | 10 +- .../statements/test_update_statement.py | 2 +- tests/integration/cqlengine/test_ifexists.py | 6 +- .../integration/cqlengine/test_ifnotexists.py | 6 +- .../cqlengine/test_lwt_conditional.py | 4 +- tests/integration/cqlengine/test_ttl.py | 8 +- .../long/test_loadbalancingpolicies.py | 10 +- .../simulacron/test_backpressure.py | 4 +- tests/integration/simulacron/test_cluster.py | 8 +- .../integration/simulacron/test_connection.py | 4 +- .../simulacron/test_empty_column.py | 4 +- tests/integration/simulacron/test_endpoint.py | 4 +- tests/integration/standard/test_cluster.py | 10 +- .../standard/test_custom_protocol_handler.py | 3 +- tests/integration/standard/test_metadata.py | 134 +++++++++--------- .../standard/test_prepared_statements.py | 2 +- tests/integration/standard/test_query.py | 2 +- .../integration/standard/test_shard_aware.py | 8 +- tests/integration/standard/test_udts.py | 2 +- tests/unit/advanced/test_metadata.py | 43 ++---- tests/unit/test_cluster.py | 10 +- tests/unit/test_metadata.py | 42 ++---- tests/unit/test_parameter_binding.py | 14 +- tests/unit/test_policies.py | 2 +- tests/unit/test_resultset.py | 3 +- tests/unit/test_row_factories.py | 4 +- tests/unit/test_sortedset.py | 2 +- 33 files changed, 177 insertions(+), 222 deletions(-) diff --git a/tests/integration/cqlengine/columns/test_container_columns.py b/tests/integration/cqlengine/columns/test_container_columns.py index 040d9125e4..92dfda0791 100644 --- a/tests/integration/cqlengine/columns/test_container_columns.py +++ b/tests/integration/cqlengine/columns/test_container_columns.py @@ -116,11 +116,11 @@ def test_io_success(self): self.assertIsInstance(m2.int_set, set) self.assertIsInstance(m2.text_set, set) - self.assertIn(1, m2.int_set) - self.assertIn(2, m2.int_set) + assert 1 in m2.int_set + assert 2 in m2.int_set - self.assertIn('kai', m2.text_set) - self.assertIn('andreas', m2.text_set) + assert 'kai' in m2.text_set + assert 'andreas' in m2.text_set def test_type_validation(self): """ diff --git a/tests/integration/cqlengine/management/test_compaction_settings.py b/tests/integration/cqlengine/management/test_compaction_settings.py index d58c419d0e..b2dd6b8ec3 100644 --- a/tests/integration/cqlengine/management/test_compaction_settings.py +++ b/tests/integration/cqlengine/management/test_compaction_settings.py @@ -110,7 +110,7 @@ def _verify_options(self, table_meta, expected_options): for name, value in expected_options.items(): if isinstance(value, str): - self.assertIn("%s = '%s'" % (name, value), cql) + assert "%s = '%s'" % (name, value) in cql else: start = cql.find("%s = {" % (name,)) end = cql.find('}', start) diff --git a/tests/integration/cqlengine/management/test_management.py b/tests/integration/cqlengine/management/test_management.py index 5bfb5e38ba..3a0ac569a8 100644 --- a/tests/integration/cqlengine/management/test_management.py +++ b/tests/integration/cqlengine/management/test_management.py @@ -41,7 +41,7 @@ def test_create_drop_succeeeds(self): keyspace_ss = 'test_ks_ss' self.assertNotIn(keyspace_ss, cluster.metadata.keyspaces) management.create_keyspace_simple(keyspace_ss, 2) - self.assertIn(keyspace_ss, cluster.metadata.keyspaces) + assert keyspace_ss in cluster.metadata.keyspaces management.drop_keyspace(keyspace_ss) self.assertNotIn(keyspace_ss, cluster.metadata.keyspaces) @@ -49,7 +49,7 @@ def test_create_drop_succeeeds(self): keyspace_nts = 'test_ks_nts' self.assertNotIn(keyspace_nts, cluster.metadata.keyspaces) management.create_keyspace_network_topology(keyspace_nts, {'dc1': 1}) - self.assertIn(keyspace_nts, cluster.metadata.keyspaces) + assert keyspace_nts in cluster.metadata.keyspaces management.drop_keyspace(keyspace_nts) self.assertNotIn(keyspace_nts, cluster.metadata.keyspaces) @@ -187,20 +187,20 @@ def test_add_column(self): meta_columns = _get_table_metadata(FirstModel).columns assert len(meta_columns) == 5 assert len(ThirdModel._columns) == 4 - self.assertIn('fourth_key', meta_columns) + assert 'fourth_key' in meta_columns self.assertNotIn('fourth_key', ThirdModel._columns) - self.assertIn('blah', ThirdModel._columns) - self.assertIn('blah', meta_columns) + assert 'blah' in ThirdModel._columns + assert 'blah' in meta_columns sync_table(FourthModel) meta_columns = _get_table_metadata(FirstModel).columns assert len(meta_columns) == 5 assert len(ThirdModel._columns) == 4 - self.assertIn('fourth_key', meta_columns) + assert 'fourth_key' in meta_columns self.assertNotIn('fourth_key', FourthModel._columns) - self.assertIn('renamed', FourthModel._columns) + assert 'renamed' in FourthModel._columns self.assertNotIn('renamed', meta_columns) - self.assertIn('blah', meta_columns) + assert 'blah' in meta_columns class ModelWithTableProperties(Model): @@ -277,14 +277,14 @@ def test_sync_table_works_with_primary_keys_only_tables(self): # blows up with DoesNotExist if table does not exist table_meta = management._get_table_metadata(PrimaryKeysOnlyModel) - self.assertIn('LeveledCompactionStrategy', table_meta.as_cql_query()) + assert 'LeveledCompactionStrategy' in table_meta.as_cql_query() PrimaryKeysOnlyModel.__options__['compaction']['class'] = 'SizeTieredCompactionStrategy' sync_table(PrimaryKeysOnlyModel) table_meta = management._get_table_metadata(PrimaryKeysOnlyModel) - self.assertIn('SizeTieredCompactionStrategy', table_meta.as_cql_query()) + assert 'SizeTieredCompactionStrategy' in table_meta.as_cql_query() def test_primary_key_validation(self): """ @@ -476,7 +476,7 @@ class StaticModel(Model): self.assertGreater(m.call_count, 0) statement = m.call_args[0][0].query_string - self.assertIn('"name" text static', statement) + assert '"name" text static' in statement # if we sync again, we should not apply an alter w/ a static sync_table(StaticModel) diff --git a/tests/integration/cqlengine/model/test_model.py b/tests/integration/cqlengine/model/test_model.py index 6868fc7fac..48217b052c 100644 --- a/tests/integration/cqlengine/model/test_model.py +++ b/tests/integration/cqlengine/model/test_model.py @@ -259,9 +259,7 @@ class SensitiveModel(Model): # ignore DeprecationWarning('The loop argument is deprecated since Python 3.8, and scheduled for removal in Python 3.10.') relevant_warnings = [warn for warn in w if "The loop argument is deprecated" not in str(warn.message)] - self.assertIn("__table_name_case_sensitive__ will be removed in 4.0.", str(relevant_warnings[0].message)) - self.assertIn("__table_name_case_sensitive__ will be removed in 4.0.", str(relevant_warnings[1].message)) - self.assertIn("ModelQuerySet indexing with negative indices support will be removed in 4.0.", - str(relevant_warnings[2].message)) - self.assertIn("ModelQuerySet slicing with negative indices support will be removed in 4.0.", - str(relevant_warnings[3].message)) + assert "__table_name_case_sensitive__ will be removed in 4.0." in str(relevant_warnings[0].message) + assert "__table_name_case_sensitive__ will be removed in 4.0." in str(relevant_warnings[1].message) + assert "ModelQuerySet indexing with negative indices support will be removed in 4.0." in str(relevant_warnings[2].message) + assert "ModelQuerySet slicing with negative indices support will be removed in 4.0." in str(relevant_warnings[3].message) diff --git a/tests/integration/cqlengine/model/test_udts.py b/tests/integration/cqlengine/model/test_udts.py index c7893d39b3..a70c685ccd 100644 --- a/tests/integration/cqlengine/model/test_udts.py +++ b/tests/integration/cqlengine/model/test_udts.py @@ -408,7 +408,7 @@ def test_register_default_keyspace(self): connection.udt_by_keyspace.clear() User.register_for_keyspace(None) assert len(connection.udt_by_keyspace) == 1 - self.assertIn(None, connection.udt_by_keyspace) + assert None in connection.udt_by_keyspace # register should be with default keyspace, not None cluster = Mock() @@ -445,7 +445,7 @@ class TheModel(Model): assert len(type_meta.field_names) == len(type_fields) for f in type_fields: - self.assertIn(f.db_field_name, type_meta.field_names) + assert f.db_field_name in type_meta.field_names id = 0 age = 42 diff --git a/tests/integration/cqlengine/statements/test_insert_statement.py b/tests/integration/cqlengine/statements/test_insert_statement.py index 149cfa93a2..0f70253a6c 100644 --- a/tests/integration/cqlengine/statements/test_insert_statement.py +++ b/tests/integration/cqlengine/statements/test_insert_statement.py @@ -40,4 +40,4 @@ def test_additional_rendering(self): ist = InsertStatement('table', ttl=60) ist.add_assignment(Column(db_field='a'), 'b') ist.add_assignment(Column(db_field='c'), 'd') - self.assertIn('USING TTL 60', str(ist)) + assert 'USING TTL 60' in str(ist) diff --git a/tests/integration/cqlengine/statements/test_select_statement.py b/tests/integration/cqlengine/statements/test_select_statement.py index 40516fea3c..ce2c5f6a99 100644 --- a/tests/integration/cqlengine/statements/test_select_statement.py +++ b/tests/integration/cqlengine/statements/test_select_statement.py @@ -50,7 +50,7 @@ def test_count(self): ss = SelectStatement('table', count=True, limit=10, order_by='d') ss.add_where(Column(db_field='a'), EqualsOperator(), 'b') assert str(ss) == 'SELECT COUNT(*) FROM table WHERE "a" = %(0)s LIMIT 10', str(ss) - self.assertIn('LIMIT', str(ss)) + assert 'LIMIT' in str(ss) self.assertNotIn('ORDER', str(ss)) def test_distinct(self): @@ -89,14 +89,14 @@ def test_additional_rendering(self): allow_filtering=True ) qstr = str(ss) - self.assertIn('LIMIT 15', qstr) - self.assertIn('ORDER BY x, y', qstr) - self.assertIn('ALLOW FILTERING', qstr) + assert 'LIMIT 15' in qstr + assert 'ORDER BY x, y' in qstr + assert 'ALLOW FILTERING' in qstr def test_limit_rendering(self): ss = SelectStatement('table', None, limit=10) qstr = str(ss) - self.assertIn('LIMIT 10', qstr) + assert 'LIMIT 10' in qstr ss = SelectStatement('table', None, limit=0) qstr = str(ss) diff --git a/tests/integration/cqlengine/statements/test_update_statement.py b/tests/integration/cqlengine/statements/test_update_statement.py index 149f43e1bb..ba8c7b9364 100644 --- a/tests/integration/cqlengine/statements/test_update_statement.py +++ b/tests/integration/cqlengine/statements/test_update_statement.py @@ -58,7 +58,7 @@ def test_additional_rendering(self): us = UpdateStatement('table', ttl=60) us.add_assignment(Column(db_field='a'), 'b') us.add_where(Column(db_field='a'), EqualsOperator(), 'x') - self.assertIn('USING TTL 60', str(us)) + assert 'USING TTL 60' in str(us) def test_update_set_add(self): us = UpdateStatement('table') diff --git a/tests/integration/cqlengine/test_ifexists.py b/tests/integration/cqlengine/test_ifexists.py index 225b59212c..efe3b64a39 100644 --- a/tests/integration/cqlengine/test_ifexists.py +++ b/tests/integration/cqlengine/test_ifexists.py @@ -264,7 +264,7 @@ def test_if_exists_included_on_queryset_update(self): TestIfExistsModel.objects(id=uuid4()).if_exists().update(count=42) query = m.call_args[0][0].query_string - self.assertIn("IF EXISTS", query) + assert "IF EXISTS" in query def test_if_exists_included_on_update(self): """ tests that if_exists on models update works as expected """ @@ -273,7 +273,7 @@ def test_if_exists_included_on_update(self): TestIfExistsModel(id=uuid4()).if_exists().update(count=8) query = m.call_args[0][0].query_string - self.assertIn("IF EXISTS", query) + assert "IF EXISTS" in query def test_if_exists_included_on_delete(self): """ tests that if_exists on models delete works as expected """ @@ -282,7 +282,7 @@ def test_if_exists_included_on_delete(self): TestIfExistsModel(id=uuid4()).if_exists().delete() query = m.call_args[0][0].query_string - self.assertIn("IF EXISTS", query) + assert "IF EXISTS" in query class IfExistWithCounterTest(BaseIfExistsWithCounterTest): diff --git a/tests/integration/cqlengine/test_ifnotexists.py b/tests/integration/cqlengine/test_ifnotexists.py index 5da8b7a3b3..e2d704ca67 100644 --- a/tests/integration/cqlengine/test_ifnotexists.py +++ b/tests/integration/cqlengine/test_ifnotexists.py @@ -138,7 +138,7 @@ def test_if_not_exists_included_on_create(self): TestIfNotExistsModel.if_not_exists().create(count=8) query = m.call_args[0][0].query_string - self.assertIn("IF NOT EXISTS", query) + assert "IF NOT EXISTS" in query def test_if_not_exists_included_on_save(self): """ tests if we correctly put 'IF NOT EXISTS' for insert statement """ @@ -148,7 +148,7 @@ def test_if_not_exists_included_on_save(self): tm.if_not_exists().save() query = m.call_args[0][0].query_string - self.assertIn("IF NOT EXISTS", query) + assert "IF NOT EXISTS" in query def test_queryset_is_returned_on_class(self): """ ensure we get a queryset description back """ @@ -161,7 +161,7 @@ def test_batch_if_not_exists(self): with BatchQuery() as b: TestIfNotExistsModel.batch(b).if_not_exists().create(count=8) - self.assertIn("IF NOT EXISTS", m.call_args[0][0].query_string) + assert "IF NOT EXISTS" in m.call_args[0][0].query_string class IfNotExistsInstanceTest(BaseIfNotExistsTest): diff --git a/tests/integration/cqlengine/test_lwt_conditional.py b/tests/integration/cqlengine/test_lwt_conditional.py index c2890051c5..f66785bc71 100644 --- a/tests/integration/cqlengine/test_lwt_conditional.py +++ b/tests/integration/cqlengine/test_lwt_conditional.py @@ -62,7 +62,7 @@ def test_update_using_conditional(self): t.iff(text='blah blah').save() args = m.call_args - self.assertIn('IF "text" = %(0)s', args[0][0].query_string) + assert 'IF "text" = %(0)s' in args[0][0].query_string def test_update_conditional_success(self): t = TestConditionalModel.if_not_exists().create(text='blah blah', count=5) @@ -96,7 +96,7 @@ def test_blind_update(self): TestConditionalModel.objects(id=uid).iff(text='blah blah').update(text='oh hey der') args = m.call_args - self.assertIn('IF "text" = %(1)s', args[0][0].query_string) + assert 'IF "text" = %(1)s' in args[0][0].query_string def test_blind_update_fail(self): t = TestConditionalModel.if_not_exists().create(text='blah blah') diff --git a/tests/integration/cqlengine/test_ttl.py b/tests/integration/cqlengine/test_ttl.py index 6837262895..1b9796ad39 100644 --- a/tests/integration/cqlengine/test_ttl.py +++ b/tests/integration/cqlengine/test_ttl.py @@ -94,7 +94,7 @@ def test_ttl_included_on_create(self): TestTTLModel.ttl(60).create(text="hello blake") query = m.call_args[0][0].query_string - self.assertIn("USING TTL", query) + assert "USING TTL" in query def test_queryset_is_returned_on_class(self): """ @@ -113,7 +113,7 @@ def test_update_includes_ttl(self): model.ttl(60).update(text="goodbye forever") query = m.call_args[0][0].query_string - self.assertIn("USING TTL", query) + assert "USING TTL" in query def test_update_syntax_valid(self): # sanity test that ensures the TTL syntax is accepted by cassandra @@ -143,7 +143,7 @@ def test_ttl_is_include_with_query_on_update(self): o.save() query = m.call_args[0][0].query_string - self.assertIn("USING TTL", query) + assert "USING TTL" in query class TTLBlindUpdateTest(BaseTTLTest): @@ -157,7 +157,7 @@ def test_ttl_included_with_blind_update(self): TestTTLModel.objects(id=tid).ttl(60).update(text="bacon") query = m.call_args[0][0].query_string - self.assertIn("USING TTL", query) + assert "USING TTL" in query class TTLDefaultTest(BaseDefaultTTLTest): diff --git a/tests/integration/long/test_loadbalancingpolicies.py b/tests/integration/long/test_loadbalancingpolicies.py index 91ff56042b..bc7a2a5df2 100644 --- a/tests/integration/long/test_loadbalancingpolicies.py +++ b/tests/integration/long/test_loadbalancingpolicies.py @@ -500,16 +500,14 @@ def test_token_aware_composite_key(self): '(?, ?, ?)' % table) bound = prepared.bind((1, 2, 3)) result = session.execute(bound) - self.assertIn(result.response_future.attempted_hosts[0], - cluster.metadata.get_replicas(keyspace, bound.routing_key)) + assert result.response_future.attempted_hosts[0] in cluster.metadata.get_replicas(keyspace, bound.routing_key) # There could be race condition with querying a node # which doesn't yet have the data so we query one of # the replicas results = session.execute(SimpleStatement('SELECT * FROM %s WHERE k1 = 1 AND k2 = 2' % table, routing_key=bound.routing_key)) - self.assertIn(results.response_future.attempted_hosts[0], - cluster.metadata.get_replicas(keyspace, bound.routing_key)) + assert results.response_future.attempted_hosts[0] in cluster.metadata.get_replicas(keyspace, bound.routing_key) self.assertTrue(results[0].i) @@ -650,9 +648,9 @@ def test_token_aware_with_transient_replication(self): trace_hosts = [cluster.metadata.get_host(e.source) for e in f.get_query_trace().events] for h in f.attempted_hosts: - self.assertIn(h, full_dc1_replicas) + assert h in full_dc1_replicas for h in trace_hosts: - self.assertIn(h, full_dc1_replicas) + assert h in full_dc1_replicas def _set_up_shuffle_test(self, keyspace, replication_factor): diff --git a/tests/integration/simulacron/test_backpressure.py b/tests/integration/simulacron/test_backpressure.py index db81b66420..2169c056a7 100644 --- a/tests/integration/simulacron/test_backpressure.py +++ b/tests/integration/simulacron/test_backpressure.py @@ -83,7 +83,7 @@ def test_paused_connections(self): except NoHostAvailable as e: # We shouldn't have any timeouts here, but all of the queries beyond what can fit # in the tcp buffer will have returned with a ConnectionBusy exception - self.assertIn("ConnectionBusy", str(e)) + assert "ConnectionBusy" in str(e) # Verify that we can continue sending queries without any problems for host in session.cluster.metadata.all_hosts(): @@ -148,7 +148,7 @@ def test_cluster_busy(self): for i in range(1000): with self.assertRaises(NoHostAvailable) as e: session.execute(query, [str(i)]) - self.assertIn("ConnectionBusy", str(e.exception)) + assert "ConnectionBusy" in str(e.exception) def test_node_busy(self): """ Verify that once TCP buffer is full, queries continue to get re-routed to other nodes """ diff --git a/tests/integration/simulacron/test_cluster.py b/tests/integration/simulacron/test_cluster.py index 499cf10d10..2df3f99a3e 100644 --- a/tests/integration/simulacron/test_cluster.py +++ b/tests/integration/simulacron/test_cluster.py @@ -55,10 +55,10 @@ def test_writetimeout(self): assert wt.consistency == ConsistencyLevel.name_to_value[consistency] assert wt.received_responses == received_responses assert wt.required_responses == required_responses - self.assertIn(write_type, str(wt)) - self.assertIn(consistency, str(wt)) - self.assertIn(str(received_responses), str(wt)) - self.assertIn(str(required_responses), str(wt)) + assert write_type in str(wt) + assert consistency in str(wt) + assert str(received_responses) in str(wt) + assert str(required_responses) in str(wt) @requiressimulacron diff --git a/tests/integration/simulacron/test_connection.py b/tests/integration/simulacron/test_connection.py index aed4fbafe1..2c30146e9a 100644 --- a/tests/integration/simulacron/test_connection.py +++ b/tests/integration/simulacron/test_connection.py @@ -150,7 +150,7 @@ def test_heart_beat_timeout(self): time.sleep((idle_heartbeat_timeout + idle_heartbeat_interval) * 2.5) for host in cluster.metadata.all_hosts(): - self.assertIn(host, listener.hosts_marked_down) + assert host in listener.hosts_marked_down # In this case HostConnection._replace shouldn't be called self.assertNotIn("_replace", executor.called_functions) @@ -461,7 +461,7 @@ def test_driver_recovers_nework_isolation(self): time.sleep((idle_heartbeat_timeout + idle_heartbeat_interval) * 2) for host in cluster.metadata.all_hosts(): - self.assertIn(host, listener.hosts_marked_down) + assert host in listener.hosts_marked_down self.assertRaises(NoHostAvailable, session.execute, "SELECT * from system.local WHERE key='local'") diff --git a/tests/integration/simulacron/test_empty_column.py b/tests/integration/simulacron/test_empty_column.py index fbbb9ec878..589f730f04 100644 --- a/tests/integration/simulacron/test_empty_column.py +++ b/tests/integration/simulacron/test_empty_column.py @@ -221,8 +221,8 @@ def test_empty_columns_in_system_schema(self): table_metadata = self.cluster.metadata.keyspaces['testks'].tables['testtable'] assert len(table_metadata.columns) == 2 - self.assertIn('', table_metadata.columns) - self.assertIn(' ', table_metadata.columns) + assert '' in table_metadata.columns + assert ' ' in table_metadata.columns def test_empty_columns_with_cqlengine(self): self._prime_testtable_query() diff --git a/tests/integration/simulacron/test_endpoint.py b/tests/integration/simulacron/test_endpoint.py index d053da3596..4aee0016a4 100644 --- a/tests/integration/simulacron/test_endpoint.py +++ b/tests/integration/simulacron/test_endpoint.py @@ -86,7 +86,7 @@ def test_default_endpoint(self): self.assertIsInstance(self.cluster.control_connection._connection.endpoint, DefaultEndPoint) self.assertIsNotNone(self.cluster.control_connection._connection.endpoint) endpoints = [host.endpoint for host in hosts] - self.assertIn(self.cluster.control_connection._connection.endpoint, endpoints) + assert self.cluster.control_connection._connection.endpoint in endpoints def test_custom_endpoint(self): cluster = Cluster( @@ -109,6 +109,6 @@ def test_custom_endpoint(self): self.assertIsInstance(cluster.control_connection._connection.endpoint, AddressEndPoint) self.assertIsNotNone(cluster.control_connection._connection.endpoint) endpoints = [host.endpoint for host in hosts] - self.assertIn(cluster.control_connection._connection.endpoint, endpoints) + assert cluster.control_connection._connection.endpoint in endpoints cluster.shutdown() diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 1b74c44bff..304823aea8 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -656,11 +656,11 @@ def test_string_coverage(self): statement = SimpleStatement(query) future = session.execute_async(statement) - self.assertIn(query, str(future)) + assert query in str(future) future.result() - self.assertIn(query, str(future)) - self.assertIn('result', str(future)) + assert query in str(future) + assert 'result' in str(future) cluster.shutdown() def test_can_connect_with_plainauth(self): @@ -759,14 +759,14 @@ def test_idle_heartbeat(self): # holders include session pools and cc holders = cluster.get_connection_holders() - self.assertIn(cluster.control_connection, holders) + assert cluster.control_connection in holders assert len(holders) == len(cluster.metadata.all_hosts()) + 1 # hosts pools, 1 for cc # include additional sessions session2 = cluster.connect(wait_for_all_pools=True) holders = cluster.get_connection_holders() - self.assertIn(cluster.control_connection, holders) + assert cluster.control_connection in holders assert len(holders) == 2 * len(cluster.metadata.all_hosts()) + 1 # 2 sessions' hosts pools, 1 for cc cluster._idle_heartbeat.stop() diff --git a/tests/integration/standard/test_custom_protocol_handler.py b/tests/integration/standard/test_custom_protocol_handler.py index a1c1e61d83..740eac824e 100644 --- a/tests/integration/standard/test_custom_protocol_handler.py +++ b/tests/integration/standard/test_custom_protocol_handler.py @@ -147,8 +147,7 @@ def test_protocol_divergence_v5_fail_by_continuous_paging(self): # This should raise NoHostAvailable because continuous paging is not supported under ProtocolVersion.DSE_V1 with self.assertRaises(NoHostAvailable) as context: future.result() - self.assertIn("Continuous paging may only be used with protocol version ProtocolVersion.DSE_V1 or higher", - str(context.exception)) + assert "Continuous paging may only be used with protocol version ProtocolVersion.DSE_V1 or higher" in str(context.exception) cluster.shutdown() diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 65ffa30a4c..7b34596c3b 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -251,7 +251,7 @@ def test_basic_table_meta_properties(self): ) for option in tablemeta.options: - self.assertIn(option, parser.recognized_table_options) + assert option in parser.recognized_table_options self.check_create_statement(tablemeta, create_statement) @@ -497,14 +497,14 @@ def test_indexes(self): statements = [s.strip() for s in statements.split(';')] statements = list(filter(bool, statements)) assert 3 == len(statements) - self.assertIn(d_index, statements) - self.assertIn(e_index, statements) + assert d_index in statements + assert e_index in statements # make sure indexes are included in KeyspaceMetadata.export_as_string() ksmeta = self.cluster.metadata.keyspaces[self.keyspace_name] statement = ksmeta.export_as_string() - self.assertIn('CREATE INDEX d_index', statement) - self.assertIn('CREATE INDEX e_index', statement) + assert 'CREATE INDEX d_index' in statement + assert 'CREATE INDEX e_index' in statement @greaterthancass21 @requires_collection_indexes @@ -517,7 +517,7 @@ def test_collection_indexes(self): % (self.keyspace_name, self.function_table_name)) tablemeta = self.get_table_metadata() - self.assertIn('(keys(b))', tablemeta.export_as_string()) + assert '(keys(b))' in tablemeta.export_as_string() self.session.execute("DROP INDEX %s.index1" % (self.keyspace_name,)) self.session.execute("CREATE INDEX index2 ON %s.%s (b)" @@ -525,7 +525,7 @@ def test_collection_indexes(self): tablemeta = self.get_table_metadata() target = ' (b)' if CASSANDRA_VERSION < Version("3.0") else 'values(b))' # explicit values in C* 3+ - self.assertIn(target, tablemeta.export_as_string()) + assert target in tablemeta.export_as_string() # test full indexes on frozen collections, if available if CASSANDRA_VERSION >= Version("2.1.3"): @@ -536,7 +536,7 @@ def test_collection_indexes(self): % (self.keyspace_name, self.function_table_name)) tablemeta = self.get_table_metadata() - self.assertIn('(full(b))', tablemeta.export_as_string()) + assert '(full(b))' in tablemeta.export_as_string() def test_compression_disabled(self): create_statement = self.make_create_statement(["a"], ["b"], ["c"]) @@ -546,7 +546,7 @@ def test_compression_disabled(self): expected = "compression = {'enabled': 'false'}" if SCYLLA_VERSION is not None or CASSANDRA_VERSION < Version("3.0"): expected = "compression = {}" - self.assertIn(expected, tablemeta.export_as_string()) + assert expected in tablemeta.export_as_string() def test_non_size_tiered_compaction(self): """ @@ -567,8 +567,8 @@ def test_non_size_tiered_compaction(self): table_meta = self.get_table_metadata() cql = table_meta.export_as_string() - self.assertIn("'tombstone_threshold': '0.3'", cql) - self.assertIn("LeveledCompactionStrategy", cql) + assert "'tombstone_threshold': '0.3'" in cql + assert "LeveledCompactionStrategy" in cql # formerly legacy options; reintroduced in 4.0 if CASSANDRA_VERSION < Version('4.0-a'): self.assertNotIn("min_threshold", cql) @@ -603,7 +603,7 @@ def test_refresh_schema_metadata(self): self.assertNotIn("new_keyspace", cluster2.metadata.keyspaces) cluster2.refresh_schema_metadata() - self.assertIn("new_keyspace", cluster2.metadata.keyspaces) + assert "new_keyspace" in cluster2.metadata.keyspaces # Keyspace metadata modification self.session.execute("ALTER KEYSPACE {0} WITH durable_writes = false".format(self.keyspace_name)) @@ -619,14 +619,14 @@ def test_refresh_schema_metadata(self): self.session.execute("ALTER TABLE {0}.{1} ADD c double".format(self.keyspace_name, table_name)) self.assertNotIn("c", cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns) cluster2.refresh_schema_metadata() - self.assertIn("c", cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns) + assert "c" in cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns if PROTOCOL_VERSION >= 3: # UDT metadata modification self.session.execute("CREATE TYPE {0}.user (age int, name text)".format(self.keyspace_name)) assert cluster2.metadata.keyspaces[self.keyspace_name].user_types == {} cluster2.refresh_schema_metadata() - self.assertIn("user", cluster2.metadata.keyspaces[self.keyspace_name].user_types) + assert "user" in cluster2.metadata.keyspaces[self.keyspace_name].user_types if PROTOCOL_VERSION >= 4: # UDF metadata modification @@ -637,7 +637,7 @@ def test_refresh_schema_metadata(self): assert cluster2.metadata.keyspaces[self.keyspace_name].functions == {} cluster2.refresh_schema_metadata() - self.assertIn("sum_int(int,int)", cluster2.metadata.keyspaces[self.keyspace_name].functions) + assert "sum_int(int,int)" in cluster2.metadata.keyspaces[self.keyspace_name].functions # UDA metadata modification self.session.execute("""CREATE AGGREGATE {0}.sum_agg(int) @@ -648,11 +648,11 @@ def test_refresh_schema_metadata(self): assert cluster2.metadata.keyspaces[self.keyspace_name].aggregates == {} cluster2.refresh_schema_metadata() - self.assertIn("sum_agg(int)", cluster2.metadata.keyspaces[self.keyspace_name].aggregates) + assert "sum_agg(int)" in cluster2.metadata.keyspaces[self.keyspace_name].aggregates # Cluster metadata modification self.session.execute("DROP KEYSPACE new_keyspace") - self.assertIn("new_keyspace", cluster2.metadata.keyspaces) + assert "new_keyspace" in cluster2.metadata.keyspaces cluster2.refresh_schema_metadata() self.assertNotIn("new_keyspace", cluster2.metadata.keyspaces) @@ -715,7 +715,7 @@ def test_refresh_table_metadata(self): self.assertNotIn("c", cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns) cluster2.refresh_table_metadata(self.keyspace_name, table_name) - self.assertIn("c", cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns) + assert "c" in cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns cluster2.shutdown() @@ -752,7 +752,7 @@ def test_refresh_metadata_for_mv(self): self.assertNotIn("mv1", cluster2.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) cluster2.refresh_table_metadata(self.keyspace_name, "mv1") - self.assertIn("mv1", cluster2.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) + assert "mv1" in cluster2.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views finally: cluster2.shutdown() @@ -776,7 +776,7 @@ def test_refresh_metadata_for_mv(self): ) self.assertNotIn("mv2", cluster3.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) cluster3.refresh_materialized_view_metadata(self.keyspace_name, 'mv2') - self.assertIn("mv2", cluster3.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) + assert "mv2" in cluster3.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views finally: cluster3.shutdown() @@ -808,7 +808,7 @@ def test_refresh_user_type_metadata(self): assert cluster2.metadata.keyspaces[self.keyspace_name].user_types == {} cluster2.refresh_user_type_metadata(self.keyspace_name, "user") - self.assertIn("user", cluster2.metadata.keyspaces[self.keyspace_name].user_types) + assert "user" in cluster2.metadata.keyspaces[self.keyspace_name].user_types cluster2.shutdown() @@ -833,15 +833,15 @@ def test_refresh_user_type_metadata_proto_2(self): assert cluster.metadata.keyspaces[self.keyspace_name].user_types == {} session.execute("CREATE TYPE {0}.user (age int, name text)".format(self.keyspace_name)) - self.assertIn("user", cluster.metadata.keyspaces[self.keyspace_name].user_types) - self.assertIn("age", cluster.metadata.keyspaces[self.keyspace_name].user_types["user"].field_names) - self.assertIn("name", cluster.metadata.keyspaces[self.keyspace_name].user_types["user"].field_names) + assert "user" in cluster.metadata.keyspaces[self.keyspace_name].user_types + assert "age" in cluster.metadata.keyspaces[self.keyspace_name].user_types["user"].field_names + assert "name" in cluster.metadata.keyspaces[self.keyspace_name].user_types["user"].field_names session.execute("ALTER TYPE {0}.user ADD flag boolean".format(self.keyspace_name)) - self.assertIn("flag", cluster.metadata.keyspaces[self.keyspace_name].user_types["user"].field_names) + assert "flag" in cluster.metadata.keyspaces[self.keyspace_name].user_types["user"].field_names session.execute("ALTER TYPE {0}.user RENAME flag TO something".format(self.keyspace_name)) - self.assertIn("something", cluster.metadata.keyspaces[self.keyspace_name].user_types["user"].field_names) + assert "something" in cluster.metadata.keyspaces[self.keyspace_name].user_types["user"].field_names session.execute("DROP TYPE {0}.user".format(self.keyspace_name)) assert cluster.metadata.keyspaces[self.keyspace_name].user_types == {} @@ -880,7 +880,7 @@ def test_refresh_user_function_metadata(self): assert cluster2.metadata.keyspaces[self.keyspace_name].functions == {} cluster2.refresh_user_function_metadata(self.keyspace_name, UserFunctionDescriptor("sum_int", ["int", "int"])) - self.assertIn("sum_int(int,int)", cluster2.metadata.keyspaces[self.keyspace_name].functions) + assert "sum_int(int,int)" in cluster2.metadata.keyspaces[self.keyspace_name].functions cluster2.shutdown() @@ -923,7 +923,7 @@ def test_refresh_user_aggregate_metadata(self): assert cluster2.metadata.keyspaces[self.keyspace_name].aggregates == {} cluster2.refresh_user_aggregate_metadata(self.keyspace_name, UserAggregateDescriptor("sum_agg", ["int"])) - self.assertIn("sum_agg(int)", cluster2.metadata.keyspaces[self.keyspace_name].aggregates) + assert "sum_agg(int)" in cluster2.metadata.keyspaces[self.keyspace_name].aggregates cluster2.shutdown() @@ -993,8 +993,8 @@ def after_table_cql(cls, table_meta, ext_key, ext_blob): class Ext1(Ext0): name = t + '##' - self.assertIn(Ext0.name, _RegisteredExtensionType._extension_registry) - self.assertIn(Ext1.name, _RegisteredExtensionType._extension_registry) + assert Ext0.name in _RegisteredExtensionType._extension_registry + assert Ext1.name in _RegisteredExtensionType._extension_registry # There will bee the RLAC extension here. assert len(_RegisteredExtensionType._extension_registry) == 3 @@ -1017,16 +1017,16 @@ class Ext1(Ext0): table_meta = ks_meta.tables[t] view_meta = table_meta.views[v] - self.assertIn(Ext0.name, table_meta.extensions) + assert Ext0.name in table_meta.extensions new_cql = table_meta.export_as_string() self.assertNotEqual(new_cql, original_table_cql) - self.assertIn(Ext0.after_table_cql(table_meta, Ext0.name, ext_map[Ext0.name]), new_cql) + assert Ext0.after_table_cql(table_meta, Ext0.name, ext_map[Ext0.name]) in new_cql self.assertNotIn(Ext1.name, new_cql) - self.assertIn(Ext0.name, view_meta.extensions) + assert Ext0.name in view_meta.extensions new_cql = view_meta.export_as_string() self.assertNotEqual(new_cql, original_view_cql) - self.assertIn(Ext0.after_table_cql(view_meta, Ext0.name, ext_map[Ext0.name]), new_cql) + assert Ext0.after_table_cql(view_meta, Ext0.name, ext_map[Ext0.name]) in new_cql self.assertNotIn(Ext1.name, new_cql) # extensions registered, one present @@ -1040,19 +1040,19 @@ class Ext1(Ext0): table_meta = ks_meta.tables[t] view_meta = table_meta.views[v] - self.assertIn(Ext0.name, table_meta.extensions) - self.assertIn(Ext1.name, table_meta.extensions) + assert Ext0.name in table_meta.extensions + assert Ext1.name in table_meta.extensions new_cql = table_meta.export_as_string() self.assertNotEqual(new_cql, original_table_cql) - self.assertIn(Ext0.after_table_cql(table_meta, Ext0.name, ext_map[Ext0.name]), new_cql) - self.assertIn(Ext1.after_table_cql(table_meta, Ext1.name, ext_map[Ext1.name]), new_cql) + assert Ext0.after_table_cql(table_meta, Ext0.name, ext_map[Ext0.name]) in new_cql + assert Ext1.after_table_cql(table_meta, Ext1.name, ext_map[Ext1.name]) in new_cql - self.assertIn(Ext0.name, view_meta.extensions) - self.assertIn(Ext1.name, view_meta.extensions) + assert Ext0.name in view_meta.extensions + assert Ext1.name in view_meta.extensions new_cql = view_meta.export_as_string() self.assertNotEqual(new_cql, original_view_cql) - self.assertIn(Ext0.after_table_cql(view_meta, Ext0.name, ext_map[Ext0.name]), new_cql) - self.assertIn(Ext1.after_table_cql(view_meta, Ext1.name, ext_map[Ext1.name]), new_cql) + assert Ext0.after_table_cql(view_meta, Ext0.name, ext_map[Ext0.name]) in new_cql + assert Ext1.after_table_cql(view_meta, Ext1.name, ext_map[Ext1.name]) in new_cql def test_metadata_pagination(self): self.cluster.refresh_schema_metadata() @@ -1247,15 +1247,15 @@ def test_case_sensitivity(self): ksmeta = cluster.metadata.keyspaces[ksname] schema = ksmeta.export_as_string() - self.assertIn('CREATE KEYSPACE "AnInterestingKeyspace"', schema) - self.assertIn('CREATE TABLE "AnInterestingKeyspace"."AnInterestingTable"', schema) - self.assertIn('"A" int', schema) - self.assertIn('"B" int', schema) - self.assertIn('"MyColumn" int', schema) - self.assertIn('PRIMARY KEY (k, "A")', schema) - self.assertIn('WITH CLUSTERING ORDER BY ("A" DESC)', schema) - self.assertIn('CREATE INDEX myindex ON "AnInterestingKeyspace"."AnInterestingTable" ("MyColumn")', schema) - self.assertIn('CREATE INDEX "AnotherIndex" ON "AnInterestingKeyspace"."AnInterestingTable" ("B")', schema) + assert 'CREATE KEYSPACE "AnInterestingKeyspace"' in schema + assert 'CREATE TABLE "AnInterestingKeyspace"."AnInterestingTable"' in schema + assert '"A" int' in schema + assert '"B" int' in schema + assert '"MyColumn" int' in schema + assert 'PRIMARY KEY (k, "A")' in schema + assert 'WITH CLUSTERING ORDER BY ("A" DESC)' in schema + assert 'CREATE INDEX myindex ON "AnInterestingKeyspace"."AnInterestingTable" ("MyColumn")' in schema + assert 'CREATE INDEX "AnotherIndex" ON "AnInterestingKeyspace"."AnInterestingTable" ("B")' in schema cluster.shutdown() def test_already_exists_exceptions(self): @@ -2021,7 +2021,7 @@ def test_bad_keyspace(self): self.cluster.refresh_keyspace_metadata(self.keyspace_name) m = self.cluster.metadata.keyspaces[self.keyspace_name] self.assertIs(m._exc_info[0], self.BadMetaException) - self.assertIn("/*\nWarning:", m.export_as_string()) + assert "/*\nWarning:" in m.export_as_string() def test_bad_table(self): self.session.execute('CREATE TABLE %s (k int PRIMARY KEY, v int)' % self.function_name) @@ -2029,7 +2029,7 @@ def test_bad_table(self): self.cluster.refresh_table_metadata(self.keyspace_name, self.function_name) m = self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_name] self.assertIs(m._exc_info[0], self.BadMetaException) - self.assertIn("/*\nWarning:", m.export_as_string()) + assert "/*\nWarning:" in m.export_as_string() def test_bad_index(self): self.session.execute('CREATE TABLE %s (k int PRIMARY KEY, v int)' % self.function_name) @@ -2038,7 +2038,7 @@ def test_bad_index(self): self.cluster.refresh_table_metadata(self.keyspace_name, self.function_name) m = self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_name] self.assertIs(m._exc_info[0], self.BadMetaException) - self.assertIn("/*\nWarning:", m.export_as_string()) + assert "/*\nWarning:" in m.export_as_string() @greaterthancass20 def test_bad_user_type(self): @@ -2047,7 +2047,7 @@ def test_bad_user_type(self): self.cluster.refresh_schema_metadata() # presently do not capture these errors on udt direct refresh -- make sure it's contained during full refresh m = self.cluster.metadata.keyspaces[self.keyspace_name] self.assertIs(m._exc_info[0], self.BadMetaException) - self.assertIn("/*\nWarning:", m.export_as_string()) + assert "/*\nWarning:" in m.export_as_string() @greaterthancass21 @requires_java_udf @@ -2066,7 +2066,7 @@ def test_bad_user_function(self): self.cluster.refresh_schema_metadata() # presently do not capture these errors on udt direct refresh -- make sure it's contained during full refresh m = self.cluster.metadata.keyspaces[self.keyspace_name] self.assertIs(m._exc_info[0], self.BadMetaException) - self.assertIn("/*\nWarning:", m.export_as_string()) + assert "/*\nWarning:" in m.export_as_string() @greaterthancass21 @requires_java_udf @@ -2085,7 +2085,7 @@ def test_bad_user_aggregate(self): self.cluster.refresh_schema_metadata() # presently do not capture these errors on udt direct refresh -- make sure it's contained during full refresh m = self.cluster.metadata.keyspaces[self.keyspace_name] self.assertIs(m._exc_info[0], self.BadMetaException) - self.assertIn("/*\nWarning:", m.export_as_string()) + assert "/*\nWarning:" in m.export_as_string() class DynamicCompositeTypeTest(BasicSharedKeyspaceUnitTestCase): @@ -2112,13 +2112,13 @@ def test_dct_alias(self): # Format can very slightly between versions, strip out whitespace for consistency sake table_text = dct_table.as_cql_query().replace(" ", "") dynamic_type_text = "c1'org.apache.cassandra.db.marshal.DynamicCompositeType(" - self.assertIn("c1'org.apache.cassandra.db.marshal.DynamicCompositeType(", table_text) + assert "c1'org.apache.cassandra.db.marshal.DynamicCompositeType(" in table_text # Types within in the composite can come out in random order, so grab the type definition and find each one type_definition_start = table_text.index("(", table_text.find(dynamic_type_text)) type_definition_end = table_text.index(")") type_definition_text = table_text[type_definition_start:type_definition_end] - self.assertIn("s=>org.apache.cassandra.db.marshal.UTF8Type", type_definition_text) - self.assertIn("i=>org.apache.cassandra.db.marshal.Int32Type", type_definition_text) + assert "s=>org.apache.cassandra.db.marshal.UTF8Type" in type_definition_text + assert "i=>org.apache.cassandra.db.marshal.Int32Type" in type_definition_text @greaterthanorequalcass30 @@ -2153,8 +2153,8 @@ def test_materialized_view_metadata_creation(self): @test_category metadata """ - self.assertIn("mv1", self.cluster.metadata.keyspaces[self.keyspace_name].views) - self.assertIn("mv1", self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) + assert "mv1" in self.cluster.metadata.keyspaces[self.keyspace_name].views + assert "mv1" in self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views assert self.keyspace_name == self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].keyspace_name assert self.function_table_name == self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].base_table_name @@ -2174,10 +2174,10 @@ def test_materialized_view_metadata_alter(self): @test_category metadata """ - self.assertIn("SizeTieredCompactionStrategy", self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].options["compaction"]["class"]) + assert "SizeTieredCompactionStrategy" in self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].options["compaction"]["class"] self.session.execute("ALTER MATERIALIZED VIEW {0}.mv1 WITH compaction = {{ 'class' : 'LeveledCompactionStrategy' }}".format(self.keyspace_name)) - self.assertIn("LeveledCompactionStrategy", self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].options["compaction"]["class"]) + assert "LeveledCompactionStrategy" in self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].options["compaction"]["class"] def test_materialized_view_metadata_drop(self): """ @@ -2367,7 +2367,7 @@ def test_base_table_column_addition_mv(self): assert len(self.cluster.metadata.keyspaces[self.keyspace_name].views) == 2 score_table = self.cluster.metadata.keyspaces[self.keyspace_name].tables['scores'] - self.assertIn("fouls", score_table.columns) + assert "fouls" in score_table.columns # This is a workaround for mv notifications being separate from base table schema responses. # This maybe fixed with future protocol changes @@ -2377,7 +2377,7 @@ def test_base_table_column_addition_mv(self): break time.sleep(.2) - self.assertIn("fouls", mv_alltime.columns) + assert "fouls" in mv_alltime.columns mv_alltime_fouls_comumn = self.cluster.metadata.keyspaces[self.keyspace_name].views["alltimehigh"].columns['fouls'] assert mv_alltime_fouls_comumn.cql_type == 'int' @@ -2558,7 +2558,7 @@ def _assert_group_keys_by_host(self, keys, table_name, stmt): routing_key = prepared_stmt.bind(key).routing_key hosts = self.cluster.metadata.get_replicas(self.ks_name, routing_key) assert 1 == len(hosts) # RF is 1 for this keyspace - self.assertIn(key, keys_per_host[hosts[0]]) + assert key in keys_per_host[hosts[0]] class VirtualKeypaceTest(BasicSharedKeyspaceUnitTestCase): diff --git a/tests/integration/standard/test_prepared_statements.py b/tests/integration/standard/test_prepared_statements.py index 9b8b328cfe..035d37a219 100644 --- a/tests/integration/standard/test_prepared_statements.py +++ b/tests/integration/standard/test_prepared_statements.py @@ -412,7 +412,7 @@ def test_fail_if_different_query_id_on_reprepare(self): self.session.execute("USE {}".format(keyspace)) with self.assertRaises(DriverException) as e: self.session.execute(prepared, [0]) - self.assertIn("ID mismatch", str(e.exception)) + assert "ID mismatch" in str(e.exception) @greaterthanorequalcass40 diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 8ddc191dc9..6d7626fee6 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -100,7 +100,7 @@ def test_row_error_message(self): self.session.execute(ss) with self.assertRaises(DriverException) as context: self.session.execute("SELECT * FROM {0}.{1}".format(self.keyspace_name, self.function_table_name)) - self.assertIn("Failed decoding result column", str(context.exception)) + assert "Failed decoding result column" in str(context.exception) def test_trace_id_to_resultset(self): diff --git a/tests/integration/standard/test_shard_aware.py b/tests/integration/standard/test_shard_aware.py index 0680b0a458..215c69c3b4 100644 --- a/tests/integration/standard/test_shard_aware.py +++ b/tests/integration/standard/test_shard_aware.py @@ -62,8 +62,8 @@ def verify_same_shard_in_tracing(self, results, shard_name): for event in events: LOGGER.info("%s %s %s", event.source, event.thread_name, event.description) for event in events: - self.assertIn(shard_name, event.thread_name) - self.assertIn('querying locally', "\n".join([event.description for event in events])) + assert shard_name in event.thread_name + assert 'querying locally' in "\n".join([event.description for event in events]) trace_id = results.response_future.get_query_trace_ids()[0] traces = self.session.execute("SELECT * FROM system_traces.events WHERE session_id = %s", (trace_id,)) @@ -71,8 +71,8 @@ def verify_same_shard_in_tracing(self, results, shard_name): for event in events: LOGGER.info("%s %s", event.thread, event.activity) for event in events: - self.assertIn(shard_name, event.thread) - self.assertIn('querying locally', "\n".join([event.activity for event in events])) + assert shard_name in event.thread + assert 'querying locally' in "\n".join([event.activity for event in events]) def create_ks_and_cf(self): self.session.execute( diff --git a/tests/integration/standard/test_udts.py b/tests/integration/standard/test_udts.py index 30d1e8068a..6012e5803d 100644 --- a/tests/integration/standard/test_udts.py +++ b/tests/integration/standard/test_udts.py @@ -688,7 +688,7 @@ def test_type_alteration(self): type_name = "type_name" self.assertNotIn(type_name, s.cluster.metadata.keyspaces['udttests'].user_types) s.execute('CREATE TYPE %s (v0 int)' % (type_name,)) - self.assertIn(type_name, s.cluster.metadata.keyspaces['udttests'].user_types) + assert type_name in s.cluster.metadata.keyspaces['udttests'].user_types s.execute('CREATE TABLE %s (k int PRIMARY KEY, v frozen<%s>)' % (self.table_name, type_name)) s.execute('INSERT INTO %s (k, v) VALUES (0, {v0 : 1})' % (self.table_name,)) diff --git a/tests/unit/advanced/test_metadata.py b/tests/unit/advanced/test_metadata.py index a336658b00..0de467e780 100644 --- a/tests/unit/advanced/test_metadata.py +++ b/tests/unit/advanced/test_metadata.py @@ -59,14 +59,8 @@ def test_keyspace_with_graph_engine(self): km = self._create_keyspace_metadata(graph_engine) assert km.graph_engine == graph_engine cql = km.as_cql_query() - self.assertIn( - "graph_engine", - cql - ) - self.assertIn( - "Core", - cql - ) + assert "graph_engine" in cql + assert "Core" in cql def test_table_no_vertex_or_edge(self): tm = self._create_table_metadata() @@ -81,7 +75,7 @@ def test_table_with_vertex(self): self.assertIsInstance(tm.vertex, VertexMetadata) assert tm.edge is None cql = tm.as_cql_query() - self.assertIn("VERTEX LABEL", cql) + assert "VERTEX LABEL" in cql self.assertNotIn("EDGE LABEL", cql) def test_table_with_edge(self): @@ -90,9 +84,9 @@ def test_table_with_edge(self): self.assertIsInstance(tm.edge, EdgeMetadata) cql = tm.as_cql_query() self.assertNotIn("VERTEX LABEL", cql) - self.assertIn("EDGE LABEL", cql) - self.assertIn("FROM from_label", cql) - self.assertIn("TO to_label", cql) + assert "EDGE LABEL" in cql + assert "FROM from_label" in cql + assert "TO to_label" in cql def test_vertex_with_label(self): tm = self. _create_table_metadata(with_vertex=True) @@ -100,43 +94,28 @@ def test_vertex_with_label(self): def test_edge_single_partition_key_and_clustering_key(self): tm = self._create_table_metadata(with_edge=True) - self.assertIn( - 'FROM from_label(pk1, c1)', - tm.as_cql_query() - ) + assert 'FROM from_label(pk1, c1)' in tm.as_cql_query() def test_edge_multiple_partition_keys(self): edge = self._create_edge_metadata(partition_keys=['pk1', 'pk2']) tm = self. _create_table_metadata(with_edge=edge) - self.assertIn( - 'FROM from_label((pk1, pk2), ', - tm.as_cql_query() - ) + assert 'FROM from_label((pk1, pk2), ' in tm.as_cql_query() def test_edge_no_clustering_keys(self): edge = self._create_edge_metadata(clustering_keys=[]) tm = self. _create_table_metadata(with_edge=edge) - self.assertIn( - 'FROM from_label(pk1) ', - tm.as_cql_query() - ) + assert 'FROM from_label(pk1) ' in tm.as_cql_query() def test_edge_multiple_clustering_keys(self): edge = self._create_edge_metadata(clustering_keys=['c1', 'c2']) tm = self. _create_table_metadata(with_edge=edge) - self.assertIn( - 'FROM from_label(pk1, c1, c2) ', - tm.as_cql_query() - ) + assert 'FROM from_label(pk1, c1, c2) ' in tm.as_cql_query() def test_edge_multiple_partition_and_clustering_keys(self): edge = self._create_edge_metadata(partition_keys=['pk1', 'pk2'], clustering_keys=['c1', 'c2']) tm = self. _create_table_metadata(with_edge=edge) - self.assertIn( - 'FROM from_label((pk1, pk2), c1, c2) ', - tm.as_cql_query() - ) + assert 'FROM from_label((pk1, pk2), c1, c2) ' in tm.as_cql_query() class SchemaParsersTests(unittest.TestCase): diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index de2668129c..aab27f207c 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -493,8 +493,8 @@ def _check_warning_on_no_lbp_with_contact_points(self, cluster_kwargs): Cluster(**cluster_kwargs) patched_logger.warning.assert_called_once() warning_message = patched_logger.warning.call_args[0][0] - self.assertIn('please specify a load-balancing policy', warning_message) - self.assertIn("contact_points = ['127.0.0.1']", warning_message) + assert 'please specify a load-balancing policy' in warning_message + assert "contact_points = ['127.0.0.1']" in warning_message def test_no_warning_on_contact_points_with_lbp_legacy_mode(self): """ @@ -562,9 +562,9 @@ def test_warning_adding_no_lbp_ep_to_cluster_with_contact_points(self): patched_logger.warning.assert_called_once() warning_message = patched_logger.warning.call_args[0][0] - self.assertIn('no_lbp', warning_message) - self.assertIn('trying to add', warning_message) - self.assertIn('please specify a load-balancing policy', warning_message) + assert 'no_lbp' in warning_message + assert 'trying to add' in warning_message + assert 'please specify a load-balancing policy' in warning_message @mock_session_pools def test_no_warning_adding_lbp_ep_to_cluster_with_contact_points(self): diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 854f9b66f4..4c9587061b 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -130,7 +130,7 @@ def test_transient_replication_parsing(self): simple_transient = rs.create('SimpleStrategy', {'replication_factor': '3/1'}) assert simple_transient.replication_factor_info == ReplicationFactor(3, 1) assert simple_transient.replication_factor == 2 - self.assertIn("'replication_factor': '3/1'", simple_transient.export_for_schema()) + assert "'replication_factor': '3/1'" in simple_transient.export_for_schema() simple_str = rs.create('SimpleStrategy', {'replication_factor': '2'}) self.assertNotEqual(simple_transient, simple_str) @@ -171,7 +171,7 @@ def test_nts_transient_parsing(self): assert nts_transient.dc_replication_factors_info['dc2'] == ReplicationFactor(5, 1) assert nts_transient.dc_replication_factors['dc1'] == 2 assert nts_transient.dc_replication_factors['dc2'] == 4 - self.assertIn("'dc1': '3/1', 'dc2': '5/1'", nts_transient.export_for_schema()) + assert "'dc1': '3/1', 'dc2': '5/1'" in nts_transient.export_for_schema() nts_str = rs.create('NetworkTopologyStrategy', {'dc1': '3', 'dc2': '5'}) self.assertNotEqual(nts_transient, nts_str) @@ -723,28 +723,16 @@ def test_monotonic_all(self): monotonic=True, monotonic_on=() ) - self.assertIn( - 'MONOTONIC LANG', - mono_function.as_cql_query(formatted=False) - ) - self.assertIn( - 'MONOTONIC\n LANG', - mono_function.as_cql_query(formatted=True) - ) + assert 'MONOTONIC LANG' in mono_function.as_cql_query(formatted=False) + assert 'MONOTONIC\n LANG' in mono_function.as_cql_query(formatted=True) def test_monotonic_one(self): mono_on_function = self._function_with_kwargs( monotonic=False, monotonic_on=('x',) ) - self.assertIn( - 'MONOTONIC ON x LANG', - mono_on_function.as_cql_query(formatted=False) - ) - self.assertIn( - 'MONOTONIC ON x\n LANG', - mono_on_function.as_cql_query(formatted=True) - ) + assert 'MONOTONIC ON x LANG' in mono_on_function.as_cql_query(formatted=False) + assert 'MONOTONIC ON x\n LANG' in mono_on_function.as_cql_query(formatted=True) def test_nondeterministic(self): self.assertNotIn( @@ -755,18 +743,12 @@ def test_nondeterministic(self): ) def test_deterministic(self): - self.assertIn( - 'DETERMINISTIC', - self._function_with_kwargs( - deterministic=True - ).as_cql_query(formatted=False) - ) - self.assertIn( - 'DETERMINISTIC\n', - self._function_with_kwargs( - deterministic=True - ).as_cql_query(formatted=True) - ) + assert 'DETERMINISTIC' in self._function_with_kwargs( + deterministic=True + ).as_cql_query(formatted=False) + assert 'DETERMINISTIC\n' in self._function_with_kwargs( + deterministic=True + ).as_cql_query(formatted=True) class AggregateToCQLTests(unittest.TestCase): diff --git a/tests/unit/test_parameter_binding.py b/tests/unit/test_parameter_binding.py index 1230b54786..7827e354d2 100644 --- a/tests/unit/test_parameter_binding.py +++ b/tests/unit/test_parameter_binding.py @@ -50,7 +50,7 @@ def test_list_collection(self): def test_set_collection(self): result = bind_params("%s", (set(['a', 'b']),), Encoder()) - self.assertIn(result, ("{'a', 'b'}", "{'b', 'a'}")) + assert result in ("{'a', 'b'}", "{'b', 'a'}") def test_map_collection(self): vals = OrderedDict() @@ -92,9 +92,9 @@ def test_invalid_argument_type(self): try: self.bound.bind(values) except TypeError as e: - self.assertIn('v0', str(e)) - self.assertIn('Int32Type', str(e)) - self.assertIn('str', str(e)) + assert 'v0' in str(e) + assert 'Int32Type' in str(e) + assert 'str' in str(e) else: self.fail('Passed invalid type but exception was not thrown') @@ -103,9 +103,9 @@ def test_invalid_argument_type(self): try: self.bound.bind(values) except TypeError as e: - self.assertIn('rk0', str(e)) - self.assertIn('Int32Type', str(e)) - self.assertIn('list', str(e)) + assert 'rk0' in str(e) + assert 'Int32Type' in str(e) + assert 'list' in str(e) else: self.fail('Passed invalid type but exception was not thrown') diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index 0ad08d9a0e..8476673c6d 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -631,7 +631,7 @@ def get_replicas(keyspace, packed_key): replicas = get_replicas(None, struct.pack('>i', i)) # first should be the only local replica - self.assertIn(qplan[0], replicas) + assert qplan[0] in replicas assert qplan[0].datacenter == "dc1" # then the local non-replica diff --git a/tests/unit/test_resultset.py b/tests/unit/test_resultset.py index 20c2b68efd..e72891c277 100644 --- a/tests/unit/test_resultset.py +++ b/tests/unit/test_resultset.py @@ -220,6 +220,5 @@ def test_indexing_deprecation(self, mocked_warn): assert rs[0] == first assert len(mocked_warn.mock_calls) == 1 index_warning_args = tuple(mocked_warn.mock_calls[0])[1] - self.assertIn('indexing support will be removed in 4.0', - str(index_warning_args[0])) + assert 'indexing support will be removed in 4.0' in str(index_warning_args[0]) self.assertIs(index_warning_args[1], DeprecationWarning) diff --git a/tests/unit/test_row_factories.py b/tests/unit/test_row_factories.py index 8d41291095..158a2feabf 100644 --- a/tests/unit/test_row_factories.py +++ b/tests/unit/test_row_factories.py @@ -63,8 +63,8 @@ def test_creation_warning_on_long_column_list(self): rows = named_tuple_factory(self.long_colnames, self.long_rows) assert len(w) == 1 warning = w[0] - self.assertIn('pseudo_namedtuple_factory', str(warning)) - self.assertIn('3.7', str(warning)) + assert 'pseudo_namedtuple_factory' in str(warning) + assert '3.7' in str(warning) for r in rows: assert r.col0 == self.long_rows[0][0] diff --git a/tests/unit/test_sortedset.py b/tests/unit/test_sortedset.py index 887a046b5d..c27b36dd5a 100644 --- a/tests/unit/test_sortedset.py +++ b/tests/unit/test_sortedset.py @@ -376,7 +376,7 @@ def _test_uncomparable_types(self, items): assert ss.difference(subset) == s.difference(subset) assert ss.intersection(subset) == s.intersection(subset) for x in ss: - self.assertIn(x, ss) + assert x in ss ss.remove(x) self.assertNotIn(x, ss) From dc2f13d8c9e41700695f20eb35ae32f8a144d887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 19:48:26 +0200 Subject: [PATCH 045/298] Replace self.assertIsInstance with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used command: ``` ast-grep run --pattern 'self.assertIsInstance($A, $B)' --rewrite 'assert isinstance($A, $B)' --lang python ./tests -U ``` --- .../columns/test_container_columns.py | 56 +++++++++---------- .../cqlengine/columns/test_validation.py | 6 +- .../cqlengine/model/test_model_io.py | 8 +-- .../integration/cqlengine/model/test_udts.py | 2 +- .../integration/cqlengine/query/test_named.py | 8 +-- .../cqlengine/query/test_queryset.py | 8 +-- .../integration/simulacron/test_connection.py | 4 +- tests/integration/simulacron/test_endpoint.py | 8 +-- tests/integration/simulacron/test_policies.py | 2 +- tests/integration/standard/test_cluster.py | 4 +- tests/integration/standard/test_concurrent.py | 4 +- .../standard/test_cython_protocol_handlers.py | 2 +- tests/integration/standard/test_metadata.py | 26 ++++----- .../standard/test_prepared_statements.py | 38 ++++++------- tests/integration/standard/test_query.py | 14 ++--- .../standard/test_row_factories.py | 12 ++-- tests/unit/advanced/test_graph.py | 6 +- tests/unit/advanced/test_metadata.py | 4 +- tests/unit/io/utils.py | 8 +-- tests/unit/test_concurrent.py | 2 +- tests/unit/test_connection.py | 12 ++-- tests/unit/test_metadata.py | 10 ++-- tests/unit/test_policies.py | 2 +- tests/unit/test_response_future.py | 6 +- tests/unit/test_row_factories.py | 2 +- tests/unit/test_types.py | 2 +- 26 files changed, 128 insertions(+), 128 deletions(-) diff --git a/tests/integration/cqlengine/columns/test_container_columns.py b/tests/integration/cqlengine/columns/test_container_columns.py index 92dfda0791..ed599d4594 100644 --- a/tests/integration/cqlengine/columns/test_container_columns.py +++ b/tests/integration/cqlengine/columns/test_container_columns.py @@ -113,8 +113,8 @@ def test_io_success(self): m1 = TestSetModel.create(int_set=set((1, 2)), text_set=set(('kai', 'andreas'))) m2 = TestSetModel.get(partition=m1.partition) - self.assertIsInstance(m2.int_set, set) - self.assertIsInstance(m2.text_set, set) + assert isinstance(m2.int_set, set) + assert isinstance(m2.text_set, set) assert 1 in m2.int_set assert 2 in m2.int_set @@ -164,14 +164,14 @@ def test_instantiation_with_column_class(self): and that the class is instantiated in the constructor """ column = columns.Set(columns.Text) - self.assertIsInstance(column.value_col, columns.Text) + assert isinstance(column.value_col, columns.Text) def test_instantiation_with_column_instance(self): """ Tests that columns instantiated with a column instance work properly """ column = columns.Set(columns.Text(min_length=100)) - self.assertIsInstance(column.value_col, columns.Text) + assert isinstance(column.value_col, columns.Text) def test_to_python(self): """ Tests that to_python of value column is called """ @@ -227,8 +227,8 @@ def test_io_success(self): m1 = TestListModel.create(int_list=[1, 2], text_list=['kai', 'andreas']) m2 = TestListModel.get(partition=m1.partition) - self.assertIsInstance(m2.int_list, list) - self.assertIsInstance(m2.text_list, list) + assert isinstance(m2.int_list, list) + assert isinstance(m2.text_list, list) assert len(m2.int_list) == 2 assert len(m2.text_list) == 2 @@ -283,14 +283,14 @@ def test_instantiation_with_column_class(self): and that the class is instantiated in the constructor """ column = columns.List(columns.Text) - self.assertIsInstance(column.value_col, columns.Text) + assert isinstance(column.value_col, columns.Text) def test_instantiation_with_column_instance(self): """ Tests that columns instantiated with a column instance work properly """ column = columns.List(columns.Text(min_length=100)) - self.assertIsInstance(column.value_col, columns.Text) + assert isinstance(column.value_col, columns.Text) def test_to_python(self): """ Tests that to_python of value column is called """ @@ -496,16 +496,16 @@ def test_instantiation_with_column_class(self): and that the class is instantiated in the constructor """ column = columns.Map(columns.Text, columns.Integer) - self.assertIsInstance(column.key_col, columns.Text) - self.assertIsInstance(column.value_col, columns.Integer) + assert isinstance(column.key_col, columns.Text) + assert isinstance(column.value_col, columns.Integer) def test_instantiation_with_column_instance(self): """ Tests that columns instantiated with a column instance work properly """ column = columns.Map(columns.Text(min_length=100), columns.Integer()) - self.assertIsInstance(column.key_col, columns.Text) - self.assertIsInstance(column.value_col, columns.Integer) + assert isinstance(column.key_col, columns.Text) + assert isinstance(column.value_col, columns.Integer) def test_to_python(self): """ Tests that to_python of value column is called """ @@ -617,9 +617,9 @@ def test_io_success(self): m1 = TestTupleModel.create(int_tuple=(1, 2, 3, 5, 6), text_tuple=('kai', 'andreas'), mixed_tuple=('first', 2, 'Third')) m2 = TestTupleModel.get(partition=m1.partition) - self.assertIsInstance(m2.int_tuple, tuple) - self.assertIsInstance(m2.text_tuple, tuple) - self.assertIsInstance(m2.mixed_tuple, tuple) + assert isinstance(m2.int_tuple, tuple) + assert isinstance(m2.text_tuple, tuple) + assert isinstance(m2.mixed_tuple, tuple) assert (1, 2, 3) == m2.int_tuple assert ('kai', 'andreas') == m2.text_tuple @@ -651,9 +651,9 @@ def test_instantiation_with_column_class(self): @test_category object_mapper """ mixed_tuple = columns.Tuple(columns.Text, columns.Integer, columns.Text, required=False) - self.assertIsInstance(mixed_tuple.types[0], columns.Text) - self.assertIsInstance(mixed_tuple.types[1], columns.Integer) - self.assertIsInstance(mixed_tuple.types[2], columns.Text) + assert isinstance(mixed_tuple.types[0], columns.Text) + assert isinstance(mixed_tuple.types[1], columns.Integer) + assert isinstance(mixed_tuple.types[2], columns.Text) assert len(mixed_tuple.types) == 3 def test_default_empty_container_saving(self): @@ -824,15 +824,15 @@ def test_io_success(self): m1 = TestNestedModel.create(list_list=list_list_master, map_list=map_list_master, set_tuple=set_tuple_master) m2 = TestNestedModel.get(partition=m1.partition) - self.assertIsInstance(m2.list_list, list) - self.assertIsInstance(m2.list_list[0], list) - self.assertIsInstance(m2.map_list, dict) - self.assertIsInstance(m2.map_list.get("key2"), list) + assert isinstance(m2.list_list, list) + assert isinstance(m2.list_list[0], list) + assert isinstance(m2.map_list, dict) + assert isinstance(m2.map_list.get("key2"), list) assert list_list_master == m2.list_list assert map_list_master == m2.map_list assert set_tuple_master == m2.set_tuple - self.assertIsInstance(m2.set_tuple.pop(), tuple) + assert isinstance(m2.set_tuple.pop(), tuple) def test_type_validation(self): """ @@ -875,11 +875,11 @@ def test_instantiation_with_column_class(self): map_list = columns.Map(columns.Text, columns.List(columns.Text), required=False) set_tuple = columns.Set(columns.Tuple(columns.Integer, columns.Integer), required=False) - self.assertIsInstance(list_list, columns.List) - self.assertIsInstance(list_list.types[0], columns.List) - self.assertIsInstance(map_list.types[0], columns.Text) - self.assertIsInstance(map_list.types[1], columns.List) - self.assertIsInstance(set_tuple.types[0], columns.Tuple) + assert isinstance(list_list, columns.List) + assert isinstance(list_list.types[0], columns.List) + assert isinstance(map_list.types[0], columns.Text) + assert isinstance(map_list.types[1], columns.List) + assert isinstance(set_tuple.types[0], columns.Tuple) def test_default_empty_container_saving(self): """ diff --git a/tests/integration/cqlengine/columns/test_validation.py b/tests/integration/cqlengine/columns/test_validation.py index 0f167c6758..f86923ca66 100644 --- a/tests/integration/cqlengine/columns/test_validation.py +++ b/tests/integration/cqlengine/columns/test_validation.py @@ -234,15 +234,15 @@ def _check_value_is_correct_in_db(self, value): value_to_compare = value result = self.model_class.objects(test_id=0).first() - self.assertIsInstance(result.class_param, self.python_klass) + assert isinstance(result.class_param, self.python_klass) assert result.class_param == value_to_compare result = self.model_class.objects.all().allow_filtering().filter(test_id=0).first() - self.assertIsInstance(result.class_param, self.python_klass) + assert isinstance(result.class_param, self.python_klass) assert result.class_param == value_to_compare result = self.model_class.objects.all().allow_filtering().filter(test_id=0, class_param=value).first() - self.assertIsInstance(result.class_param, self.python_klass) + assert isinstance(result.class_param, self.python_klass) assert result.class_param == value_to_compare return result diff --git a/tests/integration/cqlengine/model/test_model_io.py b/tests/integration/cqlengine/model/test_model_io.py index c376889bc1..e34475cd85 100644 --- a/tests/integration/cqlengine/model/test_model_io.py +++ b/tests/integration/cqlengine/model/test_model_io.py @@ -73,10 +73,10 @@ def test_model_save_and_load(self): Tests that models can be saved and retrieved, using the create method. """ tm = TestModel.create(count=8, text='123456789') - self.assertIsInstance(tm, TestModel) + assert isinstance(tm, TestModel) tm2 = TestModel.objects(id=tm.pk).first() - self.assertIsInstance(tm2, TestModel) + assert isinstance(tm2, TestModel) for cname in tm._columns.keys(): assert getattr(tm, cname) == getattr(tm2, cname) @@ -150,7 +150,7 @@ def test_column_deleting_works_properly(self): tm.save() tm2 = TestModel.objects(id=tm.pk).first() - self.assertIsInstance(tm2, TestModel) + assert isinstance(tm2, TestModel) self.assertTrue(tm2.text is None) self.assertTrue(tm2._values['text'].previous_value is None) @@ -506,7 +506,7 @@ class TestDefaultValueTracking(Model): assert instance.int2 == 456 assert instance.int3 == 7777 self.assertIsNotNone(instance.int4) - self.assertIsInstance(instance.int4, int) + assert isinstance(instance.int4, int) self.assertGreaterEqual(instance.int4, 0) self.assertLessEqual(instance.int4, 1000) assert instance.int5 == 5555 diff --git a/tests/integration/cqlengine/model/test_udts.py b/tests/integration/cqlengine/model/test_udts.py index a70c685ccd..71c89dd58b 100644 --- a/tests/integration/cqlengine/model/test_udts.py +++ b/tests/integration/cqlengine/model/test_udts.py @@ -458,7 +458,7 @@ class TheModel(Model): john = TheModel.objects.first() assert john.id == id info = john.info - self.assertIsInstance(info, db_field_different) + assert isinstance(info, db_field_different) assert info.age == age assert info.name == name # also excercise the db_Field mapping diff --git a/tests/integration/cqlengine/query/test_named.py b/tests/integration/cqlengine/query/test_named.py index 09550e607e..8c4deba96e 100644 --- a/tests/integration/cqlengine/query/test_named.py +++ b/tests/integration/cqlengine/query/test_named.py @@ -87,12 +87,12 @@ def test_filter_method_where_clause_generation(self): where = query2._where[0] assert where.field == 'test_id' - self.assertIsInstance(where.operator, EqualsOperator) + assert isinstance(where.operator, EqualsOperator) assert where.value == 5 where = query2._where[1] assert where.field == 'expected_result' - self.assertIsInstance(where.operator, GreaterThanOrEqualOperator) + assert isinstance(where.operator, GreaterThanOrEqualOperator) assert where.value == 1 def test_query_expression_where_clause_generation(self): @@ -110,12 +110,12 @@ def test_query_expression_where_clause_generation(self): where = query2._where[0] assert where.field == 'test_id' - self.assertIsInstance(where.operator, EqualsOperator) + assert isinstance(where.operator, EqualsOperator) assert where.value == 5 where = query2._where[1] assert where.field == 'expected_result' - self.assertIsInstance(where.operator, GreaterThanOrEqualOperator) + assert isinstance(where.operator, GreaterThanOrEqualOperator) assert where.value == 1 @requires_collection_indexes diff --git a/tests/integration/cqlengine/query/test_queryset.py b/tests/integration/cqlengine/query/test_queryset.py index 84115ba835..b5a24413d6 100644 --- a/tests/integration/cqlengine/query/test_queryset.py +++ b/tests/integration/cqlengine/query/test_queryset.py @@ -131,8 +131,8 @@ def test_query_filter_parsing(self): assert len(query2._where) == 2 op = query2._where[1] - self.assertIsInstance(op, statements.WhereClause) - self.assertIsInstance(op.operator, operators.GreaterThanOrEqualOperator) + assert isinstance(op, statements.WhereClause) + assert isinstance(op.operator, operators.GreaterThanOrEqualOperator) assert op.value == 1 def test_query_expression_parsing(self): @@ -149,8 +149,8 @@ def test_query_expression_parsing(self): assert len(query2._where) == 2 op = query2._where[1] - self.assertIsInstance(op, statements.WhereClause) - self.assertIsInstance(op.operator, operators.GreaterThanOrEqualOperator) + assert isinstance(op, statements.WhereClause) + assert isinstance(op.operator, operators.GreaterThanOrEqualOperator) assert op.value == 1 def test_using_invalid_column_names_in_filter_kwargs_raises_error(self): diff --git a/tests/integration/simulacron/test_connection.py b/tests/integration/simulacron/test_connection.py index 2c30146e9a..3cefad9709 100644 --- a/tests/integration/simulacron/test_connection.py +++ b/tests/integration/simulacron/test_connection.py @@ -141,7 +141,7 @@ def test_heart_beat_timeout(self): for f in futures: f._event.wait() - self.assertIsInstance(f._final_exception, OperationTimedOut) + assert isinstance(f._final_exception, OperationTimedOut) prime_request(PrimeOptions(then=NO_THEN)) @@ -418,7 +418,7 @@ def test_host_is_not_set_to_down_after_query_oto(self): for f in futures: f._event.wait() - self.assertIsInstance(f._final_exception, OperationTimedOut) + assert isinstance(f._final_exception, OperationTimedOut) assert listener.hosts_marked_down == [] assert_quiescent_pool_state(self, cluster) diff --git a/tests/integration/simulacron/test_endpoint.py b/tests/integration/simulacron/test_endpoint.py index 4aee0016a4..d2059b6172 100644 --- a/tests/integration/simulacron/test_endpoint.py +++ b/tests/integration/simulacron/test_endpoint.py @@ -79,11 +79,11 @@ def test_default_endpoint(self): assert len(hosts) == 3 for host in hosts: self.assertIsNotNone(host.endpoint) - self.assertIsInstance(host.endpoint, DefaultEndPoint) + assert isinstance(host.endpoint, DefaultEndPoint) assert host.address == host.endpoint.address assert host.broadcast_rpc_address == host.endpoint.address - self.assertIsInstance(self.cluster.control_connection._connection.endpoint, DefaultEndPoint) + assert isinstance(self.cluster.control_connection._connection.endpoint, DefaultEndPoint) self.assertIsNotNone(self.cluster.control_connection._connection.endpoint) endpoints = [host.endpoint for host in hosts] assert self.cluster.control_connection._connection.endpoint in endpoints @@ -101,12 +101,12 @@ def test_custom_endpoint(self): assert len(hosts) == 3 for host in hosts: self.assertIsNotNone(host.endpoint) - self.assertIsInstance(host.endpoint, AddressEndPoint) + assert isinstance(host.endpoint, AddressEndPoint) assert str(host.endpoint) == host.endpoint.address assert host.address == host.endpoint.address assert host.broadcast_rpc_address == host.endpoint.address - self.assertIsInstance(cluster.control_connection._connection.endpoint, AddressEndPoint) + assert isinstance(cluster.control_connection._connection.endpoint, AddressEndPoint) self.assertIsNotNone(cluster.control_connection._connection.endpoint) endpoints = [host.endpoint for host in hosts] assert cluster.control_connection._connection.endpoint in endpoints diff --git a/tests/integration/simulacron/test_policies.py b/tests/integration/simulacron/test_policies.py index e24ea8aef7..617047831e 100644 --- a/tests/integration/simulacron/test_policies.py +++ b/tests/integration/simulacron/test_policies.py @@ -162,7 +162,7 @@ def test_speculative_and_timeout(self): response_future = self.session.execute_async(statement, execution_profile='spec_ep_brr_lim', timeout=14) response_future._event.wait(16) - self.assertIsInstance(response_future._final_exception, OperationTimedOut) + assert isinstance(response_future._final_exception, OperationTimedOut) # This is because 14 / 4 + 1 = 4 assert len(response_future.attempted_hosts) == 4 diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 304823aea8..601ca592ee 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -862,7 +862,7 @@ def test_profile_load_balancing(self): # use a copied instance and override the row factory # assert last returned value can be accessed as a namedtuple so we can prove something different named_tuple_row = rs.one() - self.assertIsInstance(named_tuple_row, tuple) + assert isinstance(named_tuple_row, tuple) self.assertTrue(named_tuple_row.release_version) tmp_profile = copy(node1) @@ -873,7 +873,7 @@ def test_profile_load_balancing(self): queried_hosts.add(rs.response_future._current_host) assert queried_hosts == expected_hosts tuple_row = rs.one() - self.assertIsInstance(tuple_row, tuple) + assert isinstance(tuple_row, tuple) with self.assertRaises(AttributeError): tuple_row.release_version diff --git a/tests/integration/standard/test_concurrent.py b/tests/integration/standard/test_concurrent.py index 031065df63..bd7e9460e5 100644 --- a/tests/integration/standard/test_concurrent.py +++ b/tests/integration/standard/test_concurrent.py @@ -291,7 +291,7 @@ def test_no_raise_on_first_failure(self): for i, (success, result) in enumerate(results): if i == 57: self.assertFalse(success) - self.assertIsInstance(result, InvalidRequest) + assert isinstance(result, InvalidRequest) else: self.assertTrue(success) self.assertFalse(result) @@ -310,7 +310,7 @@ def test_no_raise_on_first_failure_client_side(self): for i, (success, result) in enumerate(results): if i == 57: self.assertFalse(success) - self.assertIsInstance(result, TypeError) + assert isinstance(result, TypeError) else: self.assertTrue(success) self.assertFalse(result) diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index 62a8a4ad87..533c19b249 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -111,7 +111,7 @@ def test_numpy_results_paged(self): self.assertTrue(results.has_more_pages) for count, page in enumerate(results, 1): - self.assertIsInstance(page, dict) + assert isinstance(page, dict) for colname, arr in page.items(): if count <= expected_pages: self.assertGreater(len(arr), 0, "page count: %d" % (count,)) diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 7b34596c3b..164e09c0e0 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -1100,7 +1100,7 @@ def test_export_schema(self): cluster = TestCluster() cluster.connect() - self.assertIsInstance(cluster.metadata.export_schema_as_string(), str) + assert isinstance(cluster.metadata.export_schema_as_string(), str) cluster.shutdown() def test_export_keyspace_schema(self): @@ -1113,8 +1113,8 @@ def test_export_keyspace_schema(self): for keyspace in cluster.metadata.keyspaces: keyspace_metadata = cluster.metadata.keyspaces[keyspace] - self.assertIsInstance(keyspace_metadata.export_as_string(), str) - self.assertIsInstance(keyspace_metadata.as_cql_query(), str) + assert isinstance(keyspace_metadata.export_as_string(), str) + assert isinstance(keyspace_metadata.as_cql_query(), str) cluster.shutdown() def assert_equal_diff(self, received, expected): @@ -1465,10 +1465,10 @@ def test_index_updates(self): ks_meta = self.cluster.metadata.keyspaces[self.keyspace_name] table_meta = ks_meta.tables[self.table_name] - self.assertIsInstance(ks_meta.indexes['a_idx'], IndexMetadata) - self.assertIsInstance(ks_meta.indexes['b_idx'], IndexMetadata) - self.assertIsInstance(table_meta.indexes['a_idx'], IndexMetadata) - self.assertIsInstance(table_meta.indexes['b_idx'], IndexMetadata) + assert isinstance(ks_meta.indexes['a_idx'], IndexMetadata) + assert isinstance(ks_meta.indexes['b_idx'], IndexMetadata) + assert isinstance(table_meta.indexes['a_idx'], IndexMetadata) + assert isinstance(table_meta.indexes['b_idx'], IndexMetadata) # both indexes updated when index dropped self.session.execute("DROP INDEX a_idx") @@ -1479,9 +1479,9 @@ def test_index_updates(self): ks_meta = self.cluster.metadata.keyspaces[self.keyspace_name] table_meta = ks_meta.tables[self.table_name] self.assertNotIn('a_idx', ks_meta.indexes) - self.assertIsInstance(ks_meta.indexes['b_idx'], IndexMetadata) + assert isinstance(ks_meta.indexes['b_idx'], IndexMetadata) self.assertNotIn('a_idx', table_meta.indexes) - self.assertIsInstance(table_meta.indexes['b_idx'], IndexMetadata) + assert isinstance(table_meta.indexes['b_idx'], IndexMetadata) # keyspace index updated when table dropped self.drop_basic_table() @@ -1497,15 +1497,15 @@ def test_index_follows_alter(self): self.session.execute("CREATE INDEX %s ON %s (a)" % (idx, self.table_name)) ks_meta = self.cluster.metadata.keyspaces[self.keyspace_name] table_meta = ks_meta.tables[self.table_name] - self.assertIsInstance(ks_meta.indexes[idx], IndexMetadata) - self.assertIsInstance(table_meta.indexes[idx], IndexMetadata) + assert isinstance(ks_meta.indexes[idx], IndexMetadata) + assert isinstance(table_meta.indexes[idx], IndexMetadata) self.session.execute('ALTER KEYSPACE %s WITH durable_writes = false' % self.keyspace_name) old_meta = ks_meta ks_meta = self.cluster.metadata.keyspaces[self.keyspace_name] self.assertIsNot(ks_meta, old_meta) table_meta = ks_meta.tables[self.table_name] - self.assertIsInstance(ks_meta.indexes[idx], IndexMetadata) - self.assertIsInstance(table_meta.indexes[idx], IndexMetadata) + assert isinstance(ks_meta.indexes[idx], IndexMetadata) + assert isinstance(table_meta.indexes[idx], IndexMetadata) self.drop_basic_table() @requires_java_udf diff --git a/tests/integration/standard/test_prepared_statements.py b/tests/integration/standard/test_prepared_statements.py index 035d37a219..53abf26fd8 100644 --- a/tests/integration/standard/test_prepared_statements.py +++ b/tests/integration/standard/test_prepared_statements.py @@ -80,7 +80,7 @@ def test_basic(self): INSERT INTO cf0 (a, b, c) VALUES (?, ?, ?) """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind(('a', 'b', 'c')) self.session.execute(bound) @@ -89,7 +89,7 @@ def test_basic(self): """ SELECT * FROM cf0 WHERE a=? """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind(('a')) results = self.session.execute(bound) @@ -101,7 +101,7 @@ def test_basic(self): INSERT INTO cf0 (a, b, c) VALUES (?, ?, ?) """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind({ 'a': 'x', 'b': 'y', @@ -115,7 +115,7 @@ def test_basic(self): SELECT * FROM cf0 WHERE a=? """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind({'a': 'x'}) results = self.session.execute(bound) @@ -136,7 +136,7 @@ def _run_missing_primary_key(self, session): self.assertRaises(InvalidRequest, session.prepare, statement_to_prepare) else: prepared = session.prepare(statement_to_prepare) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind((1,)) self.assertRaises(InvalidRequest, session.execute, bound) @@ -155,7 +155,7 @@ def _run_missing_primary_key_dicts(self, session): self.assertRaises(InvalidRequest, session.prepare, statement_to_prepare) else: prepared = session.prepare(statement_to_prepare) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind({'v': 1}) self.assertRaises(InvalidRequest, session.execute, bound) @@ -172,7 +172,7 @@ def _run_too_many_bind_values(self, session): self.assertRaises(InvalidRequest, session.prepare, statement_to_prepare) else: prepared = session.prepare(statement_to_prepare) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) self.assertRaises(ValueError, prepared.bind, (1, 2)) def test_imprecise_bind_values_dicts(self): @@ -186,7 +186,7 @@ def test_imprecise_bind_values_dicts(self): INSERT INTO test3rf.test (k, v) VALUES (?, ?) """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) # too many values is ok - others are ignored prepared.bind({'k': 1, 'v': 2, 'v2': 3}) @@ -200,7 +200,7 @@ def test_imprecise_bind_values_dicts(self): prepared.bind({'k': 1, 'v2': 3}) # also catch too few variables with dicts - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) if PROTOCOL_VERSION < 4: self.assertRaises(KeyError, prepared.bind, {}) else: @@ -217,7 +217,7 @@ def test_none_values(self): INSERT INTO test3rf.test (k, v) VALUES (?, ?) """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind((1, None)) self.session.execute(bound) @@ -225,7 +225,7 @@ def test_none_values(self): """ SELECT * FROM test3rf.test WHERE k=? """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind((1,)) results = self.session.execute(bound) @@ -283,7 +283,7 @@ def test_no_meta(self): INSERT INTO test3rf.test (k, v) VALUES (0, 0) """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind(None) bound.consistency_level = ConsistencyLevel.ALL self.session.execute(bound) @@ -292,7 +292,7 @@ def test_no_meta(self): """ SELECT * FROM test3rf.test WHERE k=0 """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind(None) bound.consistency_level = ConsistencyLevel.ALL @@ -310,7 +310,7 @@ def test_none_values_dicts(self): INSERT INTO test3rf.test (k, v) VALUES (?, ?) """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind({'k': 1, 'v': None}) self.session.execute(bound) @@ -318,7 +318,7 @@ def test_none_values_dicts(self): """ SELECT * FROM test3rf.test WHERE k=? """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind({'k': 1}) results = self.session.execute(bound) @@ -334,7 +334,7 @@ def test_async_binding(self): INSERT INTO test3rf.test (k, v) VALUES (?, ?) """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) future = self.session.execute_async(prepared, (873, None)) future.result() @@ -342,7 +342,7 @@ def test_async_binding(self): """ SELECT * FROM test3rf.test WHERE k=? """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) future = self.session.execute_async(prepared, (873,)) results = future.result() @@ -357,7 +357,7 @@ def test_async_binding_dicts(self): INSERT INTO test3rf.test (k, v) VALUES (?, ?) """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) future = self.session.execute_async(prepared, {'k': 873, 'v': None}) future.result() @@ -365,7 +365,7 @@ def test_async_binding_dicts(self): """ SELECT * FROM test3rf.test WHERE k=? """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) future = self.session.execute_async(prepared, {'k': 873}) results = future.result() diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 6d7626fee6..0a5f0b351f 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -63,9 +63,9 @@ def test_query(self): INSERT INTO test3rf.test (k, v) VALUES (?, ?) """.format(self.keyspace_name)) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind((1, None)) - self.assertIsInstance(bound, BoundStatement) + assert isinstance(bound, BoundStatement) assert 2 == len(bound.values) self.session.execute(bound) assert bound.routing_key == b'\x00\x00\x00\x01' @@ -379,7 +379,7 @@ def test_routing_key(self): INSERT INTO test3rf.test (k, v) VALUES (?, ?) """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind((1, None)) assert bound.routing_key == b'\x00\x00\x00\x01' @@ -394,7 +394,7 @@ def test_empty_routing_key_indexes(self): """) prepared.routing_key_indexes = None - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind((1, None)) assert bound.routing_key == None @@ -408,7 +408,7 @@ def test_predefined_routing_key(self): INSERT INTO test3rf.test (k, v) VALUES (?, ?) """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind((1, None)) bound._set_routing_key('fake_key') assert bound.routing_key == 'fake_key' @@ -421,7 +421,7 @@ def test_multiple_routing_key_indexes(self): """ INSERT INTO test3rf.test (k, v) VALUES (?, ?) """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) prepared.routing_key_indexes = [0, 1] bound = prepared.bind((1, 2)) @@ -440,7 +440,7 @@ def test_bound_keyspace(self): INSERT INTO test3rf.test (k, v) VALUES (?, ?) """) - self.assertIsInstance(prepared, PreparedStatement) + assert isinstance(prepared, PreparedStatement) bound = prepared.bind((1, 2)) assert bound.keyspace == 'test3rf' diff --git a/tests/integration/standard/test_row_factories.py b/tests/integration/standard/test_row_factories.py index 44b33733bd..e0f882b43c 100644 --- a/tests/integration/standard/test_row_factories.py +++ b/tests/integration/standard/test_row_factories.py @@ -92,8 +92,8 @@ def _results_from_row_factory(self, row_factory): def test_tuple_factory(self): result = self._results_from_row_factory(tuple_factory) - self.assertIsInstance(result, ResultSet) - self.assertIsInstance(result.one(), tuple) + assert isinstance(result, ResultSet) + assert isinstance(result.one(), tuple) result = result.all() for row in result: @@ -106,7 +106,7 @@ def test_tuple_factory(self): def test_named_tuple_factory(self): result = self._results_from_row_factory(named_tuple_factory) - self.assertIsInstance(result, ResultSet) + assert isinstance(result, ResultSet) result = result.all() for row in result: @@ -119,8 +119,8 @@ def test_named_tuple_factory(self): def _test_dict_factory(self, row_factory, row_type): result = self._results_from_row_factory(row_factory) - self.assertIsInstance(result, ResultSet) - self.assertIsInstance(result.one(), row_type) + assert isinstance(result, ResultSet) + assert isinstance(result.one(), row_type) result = result.all() for row in result: @@ -164,7 +164,7 @@ def _gen_row_factory(rows): ( 1 , 1 ) '''.format(self.keyspace_name, self.function_table_name)) result = session.execute(self.select) - self.assertIsInstance(result, ResultSet) + assert isinstance(result, ResultSet) first_row = result.one() assert first_row[0] == first_row[1] diff --git a/tests/unit/advanced/test_graph.py b/tests/unit/advanced/test_graph.py index 7388301b9a..b52c37ef57 100644 --- a/tests/unit/advanced/test_graph.py +++ b/tests/unit/advanced/test_graph.py @@ -172,8 +172,8 @@ def test_as_path(self): assert path.labels == path_dict['labels'] # make sure inner objects are bound correctly - self.assertIsInstance(path.objects[0], Vertex) - self.assertIsInstance(path.objects[1], Edge) + assert isinstance(path.objects[0], Vertex) + assert isinstance(path.objects[1], Edge) # missing required properties for attr in path_dict: @@ -403,5 +403,5 @@ def test_graph_result_row_factory(self): rows = [json.dumps({'result': i}) for i in range(10)] results = graph_result_row_factory(col_names, ((o,) for o in rows)) for i, res in enumerate(results): - self.assertIsInstance(res, Result) + assert isinstance(res, Result) assert res.value == i diff --git a/tests/unit/advanced/test_metadata.py b/tests/unit/advanced/test_metadata.py index 0de467e780..056cc61d6f 100644 --- a/tests/unit/advanced/test_metadata.py +++ b/tests/unit/advanced/test_metadata.py @@ -72,7 +72,7 @@ def test_table_no_vertex_or_edge(self): def test_table_with_vertex(self): tm = self._create_table_metadata(with_vertex=True) - self.assertIsInstance(tm.vertex, VertexMetadata) + assert isinstance(tm.vertex, VertexMetadata) assert tm.edge is None cql = tm.as_cql_query() assert "VERTEX LABEL" in cql @@ -81,7 +81,7 @@ def test_table_with_vertex(self): def test_table_with_edge(self): tm = self._create_table_metadata(with_edge=True) assert tm.vertex is None - self.assertIsInstance(tm.edge, EdgeMetadata) + assert isinstance(tm.edge, EdgeMetadata) cql = tm.as_cql_query() self.assertNotIn("VERTEX LABEL", cql) assert "EDGE LABEL" in cql diff --git a/tests/unit/io/utils.py b/tests/unit/io/utils.py index 34d676dd9f..6ae620e365 100644 --- a/tests/unit/io/utils.py +++ b/tests/unit/io/utils.py @@ -331,7 +331,7 @@ def test_protocol_error(self): # make sure it errored correctly self.assertTrue(c.is_defunct) self.assertTrue(c.connected_event.is_set()) - self.assertIsInstance(c.last_error, ProtocolError) + assert isinstance(c.last_error, ProtocolError) def test_error_message_on_startup(self): c = self.make_connection() @@ -355,7 +355,7 @@ def test_error_message_on_startup(self): # make sure it errored correctly self.assertTrue(c.is_defunct) - self.assertIsInstance(c.last_error, ConnectionException) + assert isinstance(c.last_error, ConnectionException) self.assertTrue(c.connected_event.is_set()) def test_socket_error_on_write(self): @@ -367,7 +367,7 @@ def test_socket_error_on_write(self): # make sure it errored correctly self.assertTrue(c.is_defunct) - self.assertIsInstance(c.last_error, socket_error) + assert isinstance(c.last_error, socket_error) self.assertTrue(c.connected_event.is_set()) def test_blocking_on_write(self): @@ -416,7 +416,7 @@ def test_socket_error_on_read(self): # make sure it errored correctly self.assertTrue(c.is_defunct) - self.assertIsInstance(c.last_error, socket_error) + assert isinstance(c.last_error, socket_error) self.assertTrue(c.connected_event.is_set()) def test_partial_header_read(self): diff --git a/tests/unit/test_concurrent.py b/tests/unit/test_concurrent.py index e3f313e249..a4d2bdf2a9 100644 --- a/tests/unit/test_concurrent.py +++ b/tests/unit/test_concurrent.py @@ -254,4 +254,4 @@ def test_recursion_limited(self): assert len(results) == max_recursion for r in results: self.assertFalse(r[0]) - self.assertIsInstance(r[1], TypeError) + assert isinstance(r[1], TypeError) diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 8df7609bfc..b944743330 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -95,7 +95,7 @@ def test_bad_protocol_version(self, *args): # make sure it errored correctly c.defunct.assert_called_once_with(ANY) args, kwargs = c.defunct.call_args - self.assertIsInstance(args[0], ProtocolError) + assert isinstance(args[0], ProtocolError) def test_negative_body_length(self, *args): c = self.make_connection() @@ -112,7 +112,7 @@ def test_negative_body_length(self, *args): # make sure it errored correctly c.defunct.assert_called_once_with(ANY) args, kwargs = c.defunct.call_args - self.assertIsInstance(args[0], ProtocolError) + assert isinstance(args[0], ProtocolError) def test_unsupported_cql_version(self, *args): c = self.make_connection() @@ -132,7 +132,7 @@ def test_unsupported_cql_version(self, *args): # make sure it errored correctly c.defunct.assert_called_once_with(ANY) args, kwargs = c.defunct.call_args - self.assertIsInstance(args[0], ProtocolError) + assert isinstance(args[0], ProtocolError) def test_prefer_lz4_compression(self, *args): c = self.make_connection() @@ -182,7 +182,7 @@ def test_requested_compression_not_available(self, *args): # make sure it errored correctly c.defunct.assert_called_once_with(ANY) args, kwargs = c.defunct.call_args - self.assertIsInstance(args[0], ProtocolError) + assert isinstance(args[0], ProtocolError) def test_use_requested_compression(self, *args): c = self.make_connection() @@ -382,7 +382,7 @@ def send_msg(msg, req_id, msg_callback): connection.send_msg.assert_has_calls([call(ANY, request_id, ANY)] * get_holders.call_count) connection.defunct.assert_has_calls([call(ANY)] * get_holders.call_count) exc = connection.defunct.call_args_list[0][0][0] - self.assertIsInstance(exc, ConnectionException) + assert isinstance(exc, ConnectionException) self.assertRegex(exc.args[0], r'^Received unexpected response to OptionsMessage.*') holder.return_connection.assert_has_calls( [call(connection)] * get_holders.call_count) @@ -412,7 +412,7 @@ def send_msg(msg, req_id, msg_callback): connection.send_msg.assert_has_calls([call(ANY, request_id, ANY)] * get_holders.call_count) connection.defunct.assert_has_calls([call(ANY)] * get_holders.call_count) exc = connection.defunct.call_args_list[0][0][0] - self.assertIsInstance(exc, OperationTimedOut) + assert isinstance(exc, OperationTimedOut) assert exc.errors == 'Connection heartbeat timeout after 0.05 seconds' assert exc.last_host == DefaultEndPoint('localhost') holder.return_connection.assert_has_calls( diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 4c9587061b..12eeabe324 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -89,17 +89,17 @@ def test_replication_strategy(self): assert uks.make_token_replica_map({}, []) == {} fake_options_map = {'dc1': '3'} - self.assertIsInstance(rs.create('NetworkTopologyStrategy', fake_options_map), NetworkTopologyStrategy) + assert isinstance(rs.create('NetworkTopologyStrategy', fake_options_map), NetworkTopologyStrategy) assert rs.create('NetworkTopologyStrategy', fake_options_map).dc_replication_factors == NetworkTopologyStrategy(fake_options_map).dc_replication_factors fake_options_map = {'options': 'map'} assert rs.create('SimpleStrategy', fake_options_map) is None fake_options_map = {'options': 'map'} - self.assertIsInstance(rs.create('LocalStrategy', fake_options_map), LocalStrategy) + assert isinstance(rs.create('LocalStrategy', fake_options_map), LocalStrategy) fake_options_map = {'options': 'map', 'replication_factor': 3} - self.assertIsInstance(rs.create('SimpleStrategy', fake_options_map), SimpleStrategy) + assert isinstance(rs.create('SimpleStrategy', fake_options_map), SimpleStrategy) assert rs.create('SimpleStrategy', fake_options_map).replication_factor == SimpleStrategy(fake_options_map).replication_factor assert rs.create('xxxxxxxx', fake_options_map) == _UnknownStrategy('xxxxxxxx', fake_options_map) @@ -498,8 +498,8 @@ def test_from_string(self): from_unicode = BytesToken.from_string('0123456789abcdef') from_bin = BytesToken.from_string(b'0123456789abcdef') assert from_unicode == from_bin - self.assertIsInstance(from_unicode.value, bytes) - self.assertIsInstance(from_bin.value, bytes) + assert isinstance(from_unicode.value, bytes) + assert isinstance(from_bin.value, bytes) def test_comparison(self): tok = BytesToken.from_string('0123456789abcdef') diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index 8476673c6d..ca2e4f6462 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -1357,7 +1357,7 @@ def setUp(self): def _check_init(self, hfp): self.assertIs(hfp._child_policy, self.child_policy) - self.assertIsInstance(hfp._hosts_lock, LockType) + assert isinstance(hfp._hosts_lock, LockType) # we can't use a simple assertIs because we wrap the function arg0, arg1 = Mock(name='arg0'), Mock(name='arg1') diff --git a/tests/unit/test_response_future.py b/tests/unit/test_response_future.py index 7f1f14553f..089a9f12cd 100644 --- a/tests/unit/test_response_future.py +++ b/tests/unit/test_response_future.py @@ -216,14 +216,14 @@ def test_request_error_with_prepare_message(self): result = Mock(spec=OverloadedErrorMessage) result.to_exception.return_value = result rf._set_result(None, None, None, result) - self.assertIsInstance(rf._final_exception, OverloadedErrorMessage) + assert isinstance(rf._final_exception, OverloadedErrorMessage) rf = ResponseFuture(session, message, query, 1, retry_policy=retry_policy) rf._query_retries = 1 rf.send_request() result = Mock(spec=ConnectionException) rf._set_result(None, None, None, result) - self.assertIsInstance(rf._final_exception, ConnectionException) + assert isinstance(rf._final_exception, ConnectionException) def test_retry_policy_says_ignore(self): session = self.make_session() @@ -585,7 +585,7 @@ def test_prepared_query_not_found(self): self.assertTrue(session.submit.call_args) args, kwargs = session.submit.call_args assert rf._reprepare == args[-5] - self.assertIsInstance(args[-4], PrepareMessage) + assert isinstance(args[-4], PrepareMessage) assert args[-4].query == "SELECT * FROM foobar" def test_prepared_query_not_found_bad_keyspace(self): diff --git a/tests/unit/test_row_factories.py b/tests/unit/test_row_factories.py index 158a2feabf..d7d68b5b53 100644 --- a/tests/unit/test_row_factories.py +++ b/tests/unit/test_row_factories.py @@ -84,4 +84,4 @@ def test_creation_no_warning_on_short_column_list(self): assert len(w) == 0 # check that this is a real namedtuple self.assertTrue(hasattr(rows[0], '_fields')) - self.assertIsInstance(rows[0], tuple) + assert isinstance(rows[0], tuple) diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index 44f81a24b0..189781055c 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -182,7 +182,7 @@ class BarType(FooType): assert UTF8Type == ctype.subtypes[0] # middle subtype should be a BarType instance with its own subtypes and names - self.assertIsInstance(ctype.subtypes[1], BarType) + assert isinstance(ctype.subtypes[1], BarType) assert [UTF8Type] == ctype.subtypes[1].subtypes assert [b"address"] == ctype.subtypes[1].names From fd1edd4617028a5c11f853942790ee2ef613ebbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 19:50:37 +0200 Subject: [PATCH 046/298] Replace self.assertNotEqual with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used commands: ``` ast-grep run --pattern 'self.assertNotEqual($A, $B)' --rewrite 'assert $A != $B' --lang python ./tests -U ast-grep run --pattern 'self.assertNotEqual($A, $B, $C)' --rewrite 'assert $A != $B, $C' --lang python ./tests -U ``` --- .../columns/test_container_columns.py | 4 +-- .../model/test_class_construction.py | 2 +- .../integration/cqlengine/model/test_model.py | 6 ++--- .../cqlengine/model/test_model_io.py | 2 +- .../integration/cqlengine/test_consistency.py | 2 +- .../cqlengine/test_context_query.py | 2 +- tests/integration/standard/test_cluster.py | 6 ++--- tests/integration/standard/test_connection.py | 2 +- .../standard/test_control_connection.py | 2 +- tests/integration/standard/test_metadata.py | 26 +++++++++---------- tests/integration/standard/test_metrics.py | 4 +-- .../standard/test_prepared_statements.py | 6 ++--- tests/integration/standard/test_query.py | 4 +-- tests/integration/standard/test_udts.py | 2 +- tests/integration/upgrade/test_upgrade.py | 2 +- tests/unit/cqlengine/test_columns.py | 4 +-- tests/unit/test_cluster.py | 18 ++++++------- tests/unit/test_connection.py | 12 +++------ tests/unit/test_endpoints.py | 15 +++-------- tests/unit/test_host_connection_pool.py | 4 +-- tests/unit/test_metadata.py | 10 +++---- tests/unit/test_orderedmap.py | 12 ++++----- tests/unit/test_policies.py | 3 +-- tests/unit/test_sortedset.py | 12 ++++----- tests/unit/test_time_util.py | 4 +-- tests/unit/test_util_types.py | 2 +- 26 files changed, 75 insertions(+), 93 deletions(-) diff --git a/tests/integration/cqlengine/columns/test_container_columns.py b/tests/integration/cqlengine/columns/test_container_columns.py index ed599d4594..83c8706ebf 100644 --- a/tests/integration/cqlengine/columns/test_container_columns.py +++ b/tests/integration/cqlengine/columns/test_container_columns.py @@ -464,7 +464,7 @@ def test_updates_from_none(self): m2.int_map = None m2.save() m3 = TestMapModel.get(partition=m.partition) - self.assertNotEqual(m3.int_map, expected) + assert m3.int_map != expected def test_blind_updates_from_none(self): """ Tests that updates from None work as expected """ @@ -479,7 +479,7 @@ def test_blind_updates_from_none(self): TestMapModel.objects(partition=m.partition).update(int_map={}) m3 = TestMapModel.get(partition=m.partition) - self.assertNotEqual(m3.int_map, expected) + assert m3.int_map != expected def test_updates_to_none(self): """ Tests that setting the field to None works as expected """ diff --git a/tests/integration/cqlengine/model/test_class_construction.py b/tests/integration/cqlengine/model/test_class_construction.py index a6dd530182..2cec745a09 100644 --- a/tests/integration/cqlengine/model/test_class_construction.py +++ b/tests/integration/cqlengine/model/test_class_construction.py @@ -129,7 +129,7 @@ class Stuff(Model): inst1 = Stuff(num=5) inst2 = Stuff(num=7) - self.assertNotEqual(inst1.num, inst2.num) + assert inst1.num != inst2.num assert inst1.num == 5 assert inst2.num == 7 diff --git a/tests/integration/cqlengine/model/test_model.py b/tests/integration/cqlengine/model/test_model.py index 48217b052c..b8f9d3cd92 100644 --- a/tests/integration/cqlengine/model/test_model.py +++ b/tests/integration/cqlengine/model/test_model.py @@ -36,7 +36,7 @@ class EqualityModel(Model): m1 = EqualityModel(pk=1) assert m0 == m0 - self.assertNotEqual(m0, m1) + assert m0 != m1 def test_model_equality(self): """ tests the model equality functionality """ @@ -52,7 +52,7 @@ class EqualityModel1(Model): m1 = EqualityModel1(kk=1) assert m0 == m0 - self.assertNotEqual(m0, m1) + assert m0 != m1 def test_keywords_as_names(self): """ @@ -217,7 +217,7 @@ def test_comparison(self): TestQueryUpdateModel.text_map.column] assert l == sorted(l) - self.assertNotEqual(TestQueryUpdateModel.partition.column, TestQueryUpdateModel.cluster.column) + assert TestQueryUpdateModel.partition.column != TestQueryUpdateModel.cluster.column self.assertLessEqual(TestQueryUpdateModel.partition.column, TestQueryUpdateModel.cluster.column) self.assertGreater(TestQueryUpdateModel.cluster.column, TestQueryUpdateModel.partition.column) self.assertGreaterEqual(TestQueryUpdateModel.cluster.column, TestQueryUpdateModel.partition.column) diff --git a/tests/integration/cqlengine/model/test_model_io.py b/tests/integration/cqlengine/model/test_model_io.py index e34475cd85..e3a5995415 100644 --- a/tests/integration/cqlengine/model/test_model_io.py +++ b/tests/integration/cqlengine/model/test_model_io.py @@ -772,7 +772,7 @@ def test_routing_key_is_ignored(self): mrk = BasicModelNoRouting._routing_key_from_values([1], self.session.cluster.protocol_version) simple = SimpleStatement("") simple.routing_key = mrk - self.assertNotEqual(bound.routing_key, simple.routing_key) + assert bound.routing_key != simple.routing_key # Verify that basic create, update and delete work with no routing key t = BasicModelNoRouting.create(k=2, v=3) diff --git a/tests/integration/cqlengine/test_consistency.py b/tests/integration/cqlengine/test_consistency.py index 6bd095706b..21408267cb 100644 --- a/tests/integration/cqlengine/test_consistency.py +++ b/tests/integration/cqlengine/test_consistency.py @@ -84,7 +84,7 @@ def test_batch_consistency(self): TestConsistencyModel.batch(b).create(text="monkey") args = m.call_args - self.assertNotEqual(CL.ALL, args[0][0].consistency_level) + assert CL.ALL != args[0][0].consistency_level def test_blind_update(self): t = TestConsistencyModel.create(text="bacon and eggs") diff --git a/tests/integration/cqlengine/test_context_query.py b/tests/integration/cqlengine/test_context_query.py index fe4999c013..7d3c2512ce 100644 --- a/tests/integration/cqlengine/test_context_query.py +++ b/tests/integration/cqlengine/test_context_query.py @@ -139,7 +139,7 @@ def test_context_multiple_models(self): with ContextQuery(TestModel, TestModel, keyspace='ks4') as (tm1, tm2): - self.assertNotEqual(tm1, tm2) + assert tm1 != tm2 assert tm1.__keyspace__ == 'ks4' assert tm2.__keyspace__ == 'ks4' diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 601ca592ee..efac079ff8 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -886,9 +886,7 @@ def test_setting_lbp_legacy(self): cluster.load_balancing_policy = RoundRobinPolicy() assert list(cluster.load_balancing_policy.make_query_plan()) == [] cluster.connect() - self.assertNotEqual( - list(cluster.load_balancing_policy.make_query_plan()), [] - ) + assert list(cluster.load_balancing_policy.make_query_plan()) != [] def test_profile_lb_swap(self): """ @@ -967,7 +965,7 @@ def test_clone_shared_lbp(self): rr1_queried_hosts.add(rs.response_future._current_host) rs = session.execute(query, execution_profile='rr1_clone') rr1_clone_queried_hosts.add(rs.response_future._current_host) - self.assertNotEqual(rr1_clone_queried_hosts, rr1_queried_hosts) + assert rr1_clone_queried_hosts != rr1_queried_hosts def test_missing_exec_prof(self): """ diff --git a/tests/integration/standard/test_connection.py b/tests/integration/standard/test_connection.py index 7b34c3dd72..de7ef712ee 100644 --- a/tests/integration/standard/test_connection.py +++ b/tests/integration/standard/test_connection.py @@ -131,7 +131,7 @@ def test_heart_beat_timeout(self): host = "127.0.0.1:9042" node = get_node(1) initial_connections = self.fetch_connections(host, self.cluster) - self.assertNotEqual(len(initial_connections), 0) + assert len(initial_connections) != 0 self.cluster.register_listener(test_listener) # Pause the node try: diff --git a/tests/integration/standard/test_control_connection.py b/tests/integration/standard/test_control_connection.py index f2ab5a9b18..32cc468e64 100644 --- a/tests/integration/standard/test_control_connection.py +++ b/tests/integration/standard/test_control_connection.py @@ -98,7 +98,7 @@ def test_get_control_connection_host(self): # reconnect and make sure that the new host is reflected correctly self.cluster.control_connection._reconnect() new_host = self.cluster.get_control_connection_host() - self.assertNotEqual(host, new_host) + assert host != new_host # TODO: enable after https://github.com/scylladb/python-driver/issues/121 is fixed @unittest.skip('Fails on scylla due to the broadcast_rpc_port is None') diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 164e09c0e0..3860c88bde 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -134,7 +134,7 @@ def test_bad_contact_point(self): # verify the un-existing host was filtered for host in self.cluster.metadata.all_hosts(): - self.assertNotEqual(host.endpoint.address, '126.0.0.186') + assert host.endpoint.address != '126.0.0.186' class SchemaMetadataTests(BasicSegregatedKeyspaceUnitTestCase): @@ -222,7 +222,7 @@ def test_basic_table_meta_properties(self): self.cluster.refresh_schema_metadata() meta = self.cluster.metadata - self.assertNotEqual(meta.cluster_name, None) + assert meta.cluster_name != None self.assertTrue(self.keyspace_name in meta.keyspaces) ksmeta = meta.keyspaces[self.keyspace_name] @@ -1019,13 +1019,13 @@ class Ext1(Ext0): assert Ext0.name in table_meta.extensions new_cql = table_meta.export_as_string() - self.assertNotEqual(new_cql, original_table_cql) + assert new_cql != original_table_cql assert Ext0.after_table_cql(table_meta, Ext0.name, ext_map[Ext0.name]) in new_cql self.assertNotIn(Ext1.name, new_cql) assert Ext0.name in view_meta.extensions new_cql = view_meta.export_as_string() - self.assertNotEqual(new_cql, original_view_cql) + assert new_cql != original_view_cql assert Ext0.after_table_cql(view_meta, Ext0.name, ext_map[Ext0.name]) in new_cql self.assertNotIn(Ext1.name, new_cql) @@ -1043,14 +1043,14 @@ class Ext1(Ext0): assert Ext0.name in table_meta.extensions assert Ext1.name in table_meta.extensions new_cql = table_meta.export_as_string() - self.assertNotEqual(new_cql, original_table_cql) + assert new_cql != original_table_cql assert Ext0.after_table_cql(table_meta, Ext0.name, ext_map[Ext0.name]) in new_cql assert Ext1.after_table_cql(table_meta, Ext1.name, ext_map[Ext1.name]) in new_cql assert Ext0.name in view_meta.extensions assert Ext1.name in view_meta.extensions new_cql = view_meta.export_as_string() - self.assertNotEqual(new_cql, original_view_cql) + assert new_cql != original_view_cql assert Ext0.after_table_cql(view_meta, Ext0.name, ext_map[Ext0.name]) in new_cql assert Ext1.after_table_cql(view_meta, Ext1.name, ext_map[Ext1.name]) in new_cql @@ -1295,7 +1295,7 @@ def test_replicas(self): cluster.connect('test3rf') - self.assertNotEqual(list(cluster.metadata.get_replicas('test3rf', b'key')), []) + assert list(cluster.metadata.get_replicas('test3rf', b'key')) != [] host = list(cluster.metadata.get_replicas('test3rf', b'key'))[0] assert host.datacenter == 'dc1' assert host.rack == 'r1' @@ -1313,7 +1313,7 @@ def test_token_map(self): get_replicas = cluster.metadata.token_map.get_replicas for ksname in ('test1rf', 'test2rf', 'test3rf'): - self.assertNotEqual(list(get_replicas(ksname, ring[0])), []) + assert list(get_replicas(ksname, ring[0])) != [] for i, token in enumerate(ring): assert set(get_replicas('test3rf', token)) == set(owners) @@ -1406,7 +1406,7 @@ def test_keyspace_alter(self): self.session.execute('ALTER KEYSPACE %s WITH durable_writes = false' % name) new_keyspace_meta = self.cluster.metadata.keyspaces[name] - self.assertNotEqual(original_keyspace_meta, new_keyspace_meta) + assert original_keyspace_meta != new_keyspace_meta assert new_keyspace_meta.durable_writes == False @@ -1659,7 +1659,7 @@ def test_function_same_name_diff_types(self): with self.VerifiedFunction(self, **kwargs): functions = [f for f in self.keyspace_function_meta.values() if f.name == self.function_name] assert len(functions) == 2 - self.assertNotEqual(functions[0].argument_types, functions[1].argument_types) + assert functions[0].argument_types != functions[1].argument_types def test_function_no_parameters(self): """ @@ -1704,7 +1704,7 @@ def test_functions_follow_keyspace_alter(self): # After keyspace alter ensure that we maintain function equality. try: new_keyspace_meta = self.cluster.metadata.keyspaces[self.keyspace_name] - self.assertNotEqual(original_keyspace_meta, new_keyspace_meta) + assert original_keyspace_meta != new_keyspace_meta self.assertIs(original_keyspace_meta.functions, new_keyspace_meta.functions) finally: self.session.execute('ALTER KEYSPACE %s WITH durable_writes = true' % self.keyspace_name) @@ -1889,7 +1889,7 @@ def test_same_name_diff_types(self): with self.VerifiedAggregate(self, **kwargs): aggregates = [a for a in self.keyspace_aggregate_meta.values() if a.name == kwargs['name']] assert len(aggregates) == 2 - self.assertNotEqual(aggregates[0].argument_types, aggregates[1].argument_types) + assert aggregates[0].argument_types != aggregates[1].argument_types def test_aggregates_follow_keyspace_alter(self): """ @@ -1910,7 +1910,7 @@ def test_aggregates_follow_keyspace_alter(self): self.session.execute('ALTER KEYSPACE %s WITH durable_writes = false' % self.keyspace_name) try: new_keyspace_meta = self.cluster.metadata.keyspaces[self.keyspace_name] - self.assertNotEqual(original_keyspace_meta, new_keyspace_meta) + assert original_keyspace_meta != new_keyspace_meta self.assertIs(original_keyspace_meta.aggregates, new_keyspace_meta.aggregates) finally: self.session.execute('ALTER KEYSPACE %s WITH durable_writes = true' % self.keyspace_name) diff --git a/tests/integration/standard/test_metrics.py b/tests/integration/standard/test_metrics.py index 972a073aa0..72c5f15ff2 100644 --- a/tests/integration/standard/test_metrics.py +++ b/tests/integration/standard/test_metrics.py @@ -233,11 +233,11 @@ def test_metrics_per_cluster(self): assert 0 == cluster2.metrics.stats.write_timeouts # Test direct access to a child stats - self.assertNotEqual(0.0, self.cluster.metrics.request_timer['mean']) + assert 0.0 != self.cluster.metrics.request_timer['mean'] assert 0.0 == cluster2.metrics.request_timer['mean'] # Test access via metrics.get_stats() - self.assertNotEqual(0.0, stats_cluster1['request_timer']['mean']) + assert 0.0 != stats_cluster1['request_timer']['mean'] assert 0.0 == stats_cluster2['request_timer']['mean'] # Test access by stats_name diff --git a/tests/integration/standard/test_prepared_statements.py b/tests/integration/standard/test_prepared_statements.py index 53abf26fd8..e2117ca6c8 100644 --- a/tests/integration/standard/test_prepared_statements.py +++ b/tests/integration/standard/test_prepared_statements.py @@ -476,7 +476,7 @@ def test_prepared_id_is_update(self): id_after = prepared_statement.result_metadata_id - self.assertNotEqual(id_before, id_after) + assert id_before != id_after assert len(prepared_statement.result_metadata) == 4 def test_prepared_id_is_updated_across_pages(self): @@ -506,7 +506,7 @@ def test_prepared_id_is_updated_across_pages(self): id_after = prepared_statement.result_metadata_id assert result_set == expected_result_set - self.assertNotEqual(id_before, id_after) + assert id_before != id_after assert len(prepared_statement.result_metadata) == 4 def test_prepare_id_is_updated_across_session(self): @@ -532,7 +532,7 @@ def test_prepare_id_is_updated_across_session(self): one_session.execute(one_prepared_stm, (1, )) one_id_after = one_prepared_stm.result_metadata_id - self.assertNotEqual(one_id_before, one_id_after) + assert one_id_before != one_id_after assert len(one_prepared_stm.result_metadata) == 4 def test_not_reprepare_invalid_statements(self): diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 0a5f0b351f..3d63fe25ba 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -492,7 +492,7 @@ def test_prepared_metadata_generation(self): if proto_version == 1: assert select_statement.result_metadata == None else: - self.assertNotEqual(select_statement.result_metadata, None) + assert select_statement.result_metadata != None future = session.execute_async(select_statement) results = future.result() if base_line is None: @@ -1537,7 +1537,7 @@ def test_prepared_with_keyspace_explicit(self): prepared_statement_alternative = self.session.prepare(query, keyspace=self.alternative_ks) - self.assertNotEqual(prepared_statement.query_id, prepared_statement_alternative.query_id) + assert prepared_statement.query_id != prepared_statement_alternative.query_id results = self.session.execute(prepared_statement_alternative, (2,)) assert results.one() == (2, 2) diff --git a/tests/integration/standard/test_udts.py b/tests/integration/standard/test_udts.py index 6012e5803d..e047ab7480 100644 --- a/tests/integration/standard/test_udts.py +++ b/tests/integration/standard/test_udts.py @@ -675,7 +675,7 @@ def test_non_alphanum_identifiers(self): k, v = row.alphanum_type_map.popitem() assert v == 1 - self.assertNotEqual(k.__class__, tuple) # should be the namedtuple type + assert k.__class__ != tuple # should be the namedtuple type assert k[0] == 'alphanum' assert k.field_0_ == 'alphanum' # named tuple with positional field name diff --git a/tests/integration/upgrade/test_upgrade.py b/tests/integration/upgrade/test_upgrade.py index 0fd6adfb66..51b3bd3850 100644 --- a/tests/integration/upgrade/test_upgrade.py +++ b/tests/integration/upgrade/test_upgrade.py @@ -150,7 +150,7 @@ def test_schema_metadata_gets_refreshed(self): # Wait for the control connection to reconnect time.sleep(20) self.cluster_driver.refresh_schema_metadata(max_schema_agreement_wait=40) - self.assertNotEqual(original_meta, self.cluster_driver.metadata.keyspaces) + assert original_meta != self.cluster_driver.metadata.keyspaces @two_to_three_path def test_schema_nodes_gets_refreshed(self): diff --git a/tests/unit/cqlengine/test_columns.py b/tests/unit/cqlengine/test_columns.py index 6696337350..f40ff01312 100644 --- a/tests/unit/cqlengine/test_columns.py +++ b/tests/unit/cqlengine/test_columns.py @@ -25,8 +25,8 @@ def test_comparisons(self): assert c1.position - c0.position == 1 # __ne__ - self.assertNotEqual(c0, c1) - self.assertNotEqual(c0, object()) + assert c0 != c1 + assert c0 != object() # __eq__ assert c0 == c0 diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index aab27f207c..16c3c21534 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -314,10 +314,10 @@ def test_statement_params_override_legacy(self): consistency_level=ConsistencyLevel.ALL, serial_consistency_level=ConsistencyLevel.SERIAL) my_timeout = 1.1234 - self.assertNotEqual(ss.retry_policy.__class__, cluster.default_retry_policy) - self.assertNotEqual(ss.consistency_level, session.default_consistency_level) - self.assertNotEqual(ss._serial_consistency_level, session.default_serial_consistency_level) - self.assertNotEqual(my_timeout, session.default_timeout) + assert ss.retry_policy.__class__ != cluster.default_retry_policy + assert ss.consistency_level != session.default_consistency_level + assert ss._serial_consistency_level != session.default_serial_consistency_level + assert my_timeout != session.default_timeout rf = session.execute_async(ss, timeout=my_timeout) expected_profile = ExecutionProfile(load_balancing_policy=cluster.load_balancing_policy, retry_policy=ss.retry_policy, @@ -339,10 +339,10 @@ def test_statement_params_override_profile(self): consistency_level=ConsistencyLevel.ALL, serial_consistency_level=ConsistencyLevel.SERIAL) my_timeout = 1.1234 - self.assertNotEqual(ss.retry_policy.__class__, rf._load_balancer.__class__) - self.assertNotEqual(ss.consistency_level, rf.message.consistency_level) - self.assertNotEqual(ss._serial_consistency_level, rf.message.serial_consistency_level) - self.assertNotEqual(my_timeout, rf.timeout) + assert ss.retry_policy.__class__ != rf._load_balancer.__class__ + assert ss.consistency_level != rf.message.consistency_level + assert ss._serial_consistency_level != rf.message.serial_consistency_level + assert my_timeout != rf.timeout rf = session.execute_async(ss, timeout=my_timeout, execution_profile='non-default') expected_profile = ExecutionProfile(non_default_profile.load_balancing_policy, ss.retry_policy, @@ -434,7 +434,7 @@ def test_exec_profile_clone(self): assert getattr(clone, attr) == getattr(active, attr) if attr in reference_attributes: self.assertIs(getattr(clone, attr), getattr(active, attr)) - self.assertNotEqual(getattr(all_updated, attr), getattr(active, attr)) + assert getattr(all_updated, attr) != getattr(active, attr) # cannot clone nonexistent profile self.assertRaises(ValueError, session.execution_profile_clone_update, 'DOES NOT EXIST', **profile_attrs) diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index b944743330..20420b9ed8 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -453,15 +453,9 @@ def test_endpoint_equality(self): assert DefaultEndPoint('10.0.0.1') == DefaultEndPoint('10.0.0.1', 9042) - self.assertNotEqual( - DefaultEndPoint('10.0.0.1'), - DefaultEndPoint('10.0.0.2') - ) - - self.assertNotEqual( - DefaultEndPoint('10.0.0.1'), - DefaultEndPoint('10.0.0.1', 0000) - ) + assert DefaultEndPoint('10.0.0.1') != DefaultEndPoint('10.0.0.2') + + assert DefaultEndPoint('10.0.0.1') != DefaultEndPoint('10.0.0.1', 0000) def test_endpoint_resolve(self): assert DefaultEndPoint('10.0.0.1').resolve() == ('10.0.0.1', 9042) diff --git a/tests/unit/test_endpoints.py b/tests/unit/test_endpoints.py index d0553b3a60..14fb8b5806 100644 --- a/tests/unit/test_endpoints.py +++ b/tests/unit/test_endpoints.py @@ -37,22 +37,13 @@ def test_sni_endpoint_properties(self): assert str(endpoint) == 'proxy.datastax.com:30002:test' def test_endpoint_equality(self): - self.assertNotEqual( - DefaultEndPoint('10.0.0.1'), - self.endpoint_factory.create_from_sni('10.0.0.1') - ) + assert DefaultEndPoint('10.0.0.1') != self.endpoint_factory.create_from_sni('10.0.0.1') assert self.endpoint_factory.create_from_sni('10.0.0.1') == self.endpoint_factory.create_from_sni('10.0.0.1') - self.assertNotEqual( - self.endpoint_factory.create_from_sni('10.0.0.1'), - self.endpoint_factory.create_from_sni('10.0.0.0') - ) + assert self.endpoint_factory.create_from_sni('10.0.0.1') != self.endpoint_factory.create_from_sni('10.0.0.0') - self.assertNotEqual( - self.endpoint_factory.create_from_sni('10.0.0.1'), - SniEndPointFactory("proxy.datastax.com", 9999).create_from_sni('10.0.0.1') - ) + assert self.endpoint_factory.create_from_sni('10.0.0.1') != SniEndPointFactory("proxy.datastax.com", 9999).create_from_sni('10.0.0.1') def test_endpoint_resolve(self): ips = ['127.0.0.1', '127.0.0.2', '127.0.0.3'] diff --git a/tests/unit/test_host_connection_pool.py b/tests/unit/test_host_connection_pool.py index be27909be0..41f0841873 100644 --- a/tests/unit/test_host_connection_pool.py +++ b/tests/unit/test_host_connection_pool.py @@ -218,8 +218,8 @@ def test_host_equality(self): c = Host('127.0.0.2', SimpleConvictionPolicy) assert a == b, 'Two Host instances should be equal when sharing.' - self.assertNotEqual(a, c, 'Two Host instances should NOT be equal when using two different addresses.') - self.assertNotEqual(b, c, 'Two Host instances should NOT be equal when using two different addresses.') + assert a != c, 'Two Host instances should NOT be equal when using two different addresses.' + assert b != c, 'Two Host instances should NOT be equal when using two different addresses.' class HostConnectionTests(_PoolTests): diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 12eeabe324..477e64590b 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -61,8 +61,8 @@ def test_replication_factor_parsing(self): def test_replication_factor_equality(self): assert ReplicationFactor.create('3/1') == ReplicationFactor.create('3/1') assert ReplicationFactor.create('3') == ReplicationFactor.create('3') - self.assertNotEqual(ReplicationFactor.create('3'), ReplicationFactor.create('3/1')) - self.assertNotEqual(ReplicationFactor.create('3'), ReplicationFactor.create('3/1')) + assert ReplicationFactor.create('3') != ReplicationFactor.create('3/1') + assert ReplicationFactor.create('3') != ReplicationFactor.create('3/1') @@ -133,7 +133,7 @@ def test_transient_replication_parsing(self): assert "'replication_factor': '3/1'" in simple_transient.export_for_schema() simple_str = rs.create('SimpleStrategy', {'replication_factor': '2'}) - self.assertNotEqual(simple_transient, simple_str) + assert simple_transient != simple_str # make token replica map ring = [MD5Token(0), MD5Token(1), MD5Token(2)] @@ -174,7 +174,7 @@ def test_nts_transient_parsing(self): assert "'dc1': '3/1', 'dc2': '5/1'" in nts_transient.export_for_schema() nts_str = rs.create('NetworkTopologyStrategy', {'dc1': '3', 'dc2': '5'}) - self.assertNotEqual(nts_transient, nts_str) + assert nts_transient != nts_str # make token replica map ring = [MD5Token(0), MD5Token(1), MD5Token(2)] @@ -339,7 +339,7 @@ def test_simple_strategy_make_token_replica_map(self): self.assertItemsEqual(rf3_replicas[MD5Token(200)], [host3, host1, host2]) def test_ss_equals(self): - self.assertNotEqual(SimpleStrategy({'replication_factor': '1'}), NetworkTopologyStrategy({'dc1': 2})) + assert SimpleStrategy({'replication_factor': '1'}) != NetworkTopologyStrategy({'dc1': 2}) class NameEscapingTest(unittest.TestCase): diff --git a/tests/unit/test_orderedmap.py b/tests/unit/test_orderedmap.py index d1326d5acc..b91529c589 100644 --- a/tests/unit/test_orderedmap.py +++ b/tests/unit/test_orderedmap.py @@ -90,12 +90,12 @@ def test_equal(self): assert om1 == d1 assert om12 == d12 assert om21 == d12 - self.assertNotEqual(om1, om12) - self.assertNotEqual(om12, om1) - self.assertNotEqual(om12, om21) - self.assertNotEqual(om1, d12) - self.assertNotEqual(om12, d1) - self.assertNotEqual(om1, EMPTY) + assert om1 != om12 + assert om12 != om1 + assert om12 != om21 + assert om1 != d12 + assert om12 != d1 + assert om1 != EMPTY self.assertFalse(OrderedMap([('three', 3), ('four', 4)]) == d12) diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index ca2e4f6462..77b1d21da7 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -1457,8 +1457,7 @@ def setUp(self): def test_ignored_with_filter(self): assert self.hfp.distance(self.ignored_host) == HostDistance.IGNORED - self.assertNotEqual(self.hfp.distance(self.accepted_host), - HostDistance.IGNORED) + assert self.hfp.distance(self.accepted_host) != HostDistance.IGNORED def test_accepted_filter_defers_to_child_policy(self): self.hfp._child_policy.distance.side_effect = distances = Mock(), Mock() diff --git a/tests/unit/test_sortedset.py b/tests/unit/test_sortedset.py index c27b36dd5a..50c2c0d6c1 100644 --- a/tests/unit/test_sortedset.py +++ b/tests/unit/test_sortedset.py @@ -66,11 +66,11 @@ def test_equal(self): assert ss12 == s12 assert ss12 == s12 assert ss1.__eq__(None) == NotImplemented - self.assertNotEqual(ss1, ss12) - self.assertNotEqual(ss12, ss1) - self.assertNotEqual(ss1, s12) - self.assertNotEqual(ss12, s1) - self.assertNotEqual(ss1, EMPTY) + assert ss1 != ss12 + assert ss12 != ss1 + assert ss1 != s12 + assert ss12 != s1 + assert ss1 != EMPTY def test_copy(self): class comparable(object): @@ -80,7 +80,7 @@ def __lt__(self, other): o = comparable() ss = sortedset([comparable(), o]) ss2 = ss.copy() - self.assertNotEqual(id(ss), id(ss2)) + assert id(ss) != id(ss2) self.assertTrue(o in ss) self.assertTrue(o in ss2) diff --git a/tests/unit/test_time_util.py b/tests/unit/test_time_util.py index 9ecebe59f2..4e094c3bf0 100644 --- a/tests/unit/test_time_util.py +++ b/tests/unit/test_time_util.py @@ -65,7 +65,7 @@ def test_uuid_from_time(self): self.assertAlmostEqual(util.unix_time_from_uuid1(u2), t, 4) assert u.clock_seq == seq # not impossible, but we shouldn't get the same value twice - self.assertNotEqual(u1.node, u2.node) + assert u1.node != u2.node # random seq u1 = util.uuid_from_time(t, node=node) @@ -74,7 +74,7 @@ def test_uuid_from_time(self): self.assertAlmostEqual(util.unix_time_from_uuid1(u2), t, 4) assert u.node == node # not impossible, but we shouldn't get the same value twice - self.assertNotEqual(u1.clock_seq, u2.clock_seq) + assert u1.clock_seq != u2.clock_seq # node too large with self.assertRaises(ValueError): diff --git a/tests/unit/test_util_types.py b/tests/unit/test_util_types.py index 7643654edd..19df42e311 100644 --- a/tests/unit/test_util_types.py +++ b/tests/unit/test_util_types.py @@ -175,7 +175,7 @@ def test_equality(self): first = Duration(1, 1, 1) second = Duration(-1, 1, 1) - self.assertNotEqual(first, second) + assert first != second first = Duration(1, 1, 1) second = Duration(1, 1, 1) From 9f9c957a5460b276dd5b5721ec1c86caf3c18c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 19:54:24 +0200 Subject: [PATCH 047/298] Replace self.assertTrue with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used commands: ``` ast-grep run --pattern 'self.assertTrue($A)' --rewrite 'assert $A' --lang python ./tests -U ast-grep run --pattern 'self.assertTrue($A, $B)' --rewrite 'assert $A, $B' --lang python ./tests -U ``` --- tests/integration/cqlengine/base.py | 3 +- .../columns/test_container_columns.py | 14 +- .../management/test_compaction_settings.py | 4 +- .../cqlengine/management/test_management.py | 4 +- .../model/test_class_construction.py | 10 +- .../cqlengine/model/test_model_io.py | 150 +++++++++--------- .../integration/cqlengine/model/test_udts.py | 2 +- .../integration/cqlengine/query/test_named.py | 6 +- .../cqlengine/query/test_queryoperators.py | 4 +- .../cqlengine/query/test_queryset.py | 8 +- .../statements/test_delete_statement.py | 12 +- .../statements/test_select_statement.py | 12 +- .../statements/test_update_statement.py | 4 +- .../integration/cqlengine/test_consistency.py | 2 +- .../integration/cqlengine/test_ifnotexists.py | 2 +- tests/integration/cqlengine/test_ttl.py | 2 +- tests/integration/long/test_failure_types.py | 2 +- tests/integration/long/test_large_data.py | 2 +- .../long/test_loadbalancingpolicies.py | 8 +- .../integration/long/test_topology_change.py | 2 +- tests/integration/simulacron/test_cluster.py | 2 +- .../standard/test_authentication.py | 2 +- tests/integration/standard/test_cluster.py | 62 ++++---- tests/integration/standard/test_concurrent.py | 18 +-- tests/integration/standard/test_connection.py | 4 +- .../standard/test_custom_cluster.py | 2 +- .../standard/test_custom_protocol_handler.py | 2 +- .../standard/test_cython_protocol_handlers.py | 12 +- tests/integration/standard/test_metadata.py | 43 +++-- tests/integration/standard/test_metrics.py | 14 +- .../standard/test_prepared_statements.py | 2 +- tests/integration/standard/test_query.py | 38 ++--- .../integration/standard/test_query_paging.py | 12 +- .../standard/test_rack_aware_policy.py | 4 +- .../standard/test_row_factories.py | 6 +- tests/integration/standard/test_types.py | 10 +- tests/integration/standard/test_udts.py | 18 +-- tests/unit/advanced/test_geometry.py | 8 +- tests/unit/advanced/test_graph.py | 8 +- tests/unit/advanced/test_metadata.py | 2 +- tests/unit/column_encryption/test_policies.py | 2 +- tests/unit/io/test_libevreactor.py | 4 +- tests/unit/io/test_twistedreactor.py | 8 +- tests/unit/io/utils.py | 24 +-- tests/unit/test_cluster.py | 66 ++++---- tests/unit/test_concurrent.py | 2 +- tests/unit/test_connection.py | 2 +- tests/unit/test_control_connection.py | 10 +- tests/unit/test_host_connection_pool.py | 14 +- tests/unit/test_metadata.py | 6 +- tests/unit/test_orderedmap.py | 4 +- tests/unit/test_policies.py | 2 +- tests/unit/test_query.py | 2 +- tests/unit/test_response_future.py | 2 +- tests/unit/test_resultset.py | 12 +- tests/unit/test_row_factories.py | 2 +- tests/unit/test_segment.py | 2 +- tests/unit/test_sortedset.py | 42 ++--- tests/unit/test_types.py | 6 +- tests/unit/test_util_types.py | 86 +++++----- 60 files changed, 407 insertions(+), 413 deletions(-) diff --git a/tests/integration/cqlengine/base.py b/tests/integration/cqlengine/base.py index e2c02c82a3..13aa64f17b 100644 --- a/tests/integration/cqlengine/base.py +++ b/tests/integration/cqlengine/base.py @@ -42,8 +42,7 @@ def setUp(self): self.session = get_session() def assertHasAttr(self, obj, attr): - self.assertTrue(hasattr(obj, attr), - "{0} doesn't have attribute: {1}".format(obj, attr)) + assert hasattr(obj, attr), "{0} doesn't have attribute: {1}".format(obj, attr) def assertNotHasAttr(self, obj, attr): self.assertFalse(hasattr(obj, attr), diff --git a/tests/integration/cqlengine/columns/test_container_columns.py b/tests/integration/cqlengine/columns/test_container_columns.py index 83c8706ebf..4dc05e2569 100644 --- a/tests/integration/cqlengine/columns/test_container_columns.py +++ b/tests/integration/cqlengine/columns/test_container_columns.py @@ -91,7 +91,7 @@ def test_deleting_last_item_should_succeed(self): m.save() m = TestSetModel.get(partition=m.partition) - self.assertTrue(5 not in m.int_set) + assert 5 not in m.int_set def test_blind_deleting_last_item_should_succeed(self): m = TestSetModel.create() @@ -101,7 +101,7 @@ def test_blind_deleting_last_item_should_succeed(self): TestSetModel.objects(partition=m.partition).update(int_set=set()) m = TestSetModel.get(partition=m.partition) - self.assertTrue(5 not in m.int_set) + assert 5 not in m.int_set def test_empty_set_retrieval(self): m = TestSetModel.create() @@ -388,7 +388,7 @@ def test_remove_last_entry_works(self): tmp.save() tmp = TestMapModel.get(partition=tmp.partition) - self.assertTrue("blah" not in tmp.int_map) + assert "blah" not in tmp.int_map def test_io_success(self): """ Tests that a basic usage works as expected """ @@ -400,11 +400,11 @@ def test_io_success(self): text_map={'now': now, 'then': then}) m2 = TestMapModel.get(partition=m1.partition) - self.assertTrue(isinstance(m2.int_map, dict)) - self.assertTrue(isinstance(m2.text_map, dict)) + assert isinstance(m2.int_map, dict) + assert isinstance(m2.text_map, dict) - self.assertTrue(1 in m2.int_map) - self.assertTrue(2 in m2.int_map) + assert 1 in m2.int_map + assert 2 in m2.int_map assert m2.int_map[1] == k1 assert m2.int_map[2] == k2 diff --git a/tests/integration/cqlengine/management/test_compaction_settings.py b/tests/integration/cqlengine/management/test_compaction_settings.py index b2dd6b8ec3..97872e86db 100644 --- a/tests/integration/cqlengine/management/test_compaction_settings.py +++ b/tests/integration/cqlengine/management/test_compaction_settings.py @@ -125,8 +125,8 @@ def _verify_options(self, table_meta, expected_options): found_at = cql.find(attr, start) else: - self.assertTrue(found_at > start) - self.assertTrue(found_at < end) + assert found_at > start + assert found_at < end def test_all_size_tiered_options(self): class AllSizeTieredOptionsModel(Model): diff --git a/tests/integration/cqlengine/management/test_management.py b/tests/integration/cqlengine/management/test_management.py index 3a0ac569a8..7e9925df87 100644 --- a/tests/integration/cqlengine/management/test_management.py +++ b/tests/integration/cqlengine/management/test_management.py @@ -363,12 +363,12 @@ def test_sync_warnings(self): with MockLoggingHandler().set_module_name(management.__name__) as mock_handler: sync_table(BaseInconsistent) sync_table(ChangedInconsistent) - self.assertTrue('differing from the model type' in mock_handler.messages.get('warning')[0]) + assert 'differing from the model type' in mock_handler.messages.get('warning')[0] if CASSANDRA_VERSION >= Version('2.1'): sync_type(DEFAULT_KEYSPACE, BaseInconsistentType) mock_handler.reset() sync_type(DEFAULT_KEYSPACE, ChangedInconsistentType) - self.assertTrue('differing from the model user type' in mock_handler.messages.get('warning')[0]) + assert 'differing from the model user type' in mock_handler.messages.get('warning')[0] class TestIndexSetModel(Model): diff --git a/tests/integration/cqlengine/model/test_class_construction.py b/tests/integration/cqlengine/model/test_class_construction.py index 2cec745a09..50fbf38290 100644 --- a/tests/integration/cqlengine/model/test_class_construction.py +++ b/tests/integration/cqlengine/model/test_class_construction.py @@ -170,13 +170,13 @@ class ModelWithPartitionKeys(Model): cols = ModelWithPartitionKeys._columns - self.assertTrue(cols['c1'].primary_key) + assert cols['c1'].primary_key self.assertFalse(cols['c1'].partition_key) - self.assertTrue(cols['p1'].primary_key) - self.assertTrue(cols['p1'].partition_key) - self.assertTrue(cols['p2'].primary_key) - self.assertTrue(cols['p2'].partition_key) + assert cols['p1'].primary_key + assert cols['p1'].partition_key + assert cols['p2'].primary_key + assert cols['p2'].partition_key obj = ModelWithPartitionKeys(p1='a', p2='b') assert obj.pk == ('a', 'b') diff --git a/tests/integration/cqlengine/model/test_model_io.py b/tests/integration/cqlengine/model/test_model_io.py index e3a5995415..d31ced662a 100644 --- a/tests/integration/cqlengine/model/test_model_io.py +++ b/tests/integration/cqlengine/model/test_model_io.py @@ -152,8 +152,8 @@ def test_column_deleting_works_properly(self): tm2 = TestModel.objects(id=tm.pk).first() assert isinstance(tm2, TestModel) - self.assertTrue(tm2.text is None) - self.assertTrue(tm2._values['text'].previous_value is None) + assert tm2.text is None + assert tm2._values['text'].previous_value is None def test_a_sensical_error_is_raised_if_you_try_to_create_a_table_twice(self): """ @@ -329,11 +329,11 @@ def test_deleting_only_deletes_one_object(self): for i in range(5): TestMultiKeyModel.create(partition=partition, cluster=i, count=i, text=str(i)) - self.assertTrue(TestMultiKeyModel.filter(partition=partition).count() == 5) + assert TestMultiKeyModel.filter(partition=partition).count() == 5 TestMultiKeyModel.get(partition=partition, cluster=0).delete() - self.assertTrue(TestMultiKeyModel.filter(partition=partition).count() == 4) + assert TestMultiKeyModel.filter(partition=partition).count() == 4 TestMultiKeyModel.filter(partition=partition).delete() @@ -368,8 +368,8 @@ def test_vanilla_update(self): self.instance.save() check = TestMultiKeyModel.get(partition=self.instance.partition, cluster=self.instance.cluster) - self.assertTrue(check.count == 5) - self.assertTrue(check.text == 'happy') + assert check.count == 5 + assert check.text == 'happy' def test_deleting_only(self): self.instance.count = None @@ -377,79 +377,79 @@ def test_deleting_only(self): self.instance.save() check = TestMultiKeyModel.get(partition=self.instance.partition, cluster=self.instance.cluster) - self.assertTrue(check.count is None) - self.assertTrue(check.text is None) + assert check.count is None + assert check.text is None def test_get_changed_columns(self): - self.assertTrue(self.instance.get_changed_columns() == []) + assert self.instance.get_changed_columns() == [] self.instance.count = 1 changes = self.instance.get_changed_columns() - self.assertTrue(len(changes) == 1) - self.assertTrue(changes == ['count']) + assert len(changes) == 1 + assert changes == ['count'] self.instance.save() - self.assertTrue(self.instance.get_changed_columns() == []) + assert self.instance.get_changed_columns() == [] def test_previous_value_tracking_of_persisted_instance(self): # Check initial internal states. - self.assertTrue(self.instance.get_changed_columns() == []) - self.assertTrue(self.instance._values['count'].previous_value == 0) + assert self.instance.get_changed_columns() == [] + assert self.instance._values['count'].previous_value == 0 # Change value and check internal states. self.instance.count = 1 - self.assertTrue(self.instance.get_changed_columns() == ['count']) - self.assertTrue(self.instance._values['count'].previous_value == 0) + assert self.instance.get_changed_columns() == ['count'] + assert self.instance._values['count'].previous_value == 0 # Internal states should be updated on save. self.instance.save() - self.assertTrue(self.instance.get_changed_columns() == []) - self.assertTrue(self.instance._values['count'].previous_value == 1) + assert self.instance.get_changed_columns() == [] + assert self.instance._values['count'].previous_value == 1 # Change value twice. self.instance.count = 2 - self.assertTrue(self.instance.get_changed_columns() == ['count']) - self.assertTrue(self.instance._values['count'].previous_value == 1) + assert self.instance.get_changed_columns() == ['count'] + assert self.instance._values['count'].previous_value == 1 self.instance.count = 3 - self.assertTrue(self.instance.get_changed_columns() == ['count']) - self.assertTrue(self.instance._values['count'].previous_value == 1) + assert self.instance.get_changed_columns() == ['count'] + assert self.instance._values['count'].previous_value == 1 # Internal states updated on save. self.instance.save() - self.assertTrue(self.instance.get_changed_columns() == []) - self.assertTrue(self.instance._values['count'].previous_value == 3) + assert self.instance.get_changed_columns() == [] + assert self.instance._values['count'].previous_value == 3 # Change value and reset it. self.instance.count = 2 - self.assertTrue(self.instance.get_changed_columns() == ['count']) - self.assertTrue(self.instance._values['count'].previous_value == 3) + assert self.instance.get_changed_columns() == ['count'] + assert self.instance._values['count'].previous_value == 3 self.instance.count = 3 - self.assertTrue(self.instance.get_changed_columns() == []) - self.assertTrue(self.instance._values['count'].previous_value == 3) + assert self.instance.get_changed_columns() == [] + assert self.instance._values['count'].previous_value == 3 # Nothing to save: values in initial conditions. self.instance.save() - self.assertTrue(self.instance.get_changed_columns() == []) - self.assertTrue(self.instance._values['count'].previous_value == 3) + assert self.instance.get_changed_columns() == [] + assert self.instance._values['count'].previous_value == 3 # Change Multiple values self.instance.count = 4 self.instance.text = "changed" - self.assertTrue(len(self.instance.get_changed_columns()) == 2) - self.assertTrue('text' in self.instance.get_changed_columns()) - self.assertTrue('count' in self.instance.get_changed_columns()) + assert len(self.instance.get_changed_columns()) == 2 + assert 'text' in self.instance.get_changed_columns() + assert 'count' in self.instance.get_changed_columns() self.instance.save() - self.assertTrue(self.instance.get_changed_columns() == []) + assert self.instance.get_changed_columns() == [] # Reset Multiple Values self.instance.count = 5 self.instance.text = "changed" - self.assertTrue(self.instance.get_changed_columns() == ['count']) + assert self.instance.get_changed_columns() == ['count'] self.instance.text = "changed2" - self.assertTrue(len(self.instance.get_changed_columns()) == 2) - self.assertTrue('text' in self.instance.get_changed_columns()) - self.assertTrue('count' in self.instance.get_changed_columns()) + assert len(self.instance.get_changed_columns()) == 2 + assert 'text' in self.instance.get_changed_columns() + assert 'count' in self.instance.get_changed_columns() self.instance.count = 4 self.instance.text = "changed" - self.assertTrue(self.instance.get_changed_columns() == []) + assert self.instance.get_changed_columns() == [] def test_previous_value_tracking_on_instantiation(self): self.instance = TestMultiKeyModel( @@ -459,30 +459,30 @@ def test_previous_value_tracking_on_instantiation(self): text='happy') # Columns of instances not persisted yet should be marked as changed. - self.assertTrue(set(self.instance.get_changed_columns()) == set([ - 'partition', 'cluster', 'count', 'text'])) - self.assertTrue(self.instance._values['partition'].previous_value is None) - self.assertTrue(self.instance._values['cluster'].previous_value is None) - self.assertTrue(self.instance._values['count'].previous_value is None) - self.assertTrue(self.instance._values['text'].previous_value is None) + assert set(self.instance.get_changed_columns()) == set([ + 'partition', 'cluster', 'count', 'text']) + assert self.instance._values['partition'].previous_value is None + assert self.instance._values['cluster'].previous_value is None + assert self.instance._values['count'].previous_value is None + assert self.instance._values['text'].previous_value is None # Value changes doesn't affect internal states. self.instance.count = 1 - self.assertTrue('count' in self.instance.get_changed_columns()) - self.assertTrue(self.instance._values['count'].previous_value is None) + assert 'count' in self.instance.get_changed_columns() + assert self.instance._values['count'].previous_value is None self.instance.count = 2 - self.assertTrue('count' in self.instance.get_changed_columns()) - self.assertTrue(self.instance._values['count'].previous_value is None) + assert 'count' in self.instance.get_changed_columns() + assert self.instance._values['count'].previous_value is None # Value reset is properly tracked. self.instance.count = None - self.assertTrue('count' not in self.instance.get_changed_columns()) - self.assertTrue(self.instance._values['count'].previous_value is None) + assert 'count' not in self.instance.get_changed_columns() + assert self.instance._values['count'].previous_value is None self.instance.save() - self.assertTrue(self.instance.get_changed_columns() == []) - self.assertTrue(self.instance._values['count'].previous_value is None) - self.assertTrue(self.instance.count is None) + assert self.instance.get_changed_columns() == [] + assert self.instance._values['count'].previous_value is None + assert self.instance.count is None def test_previous_value_tracking_on_instantiation_with_default(self): @@ -510,22 +510,22 @@ class TestDefaultValueTracking(Model): self.assertGreaterEqual(instance.int4, 0) self.assertLessEqual(instance.int4, 1000) assert instance.int5 == 5555 - self.assertTrue(instance.int6 is None) + assert instance.int6 is None # All previous values are unset as the object hasn't been persisted # yet. - self.assertTrue(instance._values['id'].previous_value is None) - self.assertTrue(instance._values['int1'].previous_value is None) - self.assertTrue(instance._values['int2'].previous_value is None) - self.assertTrue(instance._values['int3'].previous_value is None) - self.assertTrue(instance._values['int4'].previous_value is None) - self.assertTrue(instance._values['int5'].previous_value is None) - self.assertTrue(instance._values['int6'].previous_value is None) + assert instance._values['id'].previous_value is None + assert instance._values['int1'].previous_value is None + assert instance._values['int2'].previous_value is None + assert instance._values['int3'].previous_value is None + assert instance._values['int4'].previous_value is None + assert instance._values['int5'].previous_value is None + assert instance._values['int6'].previous_value is None # All explicitely set columns, and those with default values are # flagged has changed. - self.assertTrue(set(instance.get_changed_columns()) == set([ - 'id', 'int1', 'int3', 'int5'])) + assert set(instance.get_changed_columns()) == set([ + 'id', 'int1', 'int3', 'int5']) def test_save_to_none(self): """ @@ -600,28 +600,28 @@ def test_success_case(self): # object hasn't been saved, # shouldn't be able to update - self.assertTrue(not tm._is_persisted) - self.assertTrue(not tm._can_update()) + assert not tm._is_persisted + assert not tm._can_update() tm.save() # object has been saved, # should be able to update - self.assertTrue(tm._is_persisted) - self.assertTrue(tm._can_update()) + assert tm._is_persisted + assert tm._can_update() tm.count = 200 # primary keys haven't changed, # should still be able to update - self.assertTrue(tm._can_update()) + assert tm._can_update() tm.save() tm.id = uuid4() # primary keys have changed, # should not be able to update - self.assertTrue(not tm._can_update()) + assert not tm._can_update() class IndexDefinitionModel(Model): @@ -654,9 +654,9 @@ def test_reserved_cql_words_can_be_used_as_column_names(self): model2 = ReservedWordModel.filter(token='1') - self.assertTrue(len(model2) == 1) - self.assertTrue(model1.token == model2[0].token) - self.assertTrue(model1.insert == model2[0].insert) + assert len(model2) == 1 + assert model1.token == model2[0].token + assert model1.insert == model2[0].insert class TestQueryModel(Model): @@ -701,8 +701,8 @@ def test_query_with_date(self): TestQueryModel.test_id == uid, TestQueryModel.date == day).limit(1).first() - self.assertTrue(inst.test_id == uid) - self.assertTrue(inst.date == day) + assert inst.test_id == uid + assert inst.date == day class BasicModelNoRouting(Model): diff --git a/tests/integration/cqlengine/model/test_udts.py b/tests/integration/cqlengine/model/test_udts.py index 71c89dd58b..b80b70899e 100644 --- a/tests/integration/cqlengine/model/test_udts.py +++ b/tests/integration/cqlengine/model/test_udts.py @@ -114,7 +114,7 @@ def test_can_insert_udts(self): john = UserModel.objects.first() assert 0 == john.id - self.assertTrue(type(john.info) is User) + assert type(john.info) is User assert 42 == john.info.age assert "John" == john.info.name diff --git a/tests/integration/cqlengine/query/test_named.py b/tests/integration/cqlengine/query/test_named.py index 8c4deba96e..a2f890600b 100644 --- a/tests/integration/cqlengine/query/test_named.py +++ b/tests/integration/cqlengine/query/test_named.py @@ -358,9 +358,9 @@ def test_named_table_with_mv(self): key_space = NamedKeyspace(ks) mv_monthly = key_space.table("monthlyhigh") mv_all_time = key_space.table("alltimehigh") - self.assertTrue(self.check_table_size("scores", key_space, len(parameters))) - self.assertTrue(self.check_table_size("monthlyhigh", key_space, len(parameters))) - self.assertTrue(self.check_table_size("alltimehigh", key_space, len(parameters))) + assert self.check_table_size("scores", key_space, len(parameters)) + assert self.check_table_size("monthlyhigh", key_space, len(parameters)) + assert self.check_table_size("alltimehigh", key_space, len(parameters)) filtered_mv_monthly_objects = mv_monthly.objects.filter(game='Chess', year=2015, month=6) assert len(filtered_mv_monthly_objects) == 1 diff --git a/tests/integration/cqlengine/query/test_queryoperators.py b/tests/integration/cqlengine/query/test_queryoperators.py index f829a6c74a..05c6f7c3cf 100644 --- a/tests/integration/cqlengine/query/test_queryoperators.py +++ b/tests/integration/cqlengine/query/test_queryoperators.py @@ -154,6 +154,6 @@ def test_named_table_pk_token_function(self): query = named.all().limit(1) first_page = list(query) last = first_page[-1] - self.assertTrue(len(first_page) == 1) + assert len(first_page) == 1 next_page = list(query.filter(pk__token__gt=functions.Token(last.key))) - self.assertTrue(len(next_page) == 1) + assert len(next_page) == 1 diff --git a/tests/integration/cqlengine/query/test_queryset.py b/tests/integration/cqlengine/query/test_queryset.py index b5a24413d6..dc28da7cca 100644 --- a/tests/integration/cqlengine/query/test_queryset.py +++ b/tests/integration/cqlengine/query/test_queryset.py @@ -1325,7 +1325,7 @@ def test_db_field_names_used(self): v1=9, ) for value in values: - self.assertTrue(value not in str(b.queries[0])) + assert value not in str(b.queries[0]) # Test DML path b2 = BatchQuery() @@ -1335,7 +1335,7 @@ def test_db_field_names_used(self): v1=9, ) for value in values: - self.assertTrue(value not in str(b2.queries[0])) + assert value not in str(b2.queries[0]) def test_db_field_value_list(self): DBFieldModel.create(k0=0, k1=0, c0=0, v0=4, v1=5) @@ -1455,7 +1455,7 @@ def test_defaultFetchSize(self): # Validate correct fields are fetched smiths = list(People.filter(last_name="Smith")) assert len(smiths) == 3 - self.assertTrue(smiths[0].last_name is not None) + assert smiths[0].last_name is not None # Modify table with new value sync_table(People2) @@ -1471,4 +1471,4 @@ def test_defaultFetchSize(self): # validate correct items are returneds smiths = list(People2.filter(last_name="Smith")) assert len(smiths) == 5 - self.assertTrue(smiths[0].last_name is not None) + assert smiths[0].last_name is not None diff --git a/tests/integration/cqlengine/statements/test_delete_statement.py b/tests/integration/cqlengine/statements/test_delete_statement.py index 64852f1021..c5335e94c4 100644 --- a/tests/integration/cqlengine/statements/test_delete_statement.py +++ b/tests/integration/cqlengine/statements/test_delete_statement.py @@ -30,19 +30,19 @@ def test_single_field_is_listified(self): def test_field_rendering(self): """ tests that fields are properly added to the select statement """ ds = DeleteStatement('table', ['f1', 'f2']) - self.assertTrue(str(ds).startswith('DELETE "f1", "f2"'), str(ds)) - self.assertTrue(str(ds).startswith('DELETE "f1", "f2"'), str(ds)) + assert str(ds).startswith('DELETE "f1", "f2"'), str(ds) + assert str(ds).startswith('DELETE "f1", "f2"'), str(ds) def test_none_fields_rendering(self): """ tests that a '*' is added if no fields are passed in """ ds = DeleteStatement('table', None) - self.assertTrue(str(ds).startswith('DELETE FROM'), str(ds)) - self.assertTrue(str(ds).startswith('DELETE FROM'), str(ds)) + assert str(ds).startswith('DELETE FROM'), str(ds) + assert str(ds).startswith('DELETE FROM'), str(ds) def test_table_rendering(self): ds = DeleteStatement('table', None) - self.assertTrue(str(ds).startswith('DELETE FROM table'), str(ds)) - self.assertTrue(str(ds).startswith('DELETE FROM table'), str(ds)) + assert str(ds).startswith('DELETE FROM table'), str(ds) + assert str(ds).startswith('DELETE FROM table'), str(ds) def test_where_clause_rendering(self): ds = DeleteStatement('table', None) diff --git a/tests/integration/cqlengine/statements/test_select_statement.py b/tests/integration/cqlengine/statements/test_select_statement.py index ce2c5f6a99..9a6fc2e582 100644 --- a/tests/integration/cqlengine/statements/test_select_statement.py +++ b/tests/integration/cqlengine/statements/test_select_statement.py @@ -27,19 +27,19 @@ def test_single_field_is_listified(self): def test_field_rendering(self): """ tests that fields are properly added to the select statement """ ss = SelectStatement('table', ['f1', 'f2']) - self.assertTrue(str(ss).startswith('SELECT "f1", "f2"'), str(ss)) - self.assertTrue(str(ss).startswith('SELECT "f1", "f2"'), str(ss)) + assert str(ss).startswith('SELECT "f1", "f2"'), str(ss) + assert str(ss).startswith('SELECT "f1", "f2"'), str(ss) def test_none_fields_rendering(self): """ tests that a '*' is added if no fields are passed in """ ss = SelectStatement('table') - self.assertTrue(str(ss).startswith('SELECT *'), str(ss)) - self.assertTrue(str(ss).startswith('SELECT *'), str(ss)) + assert str(ss).startswith('SELECT *'), str(ss) + assert str(ss).startswith('SELECT *'), str(ss) def test_table_rendering(self): ss = SelectStatement('table') - self.assertTrue(str(ss).startswith('SELECT * FROM table'), str(ss)) - self.assertTrue(str(ss).startswith('SELECT * FROM table'), str(ss)) + assert str(ss).startswith('SELECT * FROM table'), str(ss) + assert str(ss).startswith('SELECT * FROM table'), str(ss) def test_where_clause_rendering(self): ss = SelectStatement('table') diff --git a/tests/integration/cqlengine/statements/test_update_statement.py b/tests/integration/cqlengine/statements/test_update_statement.py index ba8c7b9364..338d92872a 100644 --- a/tests/integration/cqlengine/statements/test_update_statement.py +++ b/tests/integration/cqlengine/statements/test_update_statement.py @@ -25,8 +25,8 @@ class UpdateStatementTests(unittest.TestCase): def test_table_rendering(self): """ tests that fields are properly added to the select statement """ us = UpdateStatement('table') - self.assertTrue(str(us).startswith('UPDATE table SET'), str(us)) - self.assertTrue(str(us).startswith('UPDATE table SET'), str(us)) + assert str(us).startswith('UPDATE table SET'), str(us) + assert str(us).startswith('UPDATE table SET'), str(us) def test_rendering(self): us = UpdateStatement('table') diff --git a/tests/integration/cqlengine/test_consistency.py b/tests/integration/cqlengine/test_consistency.py index 21408267cb..dedbe01fdf 100644 --- a/tests/integration/cqlengine/test_consistency.py +++ b/tests/integration/cqlengine/test_consistency.py @@ -57,7 +57,7 @@ def test_create_uses_consistency(self): def test_queryset_is_returned_on_create(self): qs = TestConsistencyModel.consistency(CL.ALL) - self.assertTrue(isinstance(qs, TestConsistencyModel.__queryset__), type(qs)) + assert isinstance(qs, TestConsistencyModel.__queryset__), type(qs) def test_update_uses_consistency(self): t = TestConsistencyModel.create(text="bacon and eggs") diff --git a/tests/integration/cqlengine/test_ifnotexists.py b/tests/integration/cqlengine/test_ifnotexists.py index e2d704ca67..0a343613f7 100644 --- a/tests/integration/cqlengine/test_ifnotexists.py +++ b/tests/integration/cqlengine/test_ifnotexists.py @@ -153,7 +153,7 @@ def test_if_not_exists_included_on_save(self): def test_queryset_is_returned_on_class(self): """ ensure we get a queryset description back """ qs = TestIfNotExistsModel.if_not_exists() - self.assertTrue(isinstance(qs, TestIfNotExistsModel.__queryset__), type(qs)) + assert isinstance(qs, TestIfNotExistsModel.__queryset__), type(qs) def test_batch_if_not_exists(self): """ ensure 'IF NOT EXISTS' exists in statement when in batch """ diff --git a/tests/integration/cqlengine/test_ttl.py b/tests/integration/cqlengine/test_ttl.py index 1b9796ad39..05fe7772f9 100644 --- a/tests/integration/cqlengine/test_ttl.py +++ b/tests/integration/cqlengine/test_ttl.py @@ -101,7 +101,7 @@ def test_queryset_is_returned_on_class(self): ensures we get a queryset descriptor back """ qs = TestTTLModel.ttl(60) - self.assertTrue(isinstance(qs, TestTTLModel.__queryset__), type(qs)) + assert isinstance(qs, TestTTLModel.__queryset__), type(qs) class TTLInstanceUpdateTest(BaseTTLTest): diff --git a/tests/integration/long/test_failure_types.py b/tests/integration/long/test_failure_types.py index 98ff114685..920f98731e 100644 --- a/tests/integration/long/test_failure_types.py +++ b/tests/integration/long/test_failure_types.py @@ -403,5 +403,5 @@ def test_async_timeouts(self): total_time = end_time-start_time # check timeout and ensure it's within a reasonable range self.assertAlmostEqual(expected_time, total_time, delta=.05) - self.assertTrue(mock_errorback.called) + assert mock_errorback.called self.assertFalse(mock_callback.called) diff --git a/tests/integration/long/test_large_data.py b/tests/integration/long/test_large_data.py index 6c75059a26..5fc79a4669 100644 --- a/tests/integration/long/test_large_data.py +++ b/tests/integration/long/test_large_data.py @@ -237,7 +237,7 @@ def test_large_text(self): for i, row in enumerate(result): assert row['txt'] == text found_result = True - self.assertTrue(found_result, "No results were found") + assert found_result, "No results were found" session.cluster.shutdown() diff --git a/tests/integration/long/test_loadbalancingpolicies.py b/tests/integration/long/test_loadbalancingpolicies.py index bc7a2a5df2..af3cd822ff 100644 --- a/tests/integration/long/test_loadbalancingpolicies.py +++ b/tests/integration/long/test_loadbalancingpolicies.py @@ -185,9 +185,9 @@ def test_token_aware_is_used_by_default(self): self.addCleanup(cluster.shutdown) if murmur3 is not None: - self.assertTrue(isinstance(cluster.profile_manager.default.load_balancing_policy, TokenAwarePolicy)) + assert isinstance(cluster.profile_manager.default.load_balancing_policy, TokenAwarePolicy) else: - self.assertTrue(isinstance(cluster.profile_manager.default.load_balancing_policy, DCAwareRoundRobinPolicy)) + assert isinstance(cluster.profile_manager.default.load_balancing_policy, DCAwareRoundRobinPolicy) def test_roundrobin(self): use_singledc() @@ -509,7 +509,7 @@ def test_token_aware_composite_key(self): routing_key=bound.routing_key)) assert results.response_future.attempted_hosts[0] in cluster.metadata.get_replicas(keyspace, bound.routing_key) - self.assertTrue(results[0].i) + assert results[0].i def test_token_aware_with_rf_2(self, use_prepared=False): use_singledc() @@ -762,7 +762,7 @@ def test_black_list_with_host_filter_policy(self): first_node_count = self.coordinator_stats.get_query_count(1) third_node_count = self.coordinator_stats.get_query_count(3) assert first_node_count + third_node_count == 12 - self.assertTrue(first_node_count == 8 or first_node_count == 4) + assert first_node_count == 8 or first_node_count == 4 self.coordinator_stats.assert_query_count_equals(self, 2, 0) diff --git a/tests/integration/long/test_topology_change.py b/tests/integration/long/test_topology_change.py index 5b12eef28c..21c1ca10b3 100644 --- a/tests/integration/long/test_topology_change.py +++ b/tests/integration/long/test_topology_change.py @@ -39,7 +39,7 @@ def test_removed_node_stops_reconnecting(self): get_node(3).nodetool("disablebinary") wait_until(condition=lambda: state_listener.downed_host is not None, delay=2, max_attempts=50) - self.assertTrue(state_listener.downed_host.is_currently_reconnecting()) + assert state_listener.downed_host.is_currently_reconnecting() decommission(3) diff --git a/tests/integration/simulacron/test_cluster.py b/tests/integration/simulacron/test_cluster.py index 2df3f99a3e..0df1a4a35a 100644 --- a/tests/integration/simulacron/test_cluster.py +++ b/tests/integration/simulacron/test_cluster.py @@ -103,5 +103,5 @@ def test_duplicate(self): warnings = mock_handler.messages.get("warning") assert len(warnings) == 1 - self.assertTrue('multiple hosts with the same endpoint' in warnings[0]) + assert 'multiple hosts with the same endpoint' in warnings[0] cluster.shutdown() diff --git a/tests/integration/standard/test_authentication.py b/tests/integration/standard/test_authentication.py index d87c3c26e1..75be6b0c9a 100644 --- a/tests/integration/standard/test_authentication.py +++ b/tests/integration/standard/test_authentication.py @@ -105,7 +105,7 @@ def test_auth_connect(self): cluster = self.cluster_as(user, passwd) session = cluster.connect(wait_for_all_pools=True) try: - self.assertTrue(session.execute("SELECT release_version FROM system.local WHERE key='local'")) + assert session.execute("SELECT release_version FROM system.local WHERE key='local'") assert_quiescent_pool_state(self, cluster, wait=1) for pool in session.get_pools(): connection, _ = pool.borrow_connection(timeout=0) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index efac079ff8..81b332d436 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -87,7 +87,7 @@ def test_ignored_host_up(self): cluster.connect() for host in cluster.metadata.all_hosts(): if str(host) == "127.0.0.1:9042": - self.assertTrue(host.is_up) + assert host.is_up else: assert host.is_up is None cluster.shutdown() @@ -104,7 +104,7 @@ def test_host_resolution(self): @test_category connection """ cluster = TestCluster(contact_points=["localhost"], connect_timeout=1) - self.assertTrue(DefaultEndPoint('127.0.0.1') in cluster.endpoints_resolved) + assert DefaultEndPoint('127.0.0.1') in cluster.endpoints_resolved @local def test_host_duplication(self): @@ -498,7 +498,7 @@ def patched_wait_for_responses(*args, **kwargs): # cluster agreement wait exceeded c = TestCluster(max_schema_agreement_wait=agreement_timeout) c.connect() - self.assertTrue(c.metadata.keyspaces) + assert c.metadata.keyspaces # cluster agreement wait used for refresh original_meta = c.metadata.keyspaces @@ -526,7 +526,7 @@ def patched_wait_for_responses(*args, **kwargs): s = c.connect() end_time = time.time() self.assertLess(end_time - start_time, refresh_threshold) - self.assertTrue(c.metadata.keyspaces) + assert c.metadata.keyspaces # cluster agreement wait used for refresh original_meta = c.metadata.keyspaces @@ -746,13 +746,13 @@ def test_idle_heartbeat(self): self.assertListEqual(list(c.request_ids), list(expected_ids)) # assert idle status - self.assertTrue(all(c.is_idle for c in connections)) + assert all(c.is_idle for c in connections) # send messages on all connections statements_and_params = [("SELECT release_version FROM system.local WHERE key='local'", ())] * len(cluster.metadata.all_hosts()) results = execute_concurrent(session, statements_and_params) for success, result in results: - self.assertTrue(success) + assert success # assert not idle status self.assertFalse(any(c.is_idle if not c.is_control_connection else False for c in connections)) @@ -777,7 +777,7 @@ def test_idle_heartbeat(self): @patch('cassandra.cluster.Cluster.idle_heartbeat_interval', new=0.1) def test_idle_heartbeat_disabled(self): - self.assertTrue(Cluster.idle_heartbeat_interval) + assert Cluster.idle_heartbeat_interval # heartbeat disabled with '0' cluster = TestCluster(idle_heartbeat_interval=0) @@ -802,10 +802,10 @@ def test_pool_management(self): # prepare p = session.prepare("SELECT * FROM system.local WHERE key=?") - self.assertTrue(session.execute(p, ('local',))) + assert session.execute(p, ('local',)) # simple - self.assertTrue(session.execute("SELECT * FROM system.local WHERE key='local'")) + assert session.execute("SELECT * FROM system.local WHERE key='local'") # set keyspace session.set_keyspace('system') @@ -863,7 +863,7 @@ def test_profile_load_balancing(self): # assert last returned value can be accessed as a namedtuple so we can prove something different named_tuple_row = rs.one() assert isinstance(named_tuple_row, tuple) - self.assertTrue(named_tuple_row.release_version) + assert named_tuple_row.release_version tmp_profile = copy(node1) tmp_profile.row_factory = tuple_factory @@ -878,7 +878,7 @@ def test_profile_load_balancing(self): tuple_row.release_version # make sure original profile is not impacted - self.assertTrue(session.execute(query, execution_profile='node1').one().release_version) + assert session.execute(query, execution_profile='node1').one().release_version def test_setting_lbp_legacy(self): cluster = TestCluster() @@ -1328,7 +1328,7 @@ def test_no_connect(self): """ with TestCluster() as cluster: self.assertFalse(cluster.is_shutdown) - self.assertTrue(cluster.is_shutdown) + assert cluster.is_shutdown def test_simple_nested(self): """ @@ -1344,9 +1344,9 @@ def test_simple_nested(self): with cluster.connect() as session: self.assertFalse(cluster.is_shutdown) self.assertFalse(session.is_shutdown) - self.assertTrue(session.execute('select release_version from system.local').one()) - self.assertTrue(session.is_shutdown) - self.assertTrue(cluster.is_shutdown) + assert session.execute('select release_version from system.local').one() + assert session.is_shutdown + assert cluster.is_shutdown def test_cluster_no_session(self): """ @@ -1362,9 +1362,9 @@ def test_cluster_no_session(self): session = cluster.connect() self.assertFalse(cluster.is_shutdown) self.assertFalse(session.is_shutdown) - self.assertTrue(session.execute('select release_version from system.local').one()) - self.assertTrue(session.is_shutdown) - self.assertTrue(cluster.is_shutdown) + assert session.execute('select release_version from system.local').one() + assert session.is_shutdown + assert cluster.is_shutdown def test_session_no_cluster(self): """ @@ -1382,15 +1382,15 @@ def test_session_no_cluster(self): self.assertFalse(cluster.is_shutdown) self.assertFalse(session.is_shutdown) self.assertFalse(unmanaged_session.is_shutdown) - self.assertTrue(session.execute('select release_version from system.local').one()) - self.assertTrue(session.is_shutdown) + assert session.execute('select release_version from system.local').one() + assert session.is_shutdown self.assertFalse(cluster.is_shutdown) self.assertFalse(unmanaged_session.is_shutdown) unmanaged_session.shutdown() - self.assertTrue(unmanaged_session.is_shutdown) + assert unmanaged_session.is_shutdown self.assertFalse(cluster.is_shutdown) cluster.shutdown() - self.assertTrue(cluster.is_shutdown) + assert cluster.is_shutdown class HostStateTest(unittest.TestCase): @@ -1413,7 +1413,7 @@ def test_down_event_with_active_connection(self): cluster.on_down(random_host, False) for _ in range(10): new_host = cluster.metadata.all_hosts()[0] - self.assertTrue(new_host.is_up, "Host was not up on iteration {0}".format(_)) + assert new_host.is_up, "Host was not up on iteration {0}".format(_) time.sleep(.01) pool = session._pools.get(random_host) @@ -1426,7 +1426,7 @@ def test_down_event_with_active_connection(self): was_marked_down = True break time.sleep(.01) - self.assertTrue(was_marked_down) + assert was_marked_down @local @@ -1505,7 +1505,7 @@ def test_valid_protocol_version_beta_options_connect(self): cluster = Cluster(protocol_version=cassandra.ProtocolVersion.V6, allow_beta_protocol_version=True) session = cluster.connect() assert cluster.protocol_version == cassandra.ProtocolVersion.V6 - self.assertTrue(session.execute("select release_version from system.local").one()) + assert session.execute("select release_version from system.local").one() cluster.shutdown() @@ -1525,9 +1525,9 @@ def test_deprecation_warnings_legacy_parameters(self): TestCluster(load_balancing_policy=RoundRobinPolicy()) logging.info(w) self.assertGreaterEqual(len(w), 1) - self.assertTrue(any(["Legacy execution parameters will be removed in 4.0. " + assert any(["Legacy execution parameters will be removed in 4.0. " "Consider using execution profiles." in - str(wa.message) for wa in w])) + str(wa.message) for wa in w]) def test_deprecation_warnings_meta_refreshed(self): """ @@ -1545,8 +1545,8 @@ def test_deprecation_warnings_meta_refreshed(self): cluster.set_meta_refresh_enabled(True) logging.info(w) self.assertGreaterEqual(len(w), 1) - self.assertTrue(any(["Cluster.set_meta_refresh_enabled is deprecated and will be removed in 4.0." in - str(wa.message) for wa in w])) + assert any(["Cluster.set_meta_refresh_enabled is deprecated and will be removed in 4.0." in + str(wa.message) for wa in w]) def test_deprecation_warning_default_consistency_level(self): """ @@ -1564,5 +1564,5 @@ def test_deprecation_warning_default_consistency_level(self): session = cluster.connect() session.default_consistency_level = ConsistencyLevel.ONE self.assertGreaterEqual(len(w), 1) - self.assertTrue(any(["Setting the consistency level at the session level will be removed in 4.0" in - str(wa.message) for wa in w])) + assert any(["Setting the consistency level at the session level will be removed in 4.0" in + str(wa.message) for wa in w]) diff --git a/tests/integration/standard/test_concurrent.py b/tests/integration/standard/test_concurrent.py index bd7e9460e5..5cad8a79f7 100644 --- a/tests/integration/standard/test_concurrent.py +++ b/tests/integration/standard/test_concurrent.py @@ -92,7 +92,7 @@ def execute_concurrent_base(self, test_fn, validate_fn, zip_args=True): test_fn(self.session, statement, parameters) assert num_statements == len(results) for success, result in results: - self.assertTrue(success) + assert success self.assertFalse(result) # read @@ -155,13 +155,13 @@ def test_execute_concurrent_with_args_generator(self): results = self.execute_concurrent_args_helper(self.session, statement, parameters, results_generator=True) for success, result in results: - self.assertTrue(success) + assert success self.assertFalse(result) results = self.execute_concurrent_args_helper(self.session, statement, parameters, results_generator=True) for result in results: - self.assertTrue(isinstance(result, ExecutionResult)) - self.assertTrue(result.success) + assert isinstance(result, ExecutionResult) + assert result.success self.assertFalse(result.result_or_exc) # read @@ -192,7 +192,7 @@ def test_execute_concurrent_paged_result(self): results = self.execute_concurrent_args_helper(self.session, statement, parameters) assert num_statements == len(results) for success, result in results: - self.assertTrue(success) + assert success self.assertFalse(result) # read @@ -203,9 +203,9 @@ def test_execute_concurrent_paged_result(self): results = self.execute_concurrent_args_helper(self.session, statement, [(num_statements,)]) assert 1 == len(results) - self.assertTrue(results[0][0]) + assert results[0][0] result = results[0][1] - self.assertTrue(result.has_more_pages) + assert result.has_more_pages assert num_statements == sum(1 for _ in result) def test_execute_concurrent_paged_result_generator(self): @@ -293,7 +293,7 @@ def test_no_raise_on_first_failure(self): self.assertFalse(success) assert isinstance(result, InvalidRequest) else: - self.assertTrue(success) + assert success self.assertFalse(result) def test_no_raise_on_first_failure_client_side(self): @@ -312,5 +312,5 @@ def test_no_raise_on_first_failure_client_side(self): self.assertFalse(success) assert isinstance(result, TypeError) else: - self.assertTrue(success) + assert success self.assertFalse(result) diff --git a/tests/integration/standard/test_connection.py b/tests/integration/standard/test_connection.py index de7ef712ee..91f19d2b14 100644 --- a/tests/integration/standard/test_connection.py +++ b/tests/integration/standard/test_connection.py @@ -141,7 +141,7 @@ def test_heart_beat_timeout(self): # Wait to seconds for the driver to be notified time.sleep(2) - self.assertTrue(test_listener.host_down) + assert test_listener.host_down # Resume paused node finally: node.resume() @@ -399,7 +399,7 @@ def test_connect_timeout(self): self.assertAlmostEqual(start, end, 1) exception_thrown = True break - self.assertTrue(exception_thrown) + assert exception_thrown def test_subclasses_share_loop(self): diff --git a/tests/integration/standard/test_custom_cluster.py b/tests/integration/standard/test_custom_cluster.py index c1eabbfd1f..3da6d176ad 100644 --- a/tests/integration/standard/test_custom_cluster.py +++ b/tests/integration/standard/test_custom_cluster.py @@ -52,5 +52,5 @@ def test_connection_honor_cluster_port(self): wait_until(lambda: len(cluster.metadata.all_hosts()) == 3, 1, 5) for host in cluster.metadata.all_hosts(): - self.assertTrue(host.is_up) + assert host.is_up session.execute("select * from system.local where key='local'", host=host) diff --git a/tests/integration/standard/test_custom_protocol_handler.py b/tests/integration/standard/test_custom_protocol_handler.py index 740eac824e..d3fe96a669 100644 --- a/tests/integration/standard/test_custom_protocol_handler.py +++ b/tests/integration/standard/test_custom_protocol_handler.py @@ -77,7 +77,7 @@ def test_custom_raw_uuid_row_results(self): session.client_protocol_handler = CustomTestRawRowType result_set = session.execute("SELECT schema_version FROM system.local WHERE key='local'") raw_value = result_set.one()[0] - self.assertTrue(isinstance(raw_value, bytes)) + assert isinstance(raw_value, bytes) assert len(raw_value) == 16 # Ensure that we get normal uuid back when we re-connect diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index 533c19b249..6de7248675 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -73,7 +73,7 @@ def test_cython_lazy_results_paged(self): results = session.execute("SELECT * FROM test_table") - self.assertTrue(results.has_more_pages) + assert results.has_more_pages assert verify_iterator_data(self.assertEqual, results) == self.N_ITEMS # make sure we see all rows cluster.shutdown() @@ -109,7 +109,7 @@ def test_numpy_results_paged(self): results = session.execute("SELECT * FROM test_table") - self.assertTrue(results.has_more_pages) + assert results.has_more_pages for count, page in enumerate(results, 1): assert isinstance(page, dict) for colname, arr in page.items(): @@ -136,8 +136,8 @@ def test_cython_numpy_are_installed_valid(self): @test_category configuration """ if VERIFY_CYTHON: - self.assertTrue(HAVE_CYTHON) - self.assertTrue(HAVE_NUMPY) + assert HAVE_CYTHON + assert HAVE_NUMPY def _verify_numpy_page(self, page): colnames = self.colnames @@ -257,5 +257,5 @@ def test_null_types(self): [self.assertIsNotNone(col_array[i]) for i in mapped_index[:begin_unset]] for i in mapped_index[begin_unset:]: assert col_array[i] is None - self.assertTrue(had_masked) - self.assertTrue(had_none) + assert had_masked + assert had_none diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 3860c88bde..4334491109 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -84,11 +84,11 @@ def test_host_addresses(self): # The control connection node should have the listen address set. listen_addrs = [host.listen_address for host in self.cluster.metadata.all_hosts()] - self.assertTrue(local_host in listen_addrs) + assert local_host in listen_addrs # The control connection node should have the broadcast_rpc_address set. rpc_addrs = [host.broadcast_rpc_address for host in self.cluster.metadata.all_hosts()] - self.assertTrue(local_host in rpc_addrs) + assert local_host in rpc_addrs @unittest.skipUnless( os.getenv('MAPPED_CASSANDRA_VERSION', None) is not None, @@ -223,15 +223,15 @@ def test_basic_table_meta_properties(self): meta = self.cluster.metadata assert meta.cluster_name != None - self.assertTrue(self.keyspace_name in meta.keyspaces) + assert self.keyspace_name in meta.keyspaces ksmeta = meta.keyspaces[self.keyspace_name] assert ksmeta.name == self.keyspace_name - self.assertTrue(ksmeta.durable_writes) + assert ksmeta.durable_writes assert ksmeta.replication_strategy.name == 'SimpleStrategy' assert ksmeta.replication_strategy.replication_factor == 1 - self.assertTrue(self.function_table_name in ksmeta.tables) + assert self.function_table_name in ksmeta.tables tablemeta = ksmeta.tables[self.function_table_name] assert tablemeta.keyspace_name == ksmeta.name assert tablemeta.name == self.function_table_name @@ -346,7 +346,7 @@ def test_cluster_column_ordering_reversed_metadata(self): b_column = tablemeta.columns['b'] self.assertFalse(b_column.is_reversed) c_column = tablemeta.columns['c'] - self.assertTrue(c_column.is_reversed) + assert c_column.is_reversed def test_compound_primary_keys_more_columns_compact(self): create_statement = self.make_create_statement(["a"], ["b", "c"], ["d"]) @@ -396,14 +396,14 @@ def test_cql_compatibility(self): assert [] == tablemeta.clustering_key assert [u'a', u'b', u'c', u'd'] == sorted(tablemeta.columns.keys()) - self.assertTrue(tablemeta.is_cql_compatible) + assert tablemeta.is_cql_compatible # It will be cql compatible after CASSANDRA-10857 # since compact storage is being dropped tablemeta.clustering_key = ["foo", "bar"] tablemeta.columns["foo"] = None tablemeta.columns["bar"] = None - self.assertTrue(tablemeta.is_cql_compatible) + assert tablemeta.is_cql_compatible def test_compound_primary_keys_ordering(self): create_statement = self.make_create_statement(["a"], ["b"], ["c"]) @@ -607,7 +607,7 @@ def test_refresh_schema_metadata(self): # Keyspace metadata modification self.session.execute("ALTER KEYSPACE {0} WITH durable_writes = false".format(self.keyspace_name)) - self.assertTrue(cluster2.metadata.keyspaces[self.keyspace_name].durable_writes) + assert cluster2.metadata.keyspaces[self.keyspace_name].durable_writes cluster2.refresh_schema_metadata() self.assertFalse(cluster2.metadata.keyspaces[self.keyspace_name].durable_writes) @@ -679,9 +679,9 @@ def test_refresh_keyspace_metadata(self): cluster2 = TestCluster(schema_event_refresh_window=-1) cluster2.connect() - self.assertTrue(cluster2.metadata.keyspaces[self.keyspace_name].durable_writes) + assert cluster2.metadata.keyspaces[self.keyspace_name].durable_writes self.session.execute("ALTER KEYSPACE {0} WITH durable_writes = false".format(self.keyspace_name)) - self.assertTrue(cluster2.metadata.keyspaces[self.keyspace_name].durable_writes) + assert cluster2.metadata.keyspaces[self.keyspace_name].durable_writes cluster2.refresh_keyspace_metadata(self.keyspace_name) self.assertFalse(cluster2.metadata.keyspaces[self.keyspace_name].durable_writes) @@ -1333,7 +1333,7 @@ def test_token(self): cluster = TestCluster() cluster.connect() tmap = cluster.metadata.token_map - self.assertTrue(issubclass(tmap.token_class, Token)) + assert issubclass(tmap.token_class, Token) assert expected_node_count == len(tmap.ring) cluster.shutdown() @@ -2253,22 +2253,22 @@ def test_create_view_metadata(self): # Make sure user is a partition key, and not null assert len(score_table.partition_key) == 1 self.assertIsNotNone(score_table.columns['user']) - self.assertTrue(score_table.columns['user'], score_table.partition_key[0]) + assert score_table.columns['user'], score_table.partition_key[0] # Validate clustering keys assert len(score_table.clustering_key) == 4 self.assertIsNotNone(score_table.columns['game']) - self.assertTrue(score_table.columns['game'], score_table.clustering_key[0]) + assert score_table.columns['game'], score_table.clustering_key[0] self.assertIsNotNone(score_table.columns['year']) - self.assertTrue(score_table.columns['year'], score_table.clustering_key[1]) + assert score_table.columns['year'], score_table.clustering_key[1] self.assertIsNotNone(score_table.columns['month']) - self.assertTrue(score_table.columns['month'], score_table.clustering_key[2]) + assert score_table.columns['month'], score_table.clustering_key[2] self.assertIsNotNone(score_table.columns['day']) - self.assertTrue(score_table.columns['day'], score_table.clustering_key[3]) + assert score_table.columns['day'], score_table.clustering_key[3] self.assertIsNotNone(score_table.columns['score']) @@ -2475,12 +2475,12 @@ def test_metadata_with_quoted_identifiers(self): # Validate partition key, and not null assert len(t1_table.partition_key) == 1 self.assertIsNotNone(t1_table.columns['theKey']) - self.assertTrue(t1_table.columns['theKey'], t1_table.partition_key[0]) + assert t1_table.columns['theKey'], t1_table.partition_key[0] # Validate clustering key column assert len(t1_table.clustering_key) == 1 self.assertIsNotNone(t1_table.columns['the;Clustering']) - self.assertTrue(t1_table.columns['the;Clustering'], t1_table.clustering_key[0]) + assert t1_table.columns['the;Clustering'], t1_table.clustering_key[0] # Validate regular column self.assertIsNotNone(t1_table.columns['the Value']) @@ -2567,10 +2567,7 @@ class VirtualKeypaceTest(BasicSharedKeyspaceUnitTestCase): def test_existing_keyspaces_have_correct_virtual_tags(self): for name, ks in self.cluster.metadata.keyspaces.items(): if name in self.virtual_ks_names: - self.assertTrue( - ks.virtual, - 'incorrect .virtual value for {}'.format(name) - ) + assert ks.virtual, 'incorrect .virtual value for {}'.format(name) else: self.assertFalse( ks.virtual, diff --git a/tests/integration/standard/test_metrics.py b/tests/integration/standard/test_metrics.py index 72c5f15ff2..4923218533 100644 --- a/tests/integration/standard/test_metrics.py +++ b/tests/integration/standard/test_metrics.py @@ -92,7 +92,7 @@ def test_write_timeout(self): # Assert read query = SimpleStatement("SELECT * FROM test WHERE k=1", consistency_level=ConsistencyLevel.ALL) results = execute_until_pass(self.session, query) - self.assertTrue(results) + assert results # Pause node so it shows as unreachable to coordinator get_node(1).pause() @@ -121,7 +121,7 @@ def test_read_timeout(self): # Assert read query = SimpleStatement("SELECT * FROM test WHERE k=1", consistency_level=ConsistencyLevel.ALL) results = execute_until_pass(self.session, query) - self.assertTrue(results) + assert results # Pause node so it shows as unreachable to coordinator get_node(1).pause() @@ -149,7 +149,7 @@ def test_unavailable(self): # Assert read query = SimpleStatement("SELECT * FROM test WHERE k=1", consistency_level=ConsistencyLevel.ALL) results = execute_until_pass(self.session, query) - self.assertTrue(results) + assert results # Stop node gracefully # Sometimes this commands continues with the other nodes having not noticed @@ -289,8 +289,8 @@ def test_duplicate_metrics_per_cluster(self): assert cluster3.metrics.get_stats()['request_timer']['count'] == 5 # Check scales to ensure they are appropriately named - self.assertTrue("appcluster" in scales._Stats.stats.keys()) - self.assertTrue("devops" in scales._Stats.stats.keys()) + assert "appcluster" in scales._Stats.stats.keys() + assert "devops" in scales._Stats.stats.keys() cluster2.shutdown() cluster3.shutdown() @@ -385,8 +385,8 @@ def test_metrics_per_cluster(self): except SyntaxException: continue - self.assertTrue(self.wait_for_count(ra, 10)) - self.assertTrue(self.wait_for_count(ra, 3, error=True)) + assert self.wait_for_count(ra, 10) + assert self.wait_for_count(ra, 3, error=True) ra.remove_ra(self.session) diff --git a/tests/integration/standard/test_prepared_statements.py b/tests/integration/standard/test_prepared_statements.py index e2117ca6c8..47419e5aa0 100644 --- a/tests/integration/standard/test_prepared_statements.py +++ b/tests/integration/standard/test_prepared_statements.py @@ -496,7 +496,7 @@ def test_prepared_id_is_updated_across_pages(self): prepared_statement.fetch_size = 2 result = self.session.execute(prepared_statement.bind((None))) - self.assertTrue(result.has_more_pages) + assert result.has_more_pages self.session.execute("ALTER TABLE {} ADD c int".format(self.table_name)) diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 3d63fe25ba..9695c29483 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -81,7 +81,7 @@ def test_trace_prints_okay(self): # Ensure this does not throw an exception trace = rs.get_query_trace() - self.assertTrue(trace.events) + assert trace.events str(trace) for event in trace.events: str(event) @@ -113,7 +113,7 @@ def test_trace_id_to_resultset(self): rs_trace = rs.get_query_trace() assert rs_trace == future_trace - self.assertTrue(rs_trace.events) + assert rs_trace.events assert len(rs_trace.events) == len(future_trace.events) self.assertListEqual([rs_trace], rs.get_all_query_traces()) @@ -129,7 +129,7 @@ def test_trace_ignores_row_factory(self): # Ensure this does not throw an exception trace = rs.get_query_trace() - self.assertTrue(trace.events) + assert trace.events str(trace) for event in trace.events: str(event) @@ -171,7 +171,7 @@ def test_client_ip_in_trace(self): # Ensure that ip is set self.assertIsNotNone(client_ip, "Client IP was not set in trace with C* >= 2.2") - self.assertTrue(pat.match(client_ip), "Client IP from trace did not match the expected value") + assert pat.match(client_ip), "Client IP from trace did not match the expected value" def test_trace_cl(self): """ @@ -224,12 +224,12 @@ def test_incomplete_query_trace(self): assert len(response_future._query_traces) == 1 trace = response_future._query_traces[0] - self.assertTrue(self._wait_for_trace_to_populate(trace.trace_id)) + assert self._wait_for_trace_to_populate(trace.trace_id) # Delete trace duration from the session (this is what the driver polls for "complete") delete_statement = SimpleStatement("DELETE duration FROM system_traces.sessions WHERE session_id = {0}".format(trace.trace_id), consistency_level=ConsistencyLevel.ALL) self.session.execute(delete_statement) - self.assertTrue(self._wait_for_trace_to_delete(trace.trace_id)) + assert self._wait_for_trace_to_delete(trace.trace_id) # should raise because duration is not set self.assertRaises(TraceUnavailable, trace.populate, max_wait=0.2, wait_for_complete=True) @@ -241,7 +241,7 @@ def test_incomplete_query_trace(self): self.assertIsNotNone(trace.trace_id) self.assertIsNotNone(trace.request_type) self.assertIsNotNone(trace.parameters) - self.assertTrue(trace.events) # non-zero list len + assert trace.events # non-zero list len self.assertIsNotNone(trace.started_at) def _wait_for_trace_to_populate(self, trace_id): @@ -811,7 +811,7 @@ def test_conditional_update(self): future = self.session.execute_async(statement) result = future.result() assert future.message.serial_consistency_level == ConsistencyLevel.SERIAL - self.assertTrue(result) + assert result self.assertFalse(result.one().applied) statement = SimpleStatement( @@ -821,8 +821,8 @@ def test_conditional_update(self): future = self.session.execute_async(statement) result = future.result() assert future.message.serial_consistency_level == ConsistencyLevel.LOCAL_SERIAL - self.assertTrue(result) - self.assertTrue(result.one().applied) + assert result + assert result.one().applied def test_conditional_update_with_prepared_statements(self): self.session.execute("INSERT INTO test3rf.test (k, v) VALUES (0, 0)") @@ -833,7 +833,7 @@ def test_conditional_update_with_prepared_statements(self): future = self.session.execute_async(statement) result = future.result() assert future.message.serial_consistency_level == ConsistencyLevel.SERIAL - self.assertTrue(result) + assert result self.assertFalse(result.one().applied) statement = self.session.prepare( @@ -843,8 +843,8 @@ def test_conditional_update_with_prepared_statements(self): future = self.session.execute_async(bound) result = future.result() assert future.message.serial_consistency_level == ConsistencyLevel.LOCAL_SERIAL - self.assertTrue(result) - self.assertTrue(result.one().applied) + assert result + assert result.one().applied def test_conditional_update_with_batch_statements(self): self.session.execute("INSERT INTO test3rf.test (k, v) VALUES (0, 0)") @@ -854,7 +854,7 @@ def test_conditional_update_with_batch_statements(self): future = self.session.execute_async(statement) result = future.result() assert future.message.serial_consistency_level == ConsistencyLevel.SERIAL - self.assertTrue(result) + assert result self.assertFalse(result.one().applied) statement = BatchStatement(serial_consistency_level=ConsistencyLevel.LOCAL_SERIAL) @@ -863,8 +863,8 @@ def test_conditional_update_with_batch_statements(self): future = self.session.execute_async(statement) result = future.result() assert future.message.serial_consistency_level == ConsistencyLevel.LOCAL_SERIAL - self.assertTrue(result) - self.assertTrue(result.one().applied) + assert result + assert result.one().applied def test_bad_consistency_level(self): statement = SimpleStatement("foo") @@ -945,7 +945,7 @@ def test_no_connection_refused_on_timeout(self): self.fail("Unexpected exception %s: %s" % (exception_type, result.message)) # Make sure test passed - self.assertTrue(received_timeout) + assert received_timeout @xfail_scylla('Fails on Scylla with error `SERIAL/LOCAL_SERIAL consistency may only be requested for one partition at a time`') def test_was_applied_batch_stmt(self): @@ -1008,7 +1008,7 @@ def test_was_applied_batch_stmt(self): "INSERT INTO test3rf.lwt_clustering (k, c, v) VALUES (0, 5, 10) IF NOT EXISTS;"], [None] * 3) result = self.session.execute(batch_statement) - self.assertTrue(result.was_applied) + assert result.was_applied all_rows = self.session.execute("SELECT * from test3rf.lwt_clustering", execution_profile='serial') for i, row in enumerate(all_rows): @@ -1059,7 +1059,7 @@ def test_was_applied_batch_string(self): APPLY batch; """ result = self.session.execute(batch_str) - self.assertTrue(result.was_applied) + assert result.was_applied class BatchStatementDefaultRoutingKeyTests(unittest.TestCase): diff --git a/tests/integration/standard/test_query_paging.py b/tests/integration/standard/test_query_paging.py index 9748e9d88f..968cc908c8 100644 --- a/tests/integration/standard/test_query_paging.py +++ b/tests/integration/standard/test_query_paging.py @@ -331,7 +331,7 @@ def test_concurrent_with_paging(self): results = execute_concurrent_with_args(self.session, prepared, [None] * 10) assert 10 == len(results) for (success, result) in results: - self.assertTrue(success) + assert success assert 100 == len(list(result)) def test_fetch_size(self): @@ -346,7 +346,7 @@ def test_fetch_size(self): self.session.default_fetch_size = 10 result = self.session.execute(prepared, []) - self.assertTrue(result.has_more_pages) + assert result.has_more_pages self.session.default_fetch_size = 2000 result = self.session.execute(prepared, []) @@ -368,7 +368,7 @@ def test_fetch_size(self): prepared.fetch_size = 10 result = self.session.execute(prepared, []) - self.assertTrue(result.has_more_pages) + assert result.has_more_pages prepared.fetch_size = 2000 bound = prepared.bind([]) @@ -383,7 +383,7 @@ def test_fetch_size(self): prepared.fetch_size = 10 bound = prepared.bind([]) result = self.session.execute(bound, []) - self.assertTrue(result.has_more_pages) + assert result.has_more_pages bound.fetch_size = 2000 result = self.session.execute(bound, []) @@ -395,7 +395,7 @@ def test_fetch_size(self): bound.fetch_size = 10 result = self.session.execute(bound, []) - self.assertTrue(result.has_more_pages) + assert result.has_more_pages s = SimpleStatement("SELECT * FROM test3rf.test", fetch_size=None) result = self.session.execute(s, []) @@ -403,7 +403,7 @@ def test_fetch_size(self): s = SimpleStatement("SELECT * FROM test3rf.test") result = self.session.execute(s, []) - self.assertTrue(result.has_more_pages) + assert result.has_more_pages s = SimpleStatement("SELECT * FROM test3rf.test") s.fetch_size = None diff --git a/tests/integration/standard/test_rack_aware_policy.py b/tests/integration/standard/test_rack_aware_policy.py index f31cb77ce8..d2a358373d 100644 --- a/tests/integration/standard/test_rack_aware_policy.py +++ b/tests/integration/standard/test_rack_aware_policy.py @@ -68,7 +68,7 @@ def test_rack_aware(self): results = self.session.execute(bound) assert results == [(i, i%5, i%2)] coordinator = str(results.response_future.coordinator_host.endpoint) - self.assertTrue(coordinator in set(["127.0.0.1:9042", "127.0.0.2:9042"])) + assert coordinator in set(["127.0.0.1:9042", "127.0.0.2:9042"]) self.node2.stop(wait_other_notice=True, gently=True) @@ -86,4 +86,4 @@ def test_rack_aware(self): results = self.session.execute(bound) assert results == [(i, i%5, i%2)] coordinator = str(results.response_future.coordinator_host.endpoint) - self.assertTrue(coordinator in set(["127.0.0.3:9042", "127.0.0.4:9042"])) + assert coordinator in set(["127.0.0.3:9042", "127.0.0.4:9042"]) diff --git a/tests/integration/standard/test_row_factories.py b/tests/integration/standard/test_row_factories.py index e0f882b43c..b96e2f2901 100644 --- a/tests/integration/standard/test_row_factories.py +++ b/tests/integration/standard/test_row_factories.py @@ -66,9 +66,9 @@ def test_sanitizing(self): query = "SELECT v1 AS duplicate, v2 AS duplicate, v3 AS duplicate from {0}.{1}".format(self.ks_name, self.function_table_name) rs = self.session.execute(query) row = rs.one() - self.assertTrue(hasattr(row, 'duplicate')) - self.assertTrue(hasattr(row, 'duplicate_')) - self.assertTrue(hasattr(row, 'duplicate__')) + assert hasattr(row, 'duplicate') + assert hasattr(row, 'duplicate_') + assert hasattr(row, 'duplicate__') class RowFactoryTests(BasicSharedKeyspaceUnitTestCaseWFunctionTable): diff --git a/tests/integration/standard/test_types.py b/tests/integration/standard/test_types.py index df5a73b20b..6152fb4da9 100644 --- a/tests/integration/standard/test_types.py +++ b/tests/integration/standard/test_types.py @@ -307,12 +307,12 @@ def test_can_insert_empty_strings_and_nulls(self): columns_string = ','.join(col_names) s.execute("INSERT INTO all_empty (zz) VALUES (2)") results = s.execute("SELECT {0} FROM all_empty WHERE zz=2".format(columns_string)).one() - self.assertTrue(all(x is None for x in results)) + assert all(x is None for x in results) # verify all types initially null with prepared statement select = s.prepare("SELECT {0} FROM all_empty WHERE zz=?".format(columns_string)) results = s.execute(select.bind([2])).one() - self.assertTrue(all(x is None for x in results)) + assert all(x is None for x in results) # insert empty strings for string-like fields expected_values = dict((col, '') for col in string_columns) @@ -815,8 +815,8 @@ def verify_insert_select(ins_statement, sel_statement): for f in items: row = s.execute(sel_statement, (f,)).one() if math.isnan(f): - self.assertTrue(math.isnan(row.f)) - self.assertTrue(math.isnan(row.d)) + assert math.isnan(row.f) + assert math.isnan(row.d) else: assert row.f == f assert row.d == f @@ -847,7 +847,7 @@ def test_cython_decimal(self): try: self.session.execute("INSERT INTO {0} (dc) VALUES (-1.08430792318105707)".format(self.function_table_name)) results = self.session.execute("SELECT * FROM {0}".format(self.function_table_name)) - self.assertTrue(str(results.one().dc) == '-1.08430792318105707') + assert str(results.one().dc) == '-1.08430792318105707' finally: self.session.execute("DROP TABLE {0}".format(self.function_table_name)) diff --git a/tests/integration/standard/test_udts.py b/tests/integration/standard/test_udts.py index e047ab7480..bb04ce2f36 100644 --- a/tests/integration/standard/test_udts.py +++ b/tests/integration/standard/test_udts.py @@ -88,7 +88,7 @@ def test_can_insert_unprepared_registered_udts(self): row = result.one() assert 42 == row.b.age assert 'bob' == row.b.name - self.assertTrue(type(row.b) is User) + assert type(row.b) is User # use the same UDT name in a different keyspace s.execute(""" @@ -107,7 +107,7 @@ def test_can_insert_unprepared_registered_udts(self): row = result.one() assert 'Texas' == row.b.state assert True == row.b.is_cool - self.assertTrue(type(row.b) is User) + assert type(row.b) is User s.execute("DROP KEYSPACE udt_test_unprepared_registered2") @@ -153,7 +153,7 @@ def test_can_register_udt_before_connecting(self): row = result.one() assert 42 == row.b.age assert 'bob' == row.b.name - self.assertTrue(type(row.b) is User1) + assert type(row.b) is User1 # use the same UDT name in a different keyspace s.execute("INSERT INTO udt_test_register_before_connecting2.mytable (a, b) VALUES (%s, %s)", (0, User2('Texas', True))) @@ -161,7 +161,7 @@ def test_can_register_udt_before_connecting(self): row = result.one() assert 'Texas' == row.b.state assert True == row.b.is_cool - self.assertTrue(type(row.b) is User2) + assert type(row.b) is User2 s.execute("DROP KEYSPACE udt_test_register_before_connecting") s.execute("DROP KEYSPACE udt_test_register_before_connecting2") @@ -234,7 +234,7 @@ def test_can_insert_prepared_registered_udts(self): row = result.one() assert 42 == row.b.age assert 'bob' == row.b.name - self.assertTrue(type(row.b) is User) + assert type(row.b) is User # use the same UDT name in a different keyspace s.execute(""" @@ -256,7 +256,7 @@ def test_can_insert_prepared_registered_udts(self): row = result.one() assert 'Texas' == row.b.state assert True == row.b.is_cool - self.assertTrue(type(row.b) is User) + assert type(row.b) is User s.execute("DROP KEYSPACE udt_test_prepared_registered2") @@ -736,7 +736,7 @@ def test_alter_udt(self): self.session.execute(insert_statement, [1, typetoalter(1)]) results = self.session.execute("SELECT * from {0}".format(self.function_table_name)) for result in results: - self.assertTrue(hasattr(result.typetoalter, 'a')) + assert hasattr(result.typetoalter, 'a') self.assertFalse(hasattr(result.typetoalter, 'b')) # Alter UDT and ensure the alter is honored in results @@ -745,5 +745,5 @@ def test_alter_udt(self): self.session.execute(insert_statement, [2, typetoalter(2, 2)]) results = self.session.execute("SELECT * from {0}".format(self.function_table_name)) for result in results: - self.assertTrue(hasattr(result.typetoalter, 'a')) - self.assertTrue(hasattr(result.typetoalter, 'b')) + assert hasattr(result.typetoalter, 'a') + assert hasattr(result.typetoalter, 'b') diff --git a/tests/unit/advanced/test_geometry.py b/tests/unit/advanced/test_geometry.py index 1360bfde05..059a1f2b06 100644 --- a/tests/unit/advanced/test_geometry.py +++ b/tests/unit/advanced/test_geometry.py @@ -144,7 +144,7 @@ def test_line_parse(self): assert len(lo.coords) == 2 for cords in lo.coords: for cord in cords: - self.assertTrue(math.isnan(cord)) + assert math.isnan(cord) def test_distance_parse(self): """ @@ -203,8 +203,8 @@ def test_point_parse(self): # Test point with NAN ps = "POINT (NAN NAN)" po = Point.from_wkt(ps) - self.assertTrue(math.isnan(po.x)) - self.assertTrue(math.isnan(po.y)) + assert math.isnan(po.x) + assert math.isnan(po.y) def test_polygon_parse(self): """ @@ -252,7 +252,7 @@ def test_polygon_parse(self): po = Polygon.from_wkt(ps) for cords in po.exterior.coords: for cord in cords: - self.assertTrue(math.isnan(cord)) + assert math.isnan(cord) def _construct_line_string(self, num_of_points): # Constructs a arbitrarily long line string diff --git a/tests/unit/advanced/test_graph.py b/tests/unit/advanced/test_graph.py index b52c37ef57..d76335da65 100644 --- a/tests/unit/advanced/test_graph.py +++ b/tests/unit/advanced/test_graph.py @@ -351,25 +351,25 @@ def test_graph_source_convenience_attributes(self): opts = GraphOptions() assert opts.graph_source == b'g' self.assertFalse(opts.is_analytics_source) - self.assertTrue(opts.is_graph_source) + assert opts.is_graph_source self.assertFalse(opts.is_default_source) opts.set_source_default() self.assertIsNotNone(opts.graph_source) self.assertFalse(opts.is_analytics_source) self.assertFalse(opts.is_graph_source) - self.assertTrue(opts.is_default_source) + assert opts.is_default_source opts.set_source_analytics() self.assertIsNotNone(opts.graph_source) - self.assertTrue(opts.is_analytics_source) + assert opts.is_analytics_source self.assertFalse(opts.is_graph_source) self.assertFalse(opts.is_default_source) opts.set_source_graph() self.assertIsNotNone(opts.graph_source) self.assertFalse(opts.is_analytics_source) - self.assertTrue(opts.is_graph_source) + assert opts.is_graph_source self.assertFalse(opts.is_default_source) class GraphStatementTests(unittest.TestCase): diff --git a/tests/unit/advanced/test_metadata.py b/tests/unit/advanced/test_metadata.py index 056cc61d6f..de3fd6bf7c 100644 --- a/tests/unit/advanced/test_metadata.py +++ b/tests/unit/advanced/test_metadata.py @@ -90,7 +90,7 @@ def test_table_with_edge(self): def test_vertex_with_label(self): tm = self. _create_table_metadata(with_vertex=True) - self.assertTrue(tm.as_cql_query().endswith('VERTEX LABEL label')) + assert tm.as_cql_query().endswith('VERTEX LABEL label') def test_edge_single_partition_key_and_clustering_key(self): tm = self._create_table_metadata(with_edge=True) diff --git a/tests/unit/column_encryption/test_policies.py b/tests/unit/column_encryption/test_policies.py index 8c6b57b3cf..00ac219c0c 100644 --- a/tests/unit/column_encryption/test_policies.py +++ b/tests/unit/column_encryption/test_policies.py @@ -121,7 +121,7 @@ def test_contains_column(self): coldesc = ColDesc('ks1','table1','col1') policy = AES256ColumnEncryptionPolicy() policy.add_column(coldesc, self._random_key(), "blob") - self.assertTrue(policy.contains_column(coldesc)) + assert policy.contains_column(coldesc) self.assertFalse(policy.contains_column(ColDesc('ks2','table1','col1'))) self.assertFalse(policy.contains_column(ColDesc('ks1','table2','col1'))) self.assertFalse(policy.contains_column(ColDesc('ks1','table1','col2'))) diff --git a/tests/unit/io/test_libevreactor.py b/tests/unit/io/test_libevreactor.py index 17e03d0fd5..cf7e7caf77 100644 --- a/tests/unit/io/test_libevreactor.py +++ b/tests/unit/io/test_libevreactor.py @@ -83,8 +83,8 @@ def test_watchers_are_finished(self): # be called libev__cleanup(_global_loop) for conn in live_connections: - self.assertTrue(conn._write_watcher.stop.mock_calls) - self.assertTrue(conn._read_watcher.stop.mock_calls) + assert conn._write_watcher.stop.mock_calls + assert conn._read_watcher.stop.mock_calls _global_loop._shutdown = False diff --git a/tests/unit/io/test_twistedreactor.py b/tests/unit/io/test_twistedreactor.py index 2b89c5edf0..414bc57389 100644 --- a/tests/unit/io/test_twistedreactor.py +++ b/tests/unit/io/test_twistedreactor.py @@ -76,7 +76,7 @@ def test_makeConnection(self): object that a successful connection was made. """ self.obj_ut.makeConnection(self.tr) - self.assertTrue(self.mock_connection.client_connection_made.called) + assert self.mock_connection.client_connection_made.called def test_receiving_data(self): """ @@ -85,7 +85,7 @@ def test_receiving_data(self): """ self.obj_ut.makeConnection(self.tr) self.obj_ut.dataReceived('foobar') - self.assertTrue(self.mock_connection.handle_read.called) + assert self.mock_connection.handle_read.called self.mock_connection._iobuf.write.assert_called_with("foobar") @@ -136,8 +136,8 @@ def test_close(self, mock_connectTCP): self.obj_ut.is_closed = False self.obj_ut.close() - self.assertTrue(self.obj_ut.connected_event.is_set()) - self.assertTrue(self.obj_ut.error_all_requests.called) + assert self.obj_ut.connected_event.is_set() + assert self.obj_ut.error_all_requests.called def test_handle_read__incomplete(self): """ diff --git a/tests/unit/io/utils.py b/tests/unit/io/utils.py index 6ae620e365..6b83c34dac 100644 --- a/tests/unit/io/utils.py +++ b/tests/unit/io/utils.py @@ -249,7 +249,7 @@ def test_successful_connection(self): self.get_socket(c).recv.return_value = self.make_msg(header) c.handle_read(*self.null_handle_function_args) - self.assertTrue(c.connected_event.is_set()) + assert c.connected_event.is_set() return c def test_eagain_on_buffer_size(self): @@ -329,8 +329,8 @@ def test_protocol_error(self): c.handle_read(*self.null_handle_function_args) # make sure it errored correctly - self.assertTrue(c.is_defunct) - self.assertTrue(c.connected_event.is_set()) + assert c.is_defunct + assert c.connected_event.is_set() assert isinstance(c.last_error, ProtocolError) def test_error_message_on_startup(self): @@ -354,9 +354,9 @@ def test_error_message_on_startup(self): c.handle_read(*self.null_handle_function_args) # make sure it errored correctly - self.assertTrue(c.is_defunct) + assert c.is_defunct assert isinstance(c.last_error, ConnectionException) - self.assertTrue(c.connected_event.is_set()) + assert c.connected_event.is_set() def test_socket_error_on_write(self): c = self.make_connection() @@ -366,9 +366,9 @@ def test_socket_error_on_write(self): c.handle_write(*self.null_handle_function_args) # make sure it errored correctly - self.assertTrue(c.is_defunct) + assert c.is_defunct assert isinstance(c.last_error, socket_error) - self.assertTrue(c.connected_event.is_set()) + assert c.connected_event.is_set() def test_blocking_on_write(self): c = self.make_connection() @@ -384,7 +384,7 @@ def test_blocking_on_write(self): self.get_socket(c).send.side_effect = lambda x: len(x) c.handle_write(*self.null_handle_function_args) self.assertFalse(c.is_defunct) - self.assertTrue(self.get_socket(c).send.call_args is not None) + assert self.get_socket(c).send.call_args is not None def test_partial_send(self): c = self.make_connection() @@ -415,9 +415,9 @@ def test_socket_error_on_read(self): c.handle_read(*self.null_handle_function_args) # make sure it errored correctly - self.assertTrue(c.is_defunct) + assert c.is_defunct assert isinstance(c.last_error, socket_error) - self.assertTrue(c.connected_event.is_set()) + assert c.connected_event.is_set() def test_partial_header_read(self): c = self.make_connection() @@ -441,7 +441,7 @@ def test_partial_header_read(self): self.get_socket(c).recv.return_value = self.make_msg(header) c.handle_read(*self.null_handle_function_args) - self.assertTrue(c.connected_event.is_set()) + assert c.connected_event.is_set() self.assertFalse(c.is_defunct) def test_partial_message_read(self): @@ -468,7 +468,7 @@ def test_partial_message_read(self): self.get_socket(c).recv.return_value = self.make_msg(header) c.handle_read(*self.null_handle_function_args) - self.assertTrue(c.connected_event.is_set()) + assert c.connected_event.is_set() self.assertFalse(c.is_defunct) def test_mixed_message_and_buffer_sizes(self): diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 16c3c21534..d8bec326a1 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -38,51 +38,51 @@ def test_exception_types(self): PYTHON-443 Sanity check to ensure we don't unintentionally change class hierarchy of exception types """ - self.assertTrue(issubclass(Unavailable, DriverException)) - self.assertTrue(issubclass(Unavailable, RequestExecutionException)) + assert issubclass(Unavailable, DriverException) + assert issubclass(Unavailable, RequestExecutionException) - self.assertTrue(issubclass(ReadTimeout, DriverException)) - self.assertTrue(issubclass(ReadTimeout, RequestExecutionException)) - self.assertTrue(issubclass(ReadTimeout, Timeout)) + assert issubclass(ReadTimeout, DriverException) + assert issubclass(ReadTimeout, RequestExecutionException) + assert issubclass(ReadTimeout, Timeout) - self.assertTrue(issubclass(WriteTimeout, DriverException)) - self.assertTrue(issubclass(WriteTimeout, RequestExecutionException)) - self.assertTrue(issubclass(WriteTimeout, Timeout)) + assert issubclass(WriteTimeout, DriverException) + assert issubclass(WriteTimeout, RequestExecutionException) + assert issubclass(WriteTimeout, Timeout) - self.assertTrue(issubclass(CoordinationFailure, DriverException)) - self.assertTrue(issubclass(CoordinationFailure, RequestExecutionException)) + assert issubclass(CoordinationFailure, DriverException) + assert issubclass(CoordinationFailure, RequestExecutionException) - self.assertTrue(issubclass(ReadFailure, DriverException)) - self.assertTrue(issubclass(ReadFailure, RequestExecutionException)) - self.assertTrue(issubclass(ReadFailure, CoordinationFailure)) + assert issubclass(ReadFailure, DriverException) + assert issubclass(ReadFailure, RequestExecutionException) + assert issubclass(ReadFailure, CoordinationFailure) - self.assertTrue(issubclass(WriteFailure, DriverException)) - self.assertTrue(issubclass(WriteFailure, RequestExecutionException)) - self.assertTrue(issubclass(WriteFailure, CoordinationFailure)) + assert issubclass(WriteFailure, DriverException) + assert issubclass(WriteFailure, RequestExecutionException) + assert issubclass(WriteFailure, CoordinationFailure) - self.assertTrue(issubclass(FunctionFailure, DriverException)) - self.assertTrue(issubclass(FunctionFailure, RequestExecutionException)) + assert issubclass(FunctionFailure, DriverException) + assert issubclass(FunctionFailure, RequestExecutionException) - self.assertTrue(issubclass(RequestValidationException, DriverException)) + assert issubclass(RequestValidationException, DriverException) - self.assertTrue(issubclass(ConfigurationException, DriverException)) - self.assertTrue(issubclass(ConfigurationException, RequestValidationException)) + assert issubclass(ConfigurationException, DriverException) + assert issubclass(ConfigurationException, RequestValidationException) - self.assertTrue(issubclass(AlreadyExists, DriverException)) - self.assertTrue(issubclass(AlreadyExists, RequestValidationException)) - self.assertTrue(issubclass(AlreadyExists, ConfigurationException)) + assert issubclass(AlreadyExists, DriverException) + assert issubclass(AlreadyExists, RequestValidationException) + assert issubclass(AlreadyExists, ConfigurationException) - self.assertTrue(issubclass(InvalidRequest, DriverException)) - self.assertTrue(issubclass(InvalidRequest, RequestValidationException)) + assert issubclass(InvalidRequest, DriverException) + assert issubclass(InvalidRequest, RequestValidationException) - self.assertTrue(issubclass(Unauthorized, DriverException)) - self.assertTrue(issubclass(Unauthorized, RequestValidationException)) + assert issubclass(Unauthorized, DriverException) + assert issubclass(Unauthorized, RequestValidationException) - self.assertTrue(issubclass(AuthenticationFailed, DriverException)) + assert issubclass(AuthenticationFailed, DriverException) - self.assertTrue(issubclass(OperationTimedOut, DriverException)) + assert issubclass(OperationTimedOut, DriverException) - self.assertTrue(issubclass(UnsupportedOperation, DriverException)) + assert issubclass(UnsupportedOperation, DriverException) class ClusterTest(unittest.TestCase): @@ -218,8 +218,8 @@ def test_protocol_downgrade_test(self): lower = ProtocolVersion.get_lower_supported(ProtocolVersion.V3) assert 0 == lower - self.assertTrue(ProtocolVersion.uses_error_code_map(ProtocolVersion.DSE_V1)) - self.assertTrue(ProtocolVersion.uses_int_query_flags(ProtocolVersion.DSE_V1)) + assert ProtocolVersion.uses_error_code_map(ProtocolVersion.DSE_V1) + assert ProtocolVersion.uses_int_query_flags(ProtocolVersion.DSE_V1) self.assertFalse(ProtocolVersion.uses_error_code_map(ProtocolVersion.V4)) self.assertFalse(ProtocolVersion.uses_int_query_flags(ProtocolVersion.V4)) diff --git a/tests/unit/test_concurrent.py b/tests/unit/test_concurrent.py index a4d2bdf2a9..9df733b551 100644 --- a/tests/unit/test_concurrent.py +++ b/tests/unit/test_concurrent.py @@ -229,7 +229,7 @@ def validate_result_ordering(self, results): """ last_time_added = 0 for success, result in results: - self.assertTrue(success) + assert success current_time_added = list(result)[0] #Windows clock granularity makes this equal most of the times diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 20420b9ed8..9e2a6a784c 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -278,7 +278,7 @@ def run_heartbeat(self, get_holders_fun, count=2, interval=0.05, timeout=0.05): wait_until(lambda: get_holders_fun.call_count > 0, 0.01, 100) time.sleep(interval * (count-1)) ch.stop() - self.assertTrue(get_holders_fun.call_count) + assert get_holders_fun.call_count def test_empty_connections(self, *args): count = 3 diff --git a/tests/unit/test_control_connection.py b/tests/unit/test_control_connection.py index 92f9c80763..ecfcf1dee9 100644 --- a/tests/unit/test_control_connection.py +++ b/tests/unit/test_control_connection.py @@ -210,7 +210,7 @@ def test_wait_for_schema_agreement(self): """ Basic test with all schema versions agreeing """ - self.assertTrue(self.control_connection.wait_for_schema_agreement()) + assert self.control_connection.wait_for_schema_agreement() # the control connection should not have slept at all assert self.time.clock == 0 @@ -219,7 +219,7 @@ def test_wait_for_schema_agreement_uses_preloaded_results_if_given(self): wait_for_schema_agreement uses preloaded results if given for shared table queries """ preloaded_results = self._matching_schema_preloaded_results - self.assertTrue(self.control_connection.wait_for_schema_agreement(preloaded_results=preloaded_results)) + assert self.control_connection.wait_for_schema_agreement(preloaded_results=preloaded_results) # the control connection should not have slept at all assert self.time.clock == 0 # the connection should not have made any queries if given preloaded results @@ -230,7 +230,7 @@ def test_wait_for_schema_agreement_falls_back_to_querying_if_schemas_dont_match_ wait_for_schema_agreement requery if schema does not match using preloaded results """ preloaded_results = self._nonmatching_schema_preloaded_results - self.assertTrue(self.control_connection.wait_for_schema_agreement(preloaded_results=preloaded_results)) + assert self.control_connection.wait_for_schema_agreement(preloaded_results=preloaded_results) # the control connection should not have slept at all assert self.time.clock == 0 assert self.connection.wait_for_responses.call_count == 1 @@ -262,7 +262,7 @@ def test_wait_for_schema_agreement_skipping(self): self.connection.peer_results[1][1][3] = 'c' self.cluster.metadata.get_host(DefaultEndPoint('192.168.1.1')).is_up = False - self.assertTrue(self.control_connection.wait_for_schema_agreement()) + assert self.control_connection.wait_for_schema_agreement() assert self.time.clock == 0 def test_wait_for_schema_agreement_rpc_lookup(self): @@ -279,7 +279,7 @@ def test_wait_for_schema_agreement_rpc_lookup(self): # even though the new host has a different schema version, it's # marked as down, so the control connection shouldn't care - self.assertTrue(self.control_connection.wait_for_schema_agreement()) + assert self.control_connection.wait_for_schema_agreement() assert self.time.clock == 0 # but once we mark it up, the control connection will care diff --git a/tests/unit/test_host_connection_pool.py b/tests/unit/test_host_connection_pool.py index 41f0841873..922a5df7e7 100644 --- a/tests/unit/test_host_connection_pool.py +++ b/tests/unit/test_host_connection_pool.py @@ -147,7 +147,7 @@ def test_return_defunct_connection(self): pool.return_connection(conn) # the connection should be closed a new creation scheduled - self.assertTrue(session.submit.call_args) + assert session.submit.call_args self.assertFalse(pool.is_shutdown) def test_return_defunct_connection_on_down_host(self): @@ -169,15 +169,15 @@ def test_return_defunct_connection_on_down_host(self): pool.return_connection(conn) # the connection should be closed a new creation scheduled - self.assertTrue(conn.close.call_args) + assert conn.close.call_args if self.PoolImpl is HostConnection: # on shard aware implementation we use submit function regardless - self.assertTrue(host.signal_connection_failure.call_args) - self.assertTrue(session.submit.called) + assert host.signal_connection_failure.call_args + assert session.submit.called else: self.assertFalse(session.submit.called) - self.assertTrue(session.cluster.signal_connection_failure.call_args) - self.assertTrue(pool.is_shutdown) + assert session.cluster.signal_connection_failure.call_args + assert pool.is_shutdown def test_return_closed_connection(self): host = Mock(spec=Host, address='ip1') @@ -196,7 +196,7 @@ def test_return_closed_connection(self): pool.return_connection(conn) # a new creation should be scheduled - self.assertTrue(session.submit.call_args) + assert session.submit.call_args self.assertFalse(pool.is_shutdown) def test_host_instantiations(self): diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 477e64590b..84a31fad9a 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -254,7 +254,7 @@ def test_nts_token_performance(self): nts.make_token_replica_map(token_to_host_owner, ring) elapsed_bad = timeit.default_timer() - start_time difference = elapsed_bad - elapsed_base - self.assertTrue(difference < 1 and difference > -1) + assert difference < 1 and difference > -1 def test_nts_make_token_replica_map_multi_rack(self): token_to_host_owner = {} @@ -782,9 +782,7 @@ def test_deterministic(self): query = self._aggregate_with_kwargs( deterministic=True ).as_cql_query(formatted=formatted) - self.assertTrue(query.endswith('DETERMINISTIC'), - msg="'DETERMINISTIC' not found in {}".format(query) - ) + assert query.endswith('DETERMINISTIC'), "'DETERMINISTIC' not found in {}".format(query) class HostsTests(unittest.TestCase): diff --git a/tests/unit/test_orderedmap.py b/tests/unit/test_orderedmap.py index b91529c589..fca04cbb32 100644 --- a/tests/unit/test_orderedmap.py +++ b/tests/unit/test_orderedmap.py @@ -44,10 +44,10 @@ def test_contains(self): om = OrderedMap(zip(keys, range(len(keys)))) for k in keys: - self.assertTrue(k in om) + assert k in om self.assertFalse(k not in om) - self.assertTrue('notthere' not in om) + assert 'notthere' not in om self.assertFalse('notthere' in om) def test_keys(self): diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index 77b1d21da7..834e4eea7a 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -931,7 +931,7 @@ def test_schedule_infinite_attempts(self): class ExponentialReconnectionPolicyTest(unittest.TestCase): def _assert_between(self, value, min, max): - self.assertTrue(min <= value <= max) + assert min <= value <= max def test_bad_vals(self): self.assertRaises(ValueError, ExponentialReconnectionPolicy, -1, 0) diff --git a/tests/unit/test_query.py b/tests/unit/test_query.py index 86f84c562e..27e268d117 100644 --- a/tests/unit/test_query.py +++ b/tests/unit/test_query.py @@ -30,7 +30,7 @@ def test_clear(self): batch = BatchStatement() batch.add(ss) - self.assertTrue(batch._statements_and_parameters) + assert batch._statements_and_parameters assert batch.keyspace == keyspace assert batch.routing_key == routing_key assert batch.custom_payload == custom_payload diff --git a/tests/unit/test_response_future.py b/tests/unit/test_response_future.py index 089a9f12cd..54d3e19b7a 100644 --- a/tests/unit/test_response_future.py +++ b/tests/unit/test_response_future.py @@ -582,7 +582,7 @@ def test_prepared_query_not_found(self): result = Mock(spec=PreparedQueryNotFound, info='a' * 16) rf._set_result(None, None, None, result) - self.assertTrue(session.submit.call_args) + assert session.submit.call_args args, kwargs = session.submit.call_args assert rf._reprepare == args[-5] assert isinstance(args[-4], PrepareMessage) diff --git a/tests/unit/test_resultset.py b/tests/unit/test_resultset.py index e72891c277..bdf8c621d6 100644 --- a/tests/unit/test_resultset.py +++ b/tests/unit/test_resultset.py @@ -74,7 +74,7 @@ def test_has_more_pages(self): response_future.has_more_pages.side_effect = PropertyMock(side_effect=(True, False)) rs = ResultSet(response_future, []) type(response_future).has_more_pages = PropertyMock(side_effect=(True, False)) # after init to avoid side effects being consumed by init - self.assertTrue(rs.has_more_pages) + assert rs.has_more_pages self.assertFalse(rs.has_more_pages) def test_iterate_then_index(self): @@ -124,7 +124,7 @@ def test_index_list_mode(self): self.assertListEqual(list(rs), expected) self.assertListEqual(list(rs), expected) - self.assertTrue(rs) + assert rs # pages response_future = Mock(has_more_pages=True, _continuous_paging_session=None) @@ -139,7 +139,7 @@ def test_index_list_mode(self): self.assertListEqual(list(rs), expected) self.assertListEqual(list(rs), expected) - self.assertTrue(rs) + assert rs def test_eq(self): # no pages @@ -152,7 +152,7 @@ def test_eq(self): # results can be iterated or indexed once we're materialized self.assertListEqual(list(rs), expected) assert rs[9] == expected[9] - self.assertTrue(rs) + assert rs # pages response_future = Mock(has_more_pages=True, _continuous_paging_session=None) @@ -165,11 +165,11 @@ def test_eq(self): # results can be iterated or indexed once we're materialized self.assertListEqual(list(rs), expected) assert rs[9] == expected[9] - self.assertTrue(rs) + assert rs def test_bool(self): self.assertFalse(ResultSet(Mock(has_more_pages=False), [])) - self.assertTrue(ResultSet(Mock(has_more_pages=False), [1])) + assert ResultSet(Mock(has_more_pages=False), [1]) def test_was_applied(self): # unknown row factory raises diff --git a/tests/unit/test_row_factories.py b/tests/unit/test_row_factories.py index d7d68b5b53..7787f1d271 100644 --- a/tests/unit/test_row_factories.py +++ b/tests/unit/test_row_factories.py @@ -83,5 +83,5 @@ def test_creation_no_warning_on_short_column_list(self): rows = named_tuple_factory(self.short_colnames, self.short_rows) assert len(w) == 0 # check that this is a real namedtuple - self.assertTrue(hasattr(rows[0], '_fields')) + assert hasattr(rows[0], '_fields') assert isinstance(rows[0], tuple) diff --git a/tests/unit/test_segment.py b/tests/unit/test_segment.py index 459b68dd3a..fb8ef0c6c5 100644 --- a/tests/unit/test_segment.py +++ b/tests/unit/test_segment.py @@ -174,7 +174,7 @@ def test_decode_multi_segments(self): headers.append(segment_codec_no_compression.decode_header(buffer)) segments.append(segment_codec_no_compression.decode(buffer, headers[1])) - self.assertTrue(all([h.is_self_contained is False for h in headers])) + assert all([h.is_self_contained is False for h in headers]) decoded_msg = segments[0].payload + segments[1].payload assert decoded_msg == self.large_msg diff --git a/tests/unit/test_sortedset.py b/tests/unit/test_sortedset.py index 50c2c0d6c1..2a063b4274 100644 --- a/tests/unit/test_sortedset.py +++ b/tests/unit/test_sortedset.py @@ -37,7 +37,7 @@ def test_contains(self): ss = sortedset(input) for i in expected: - self.assertTrue(i in ss) + assert i in ss self.assertFalse(i not in ss) hi = max(expected)+1 @@ -81,8 +81,8 @@ def __lt__(self, other): ss = sortedset([comparable(), o]) ss2 = ss.copy() assert id(ss) != id(ss2) - self.assertTrue(o in ss) - self.assertTrue(o in ss2) + assert o in ss + assert o in ss2 def test_isdisjoint(self): # set, ss @@ -92,20 +92,20 @@ def test_isdisjoint(self): ss13 = sortedset([1, 3]) ss3 = sortedset([3]) # s ss disjoint - self.assertTrue(s2.isdisjoint(ss1)) - self.assertTrue(s2.isdisjoint(ss13)) + assert s2.isdisjoint(ss1) + assert s2.isdisjoint(ss13) # s ss not disjoint self.assertFalse(s12.isdisjoint(ss1)) self.assertFalse(s12.isdisjoint(ss13)) # ss s disjoint - self.assertTrue(ss1.isdisjoint(s2)) - self.assertTrue(ss13.isdisjoint(s2)) + assert ss1.isdisjoint(s2) + assert ss13.isdisjoint(s2) # ss s not disjoint self.assertFalse(ss1.isdisjoint(s12)) self.assertFalse(ss13.isdisjoint(s12)) # ss ss disjoint - self.assertTrue(ss1.isdisjoint(ss3)) - self.assertTrue(ss3.isdisjoint(ss1)) + assert ss1.isdisjoint(ss3) + assert ss3.isdisjoint(ss1) # ss ss not disjoint self.assertFalse(ss1.isdisjoint(ss13)) self.assertFalse(ss13.isdisjoint(ss1)) @@ -118,8 +118,8 @@ def test_issubset(self): ss13 = sortedset([1, 3]) ss3 = sortedset([3]) - self.assertTrue(ss1.issubset(s12)) - self.assertTrue(ss1.issubset(ss13)) + assert ss1.issubset(s12) + assert ss1.issubset(ss13) self.assertFalse(ss1.issubset(ss3)) self.assertFalse(ss13.issubset(ss3)) @@ -132,9 +132,9 @@ def test_issuperset(self): ss13 = sortedset([1, 3]) ss3 = sortedset([3]) - self.assertTrue(s12.issuperset(ss1)) - self.assertTrue(ss13.issuperset(ss3)) - self.assertTrue(ss13.issuperset(ss13)) + assert s12.issuperset(ss1) + assert ss13.issuperset(ss3) + assert ss13.issuperset(ss13) self.assertFalse(s12.issuperset(ss13)) self.assertFalse(ss1.issuperset(ss3)) @@ -243,27 +243,27 @@ def test_operators(self): # __ne__ self.assertFalse(ss12 != ss12) self.assertFalse(ss12 != sortedset([1, 2])) - self.assertTrue(ss12 != sortedset()) + assert ss12 != sortedset() # __le__ - self.assertTrue(ss1 <= ss12) - self.assertTrue(ss12 <= ss12) + assert ss1 <= ss12 + assert ss12 <= ss12 self.assertFalse(ss12 <= ss1) # __lt__ - self.assertTrue(ss1 < ss12) + assert ss1 < ss12 self.assertFalse(ss12 < ss12) self.assertFalse(ss12 < ss1) # __ge__ self.assertFalse(ss1 >= ss12) - self.assertTrue(ss12 >= ss12) - self.assertTrue(ss12 >= ss1) + assert ss12 >= ss12 + assert ss12 >= ss1 # __gt__ self.assertFalse(ss1 > ss12) self.assertFalse(ss12 > ss12) - self.assertTrue(ss12 > ss1) + assert ss12 > ss1 # __and__ assert ss1 & ss12 == ss1 diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index 189781055c..9c9ade2a7b 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -191,17 +191,17 @@ class BarType(FooType): def test_parse_casstype_vector(self): ctype = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType, 3)") - self.assertTrue(issubclass(ctype, VectorType)) + assert issubclass(ctype, VectorType) assert 3 == ctype.vector_size assert FloatType == ctype.subtype def test_parse_casstype_vector_of_vectors(self): inner_type = "org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType, 4)" ctype = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(%s, 3)" % (inner_type)) - self.assertTrue(issubclass(ctype, VectorType)) + assert issubclass(ctype, VectorType) assert 3 == ctype.vector_size sub_ctype = ctype.subtype - self.assertTrue(issubclass(sub_ctype, VectorType)) + assert issubclass(sub_ctype, VectorType) assert 4 == sub_ctype.vector_size assert FloatType == sub_ctype.subtype diff --git a/tests/unit/test_util_types.py b/tests/unit/test_util_types.py index 19df42e311..c1d575f687 100644 --- a/tests/unit/test_util_types.py +++ b/tests/unit/test_util_types.py @@ -236,55 +236,55 @@ def test_version_compare(self): # just tests a bunch of versions # major wins - self.assertTrue(Version('3.3.0') > Version('2.5.0')) - self.assertTrue(Version('3.3.0') > Version('2.5.0.66')) - self.assertTrue(Version('3.3.0') > Version('2.5.21')) + assert Version('3.3.0') > Version('2.5.0') + assert Version('3.3.0') > Version('2.5.0.66') + assert Version('3.3.0') > Version('2.5.21') # minor wins - self.assertTrue(Version('2.3.0') > Version('2.2.0')) - self.assertTrue(Version('2.3.0') > Version('2.2.7')) - self.assertTrue(Version('2.3.0') > Version('2.2.7.9')) + assert Version('2.3.0') > Version('2.2.0') + assert Version('2.3.0') > Version('2.2.7') + assert Version('2.3.0') > Version('2.2.7.9') # patch wins - self.assertTrue(Version('2.3.1') > Version('2.3.0')) - self.assertTrue(Version('2.3.1') > Version('2.3.0.4post0')) - self.assertTrue(Version('2.3.1') > Version('2.3.0.44')) + assert Version('2.3.1') > Version('2.3.0') + assert Version('2.3.1') > Version('2.3.0.4post0') + assert Version('2.3.1') > Version('2.3.0.44') # various - self.assertTrue(Version('2.3.0.1') > Version('2.3.0.0')) - self.assertTrue(Version('2.3.0.680') > Version('2.3.0.670')) - self.assertTrue(Version('2.3.0.681') > Version('2.3.0.680')) - self.assertTrue(Version('2.3.0.1build0') > Version('2.3.0.1')) # 4th part fallback to str cmp - self.assertTrue(Version('2.3.0.build0') > Version('2.3.0.1')) # 4th part fallback to str cmp - self.assertTrue(Version('2.3.0') < Version('2.3.0.build')) - - self.assertTrue(Version('4-a') <= Version('4.0.0')) - self.assertTrue(Version('4-a') <= Version('4.0-alpha1')) - self.assertTrue(Version('4-a') <= Version('4.0-beta1')) - self.assertTrue(Version('4.0.0') >= Version('4.0.0')) - self.assertTrue(Version('4.0.0.421') >= Version('4.0.0')) - self.assertTrue(Version('4.0.1') >= Version('4.0.0')) - self.assertTrue(Version('2.3.0') == Version('2.3.0')) - self.assertTrue(Version('2.3.32') == Version('2.3.32')) - self.assertTrue(Version('2.3.32') == Version('2.3.32.0')) - self.assertTrue(Version('2.3.0.build') == Version('2.3.0.build')) - - self.assertTrue(Version('4') == Version('4.0.0')) - self.assertTrue(Version('4.0') == Version('4.0.0.0')) - self.assertTrue(Version('4.0') > Version('3.9.3')) - - self.assertTrue(Version('4.0') > Version('4.0-SNAPSHOT')) - self.assertTrue(Version('4.0-SNAPSHOT') == Version('4.0-SNAPSHOT')) - self.assertTrue(Version('4.0.0-SNAPSHOT') == Version('4.0-SNAPSHOT')) - self.assertTrue(Version('4.0.0-SNAPSHOT') == Version('4.0.0-SNAPSHOT')) - self.assertTrue(Version('4.0.0.build5-SNAPSHOT') == Version('4.0.0.build5-SNAPSHOT')) - self.assertTrue(Version('4.1-SNAPSHOT') > Version('4.0-SNAPSHOT')) - self.assertTrue(Version('4.0.1-SNAPSHOT') > Version('4.0.0-SNAPSHOT')) - self.assertTrue(Version('4.0.0.build6-SNAPSHOT') > Version('4.0.0.build5-SNAPSHOT')) - self.assertTrue(Version('4.0-SNAPSHOT2') > Version('4.0-SNAPSHOT1')) - self.assertTrue(Version('4.0-SNAPSHOT2') > Version('4.0.0-SNAPSHOT1')) - - self.assertTrue(Version('4.0.0-alpha1-SNAPSHOT') > Version('4.0.0-SNAPSHOT')) + assert Version('2.3.0.1') > Version('2.3.0.0') + assert Version('2.3.0.680') > Version('2.3.0.670') + assert Version('2.3.0.681') > Version('2.3.0.680') + assert Version('2.3.0.1build0') > Version('2.3.0.1') # 4th part fallback to str cmp + assert Version('2.3.0.build0') > Version('2.3.0.1') # 4th part fallback to str cmp + assert Version('2.3.0') < Version('2.3.0.build') + + assert Version('4-a') <= Version('4.0.0') + assert Version('4-a') <= Version('4.0-alpha1') + assert Version('4-a') <= Version('4.0-beta1') + assert Version('4.0.0') >= Version('4.0.0') + assert Version('4.0.0.421') >= Version('4.0.0') + assert Version('4.0.1') >= Version('4.0.0') + assert Version('2.3.0') == Version('2.3.0') + assert Version('2.3.32') == Version('2.3.32') + assert Version('2.3.32') == Version('2.3.32.0') + assert Version('2.3.0.build') == Version('2.3.0.build') + + assert Version('4') == Version('4.0.0') + assert Version('4.0') == Version('4.0.0.0') + assert Version('4.0') > Version('3.9.3') + + assert Version('4.0') > Version('4.0-SNAPSHOT') + assert Version('4.0-SNAPSHOT') == Version('4.0-SNAPSHOT') + assert Version('4.0.0-SNAPSHOT') == Version('4.0-SNAPSHOT') + assert Version('4.0.0-SNAPSHOT') == Version('4.0.0-SNAPSHOT') + assert Version('4.0.0.build5-SNAPSHOT') == Version('4.0.0.build5-SNAPSHOT') + assert Version('4.1-SNAPSHOT') > Version('4.0-SNAPSHOT') + assert Version('4.0.1-SNAPSHOT') > Version('4.0.0-SNAPSHOT') + assert Version('4.0.0.build6-SNAPSHOT') > Version('4.0.0.build5-SNAPSHOT') + assert Version('4.0-SNAPSHOT2') > Version('4.0-SNAPSHOT1') + assert Version('4.0-SNAPSHOT2') > Version('4.0.0-SNAPSHOT1') + + assert Version('4.0.0-alpha1-SNAPSHOT') > Version('4.0.0-SNAPSHOT') class FunctionTests(unittest.TestCase): From c6f5c7cd22685b3b341e1b9d7ffa085bb1c2824a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 19:56:52 +0200 Subject: [PATCH 048/298] Replace self.assertNotIn with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used commands: ``` ast-grep run --pattern 'self.assertNotIn($A, $B)' --rewrite 'assert $A not in $B' --lang python ./tests -U ast-grep run --pattern 'self.assertNotIn($A, $B, $C)' --rewrite 'assert $A not in $B, $C' --lang python ./tests -U ``` --- .../cqlengine/management/test_management.py | 14 ++--- .../cqlengine/model/test_polymorphism.py | 2 +- .../statements/test_assignment_clauses.py | 2 +- .../statements/test_select_statement.py | 6 +- .../integration/cqlengine/test_ifnotexists.py | 2 +- tests/integration/cqlengine/test_ttl.py | 6 +- .../long/test_loadbalancingpolicies.py | 2 +- .../integration/simulacron/test_connection.py | 2 +- tests/integration/standard/test_metadata.py | 60 +++++++++---------- .../integration/standard/test_query_paging.py | 2 +- tests/integration/standard/test_udts.py | 4 +- tests/unit/advanced/test_graph.py | 4 +- tests/unit/advanced/test_metadata.py | 13 ++-- tests/unit/advanced/test_policies.py | 2 +- tests/unit/test_host_connection_pool.py | 2 +- tests/unit/test_metadata.py | 29 ++++----- tests/unit/test_policies.py | 2 +- tests/unit/test_sortedset.py | 2 +- 18 files changed, 72 insertions(+), 84 deletions(-) diff --git a/tests/integration/cqlengine/management/test_management.py b/tests/integration/cqlengine/management/test_management.py index 7e9925df87..8944a35566 100644 --- a/tests/integration/cqlengine/management/test_management.py +++ b/tests/integration/cqlengine/management/test_management.py @@ -39,20 +39,20 @@ def test_create_drop_succeeeds(self): cluster = get_cluster() keyspace_ss = 'test_ks_ss' - self.assertNotIn(keyspace_ss, cluster.metadata.keyspaces) + assert keyspace_ss not in cluster.metadata.keyspaces management.create_keyspace_simple(keyspace_ss, 2) assert keyspace_ss in cluster.metadata.keyspaces management.drop_keyspace(keyspace_ss) - self.assertNotIn(keyspace_ss, cluster.metadata.keyspaces) + assert keyspace_ss not in cluster.metadata.keyspaces keyspace_nts = 'test_ks_nts' - self.assertNotIn(keyspace_nts, cluster.metadata.keyspaces) + assert keyspace_nts not in cluster.metadata.keyspaces management.create_keyspace_network_topology(keyspace_nts, {'dc1': 1}) assert keyspace_nts in cluster.metadata.keyspaces management.drop_keyspace(keyspace_nts) - self.assertNotIn(keyspace_nts, cluster.metadata.keyspaces) + assert keyspace_nts not in cluster.metadata.keyspaces class DropTableTest(BaseCassEngTestCase): @@ -188,7 +188,7 @@ def test_add_column(self): assert len(meta_columns) == 5 assert len(ThirdModel._columns) == 4 assert 'fourth_key' in meta_columns - self.assertNotIn('fourth_key', ThirdModel._columns) + assert 'fourth_key' not in ThirdModel._columns assert 'blah' in ThirdModel._columns assert 'blah' in meta_columns @@ -197,9 +197,9 @@ def test_add_column(self): assert len(meta_columns) == 5 assert len(ThirdModel._columns) == 4 assert 'fourth_key' in meta_columns - self.assertNotIn('fourth_key', FourthModel._columns) + assert 'fourth_key' not in FourthModel._columns assert 'renamed' in FourthModel._columns - self.assertNotIn('renamed', meta_columns) + assert 'renamed' not in meta_columns assert 'blah' in meta_columns diff --git a/tests/integration/cqlengine/model/test_polymorphism.py b/tests/integration/cqlengine/model/test_polymorphism.py index ddee177172..e59ff86d09 100644 --- a/tests/integration/cqlengine/model/test_polymorphism.py +++ b/tests/integration/cqlengine/model/test_polymorphism.py @@ -154,7 +154,7 @@ def test_delete_on_subclass_does_not_include_disc_value(self): # not sure how we would even get here if it was in there # since the CQL would fail. - self.assertNotIn("row_type", m.call_args[0][0].query_string) + assert "row_type" not in m.call_args[0][0].query_string class UnindexedInheritBase(models.Model): diff --git a/tests/integration/cqlengine/statements/test_assignment_clauses.py b/tests/integration/cqlengine/statements/test_assignment_clauses.py index 91a99b036b..dce910fd5e 100644 --- a/tests/integration/cqlengine/statements/test_assignment_clauses.py +++ b/tests/integration/cqlengine/statements/test_assignment_clauses.py @@ -295,7 +295,7 @@ def test_nulled_columns_arent_included(self): c._analyze() c.set_context_id(0) - self.assertNotIn(1, c._updates) + assert 1 not in c._updates class CounterUpdateTests(unittest.TestCase): diff --git a/tests/integration/cqlengine/statements/test_select_statement.py b/tests/integration/cqlengine/statements/test_select_statement.py index 9a6fc2e582..b4bada1eb0 100644 --- a/tests/integration/cqlengine/statements/test_select_statement.py +++ b/tests/integration/cqlengine/statements/test_select_statement.py @@ -51,7 +51,7 @@ def test_count(self): ss.add_where(Column(db_field='a'), EqualsOperator(), 'b') assert str(ss) == 'SELECT COUNT(*) FROM table WHERE "a" = %(0)s LIMIT 10', str(ss) assert 'LIMIT' in str(ss) - self.assertNotIn('ORDER', str(ss)) + assert 'ORDER' not in str(ss) def test_distinct(self): ss = SelectStatement('table', distinct_fields=['field2']) @@ -100,8 +100,8 @@ def test_limit_rendering(self): ss = SelectStatement('table', None, limit=0) qstr = str(ss) - self.assertNotIn('LIMIT', qstr) + assert 'LIMIT' not in qstr ss = SelectStatement('table', None, limit=None) qstr = str(ss) - self.assertNotIn('LIMIT', qstr) + assert 'LIMIT' not in qstr diff --git a/tests/integration/cqlengine/test_ifnotexists.py b/tests/integration/cqlengine/test_ifnotexists.py index 0a343613f7..9c41f37802 100644 --- a/tests/integration/cqlengine/test_ifnotexists.py +++ b/tests/integration/cqlengine/test_ifnotexists.py @@ -188,7 +188,7 @@ def test_if_not_exists_is_not_include_with_query_on_update(self): o.save() query = m.call_args[0][0].query_string - self.assertNotIn("IF NOT EXIST", query) + assert "IF NOT EXIST" not in query class IfNotExistWithCounterTest(BaseIfNotExistsWithCounterTest): diff --git a/tests/integration/cqlengine/test_ttl.py b/tests/integration/cqlengine/test_ttl.py index 05fe7772f9..df1afb6bf0 100644 --- a/tests/integration/cqlengine/test_ttl.py +++ b/tests/integration/cqlengine/test_ttl.py @@ -186,7 +186,7 @@ def test_default_ttl_not_set(self): TestTTLModel.objects(id=tid).update(text="aligators") query = m.call_args[0][0].query_string - self.assertNotIn("USING TTL", query) + assert "USING TTL" not in query def test_default_ttl_set(self): session = get_session() @@ -205,7 +205,7 @@ def test_default_ttl_set(self): # Should not be set either query = m.call_args[0][0].query_string - self.assertNotIn("USING TTL", query) + assert "USING TTL" not in query def test_default_ttl_modify(self): session = get_session() @@ -235,4 +235,4 @@ def test_override_default_ttl(self): TestDefaultTTLModel.objects(id=tid).ttl(None).update(text="aligators expired") query = m.call_args[0][0].query_string - self.assertNotIn("USING TTL", query) + assert "USING TTL" not in query diff --git a/tests/integration/long/test_loadbalancingpolicies.py b/tests/integration/long/test_loadbalancingpolicies.py index af3cd822ff..884e20beaf 100644 --- a/tests/integration/long/test_loadbalancingpolicies.py +++ b/tests/integration/long/test_loadbalancingpolicies.py @@ -750,7 +750,7 @@ def test_black_list_with_host_filter_policy(self): session = cluster.connect() self._wait_for_nodes_up([1, 2, 3]) - self.assertNotIn(ignored_address, [h.address for h in hfp.make_query_plan()]) + assert ignored_address not in [h.address for h in hfp.make_query_plan()] create_schema(cluster, session, keyspace) self._insert(session, keyspace) diff --git a/tests/integration/simulacron/test_connection.py b/tests/integration/simulacron/test_connection.py index 3cefad9709..bba87f0ea0 100644 --- a/tests/integration/simulacron/test_connection.py +++ b/tests/integration/simulacron/test_connection.py @@ -153,7 +153,7 @@ def test_heart_beat_timeout(self): assert host in listener.hosts_marked_down # In this case HostConnection._replace shouldn't be called - self.assertNotIn("_replace", executor.called_functions) + assert "_replace" not in executor.called_functions def test_callbacks_and_pool_when_oto(self): """ diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 4334491109..2b4c29f268 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -571,8 +571,8 @@ def test_non_size_tiered_compaction(self): assert "LeveledCompactionStrategy" in cql # formerly legacy options; reintroduced in 4.0 if CASSANDRA_VERSION < Version('4.0-a'): - self.assertNotIn("min_threshold", cql) - self.assertNotIn("max_threshold", cql) + assert "min_threshold" not in cql + assert "max_threshold" not in cql @requires_java_udf def test_refresh_schema_metadata(self): @@ -596,11 +596,11 @@ def test_refresh_schema_metadata(self): cluster2 = TestCluster(schema_event_refresh_window=-1) cluster2.connect() - self.assertNotIn("new_keyspace", cluster2.metadata.keyspaces) + assert "new_keyspace" not in cluster2.metadata.keyspaces # Cluster metadata modification self.session.execute("CREATE KEYSPACE new_keyspace WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}") - self.assertNotIn("new_keyspace", cluster2.metadata.keyspaces) + assert "new_keyspace" not in cluster2.metadata.keyspaces cluster2.refresh_schema_metadata() assert "new_keyspace" in cluster2.metadata.keyspaces @@ -617,7 +617,7 @@ def test_refresh_schema_metadata(self): cluster2.refresh_schema_metadata() self.session.execute("ALTER TABLE {0}.{1} ADD c double".format(self.keyspace_name, table_name)) - self.assertNotIn("c", cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns) + assert "c" not in cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns cluster2.refresh_schema_metadata() assert "c" in cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns @@ -655,7 +655,7 @@ def test_refresh_schema_metadata(self): assert "new_keyspace" in cluster2.metadata.keyspaces cluster2.refresh_schema_metadata() - self.assertNotIn("new_keyspace", cluster2.metadata.keyspaces) + assert "new_keyspace" not in cluster2.metadata.keyspaces cluster2.shutdown() @@ -710,9 +710,9 @@ def test_refresh_table_metadata(self): cluster2 = TestCluster(schema_event_refresh_window=-1) cluster2.connect() - self.assertNotIn("c", cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns) + assert "c" not in cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns self.session.execute("ALTER TABLE {0}.{1} ADD c double".format(self.keyspace_name, table_name)) - self.assertNotIn("c", cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns) + assert "c" not in cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns cluster2.refresh_table_metadata(self.keyspace_name, table_name) assert "c" in cluster2.metadata.keyspaces[self.keyspace_name].tables[table_name].columns @@ -745,11 +745,11 @@ def test_refresh_metadata_for_mv(self): cluster2.connect() try: - self.assertNotIn("mv1", cluster2.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) + assert "mv1" not in cluster2.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views self.session.execute("CREATE MATERIALIZED VIEW {0}.mv1 AS SELECT a, b FROM {0}.{1} " "WHERE a IS NOT NULL AND b IS NOT NULL PRIMARY KEY (a, b)" .format(self.keyspace_name, self.function_table_name)) - self.assertNotIn("mv1", cluster2.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) + assert "mv1" not in cluster2.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views cluster2.refresh_table_metadata(self.keyspace_name, "mv1") assert "mv1" in cluster2.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views @@ -768,13 +768,13 @@ def test_refresh_metadata_for_mv(self): cluster3 = TestCluster(schema_event_refresh_window=-1) cluster3.connect() try: - self.assertNotIn("mv2", cluster3.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) + assert "mv2" not in cluster3.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views self.session.execute( "CREATE MATERIALIZED VIEW {0}.mv2 AS SELECT a, b FROM {0}.{1} " "WHERE a IS NOT NULL AND b IS NOT NULL PRIMARY KEY (a, b)".format( self.keyspace_name, self.function_table_name) ) - self.assertNotIn("mv2", cluster3.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) + assert "mv2" not in cluster3.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views cluster3.refresh_materialized_view_metadata(self.keyspace_name, 'mv2') assert "mv2" in cluster3.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views finally: @@ -1021,13 +1021,13 @@ class Ext1(Ext0): new_cql = table_meta.export_as_string() assert new_cql != original_table_cql assert Ext0.after_table_cql(table_meta, Ext0.name, ext_map[Ext0.name]) in new_cql - self.assertNotIn(Ext1.name, new_cql) + assert Ext1.name not in new_cql assert Ext0.name in view_meta.extensions new_cql = view_meta.export_as_string() assert new_cql != original_view_cql assert Ext0.after_table_cql(view_meta, Ext0.name, ext_map[Ext0.name]) in new_cql - self.assertNotIn(Ext1.name, new_cql) + assert Ext1.name not in new_cql # extensions registered, one present # -------------------------------------- @@ -1454,10 +1454,10 @@ def test_index_updates(self): ks_meta = self.cluster.metadata.keyspaces[self.keyspace_name] table_meta = ks_meta.tables[self.table_name] - self.assertNotIn('a_idx', ks_meta.indexes) - self.assertNotIn('b_idx', ks_meta.indexes) - self.assertNotIn('a_idx', table_meta.indexes) - self.assertNotIn('b_idx', table_meta.indexes) + assert 'a_idx' not in ks_meta.indexes + assert 'b_idx' not in ks_meta.indexes + assert 'a_idx' not in table_meta.indexes + assert 'b_idx' not in table_meta.indexes self.session.execute("CREATE INDEX a_idx ON %s (a)" % self.table_name) self.session.execute("ALTER TABLE %s ADD b int" % self.table_name) @@ -1478,17 +1478,17 @@ def test_index_updates(self): ks_meta = self.cluster.metadata.keyspaces[self.keyspace_name] table_meta = ks_meta.tables[self.table_name] - self.assertNotIn('a_idx', ks_meta.indexes) + assert 'a_idx' not in ks_meta.indexes assert isinstance(ks_meta.indexes['b_idx'], IndexMetadata) - self.assertNotIn('a_idx', table_meta.indexes) + assert 'a_idx' not in table_meta.indexes assert isinstance(table_meta.indexes['b_idx'], IndexMetadata) # keyspace index updated when table dropped self.drop_basic_table() ks_meta = self.cluster.metadata.keyspaces[self.keyspace_name] - self.assertNotIn(self.table_name, ks_meta.tables) - self.assertNotIn('a_idx', ks_meta.indexes) - self.assertNotIn('b_idx', ks_meta.indexes) + assert self.table_name not in ks_meta.tables + assert 'a_idx' not in ks_meta.indexes + assert 'b_idx' not in ks_meta.indexes def test_index_follows_alter(self): self.create_basic_table() @@ -1619,7 +1619,7 @@ def test_functions_after_udt(self): @test_category function """ - self.assertNotIn(self.function_name, self.keyspace_function_meta) + assert self.function_name not in self.keyspace_function_meta udt_name = 'udtx' self.session.execute("CREATE TYPE %s (x int)" % udt_name) @@ -1629,7 +1629,7 @@ def test_functions_after_udt(self): keyspace_cql = self.cluster.metadata.keyspaces[self.keyspace_name].export_as_string() type_idx = keyspace_cql.rfind("CREATE TYPE") func_idx = keyspace_cql.find("CREATE FUNCTION") - self.assertNotIn(-1, (type_idx, func_idx), "TYPE or FUNCTION not found in keyspace_cql: " + keyspace_cql) + assert -1 not in (type_idx, func_idx), "TYPE or FUNCTION not found in keyspace_cql: " + keyspace_cql self.assertGreater(func_idx, type_idx) def test_function_same_name_diff_types(self): @@ -1866,7 +1866,7 @@ def test_aggregates_after_functions(self): keyspace_cql = self.cluster.metadata.keyspaces[self.keyspace_name].export_as_string() func_idx = keyspace_cql.find("CREATE FUNCTION") aggregate_idx = keyspace_cql.rfind("CREATE AGGREGATE") - self.assertNotIn(-1, (aggregate_idx, func_idx), "AGGREGATE or FUNCTION not found in keyspace_cql: " + keyspace_cql) + assert -1 not in (aggregate_idx, func_idx), "AGGREGATE or FUNCTION not found in keyspace_cql: " + keyspace_cql self.assertGreater(aggregate_idx, func_idx) def test_same_name_diff_types(self): @@ -1976,7 +1976,7 @@ def test_cql_optional_params(self): cql = meta.as_cql_query() init_cond_idx = cql.find("INITCOND %s" % kwargs['initial_condition']) final_func_idx = cql.find('FINALFUNC "%s"' % kwargs['final_func']) - self.assertNotIn(-1, (init_cond_idx, final_func_idx)) + assert -1 not in (init_cond_idx, final_func_idx) self.assertGreater(init_cond_idx, final_func_idx) @@ -2197,8 +2197,8 @@ def test_materialized_view_metadata_drop(self): self.session.execute("DROP MATERIALIZED VIEW {0}.mv1".format(self.keyspace_name)) - self.assertNotIn("mv1", self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) - self.assertNotIn("mv1", self.cluster.metadata.keyspaces[self.keyspace_name].views) + assert "mv1" not in self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views + assert "mv1" not in self.cluster.metadata.keyspaces[self.keyspace_name].views self.assertDictEqual({}, self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) self.assertDictEqual({}, self.cluster.metadata.keyspaces[self.keyspace_name].views) @@ -2551,7 +2551,7 @@ def test_group_keys_by_host(self): def _assert_group_keys_by_host(self, keys, table_name, stmt): keys_per_host = group_keys_by_replica(self.session, self.ks_name, table_name, keys) - self.assertNotIn(NO_VALID_REPLICA, keys_per_host) + assert NO_VALID_REPLICA not in keys_per_host prepared_stmt = self.session.prepare(stmt) for key in keys: diff --git a/tests/integration/standard/test_query_paging.py b/tests/integration/standard/test_query_paging.py index 968cc908c8..1d2a40e0b3 100644 --- a/tests/integration/standard/test_query_paging.py +++ b/tests/integration/standard/test_query_paging.py @@ -86,7 +86,7 @@ def test_paging_state(self): result_set = self.session.execute("SELECT * FROM test3rf.test") while(result_set.has_more_pages): for row in result_set.current_rows: - self.assertNotIn(row, list_all_results) + assert row not in list_all_results list_all_results.extend(result_set.current_rows) page_state = result_set.paging_state result_set = self.session.execute("SELECT * FROM test3rf.test", paging_state=page_state) diff --git a/tests/integration/standard/test_udts.py b/tests/integration/standard/test_udts.py index bb04ce2f36..37ed4cc38a 100644 --- a/tests/integration/standard/test_udts.py +++ b/tests/integration/standard/test_udts.py @@ -67,7 +67,7 @@ def test_non_frozen_udts(self): result = self.session.execute("SELECT * FROM {0}".format(self.function_table_name)) self.assertFalse(result.one().b.has_corn) table_sql = self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].as_cql_query() - self.assertNotIn("", table_sql) + assert "" not in table_sql def test_can_insert_unprepared_registered_udts(self): """ @@ -686,7 +686,7 @@ def test_type_alteration(self): """ s = self.session type_name = "type_name" - self.assertNotIn(type_name, s.cluster.metadata.keyspaces['udttests'].user_types) + assert type_name not in s.cluster.metadata.keyspaces['udttests'].user_types s.execute('CREATE TYPE %s (v0 int)' % (type_name,)) assert type_name in s.cluster.metadata.keyspaces['udttests'].user_types diff --git a/tests/unit/advanced/test_graph.py b/tests/unit/advanced/test_graph.py index d76335da65..8e1012845b 100644 --- a/tests/unit/advanced/test_graph.py +++ b/tests/unit/advanced/test_graph.py @@ -339,8 +339,8 @@ def test_consistency_levels(self): # empty by default new_opts = GraphOptions() opt_map = new_opts.get_options_map() - self.assertNotIn('graph-read-consistency', opt_map) - self.assertNotIn('graph-write-consistency', opt_map) + assert 'graph-read-consistency' not in opt_map + assert 'graph-write-consistency' not in opt_map # set from other opt_map = new_opts.get_options_map(opts) diff --git a/tests/unit/advanced/test_metadata.py b/tests/unit/advanced/test_metadata.py index de3fd6bf7c..a3c47ab524 100644 --- a/tests/unit/advanced/test_metadata.py +++ b/tests/unit/advanced/test_metadata.py @@ -49,10 +49,7 @@ def _create_table_metadata(self, with_vertex=False, with_edge=False): def test_keyspace_no_graph_engine(self): km = self._create_keyspace_metadata(None) assert km.graph_engine == None - self.assertNotIn( - "graph_engine", - km.as_cql_query() - ) + assert "graph_engine" not in km.as_cql_query() def test_keyspace_with_graph_engine(self): graph_engine = 'Core' @@ -67,8 +64,8 @@ def test_table_no_vertex_or_edge(self): assert tm.vertex is None assert tm.edge is None cql = tm.as_cql_query() - self.assertNotIn("VERTEX LABEL", cql) - self.assertNotIn("EDGE LABEL", cql) + assert "VERTEX LABEL" not in cql + assert "EDGE LABEL" not in cql def test_table_with_vertex(self): tm = self._create_table_metadata(with_vertex=True) @@ -76,14 +73,14 @@ def test_table_with_vertex(self): assert tm.edge is None cql = tm.as_cql_query() assert "VERTEX LABEL" in cql - self.assertNotIn("EDGE LABEL", cql) + assert "EDGE LABEL" not in cql def test_table_with_edge(self): tm = self._create_table_metadata(with_edge=True) assert tm.vertex is None assert isinstance(tm.edge, EdgeMetadata) cql = tm.as_cql_query() - self.assertNotIn("VERTEX LABEL", cql) + assert "VERTEX LABEL" not in cql assert "EDGE LABEL" in cql assert "FROM from_label" in cql assert "TO to_label" in cql diff --git a/tests/unit/advanced/test_policies.py b/tests/unit/advanced/test_policies.py index d41791959f..8e421a859d 100644 --- a/tests/unit/advanced/test_policies.py +++ b/tests/unit/advanced/test_policies.py @@ -83,7 +83,7 @@ def test_target_host_down(self): target_host.is_up = False policy.on_down(target_host) query_plan = list(policy.make_query_plan(None, Mock(target_host='127.0.0.1'))) - self.assertNotIn(target_host, query_plan) + assert target_host not in query_plan def test_target_host_nominal(self): node_count = 4 diff --git a/tests/unit/test_host_connection_pool.py b/tests/unit/test_host_connection_pool.py index 922a5df7e7..5246b7a218 100644 --- a/tests/unit/test_host_connection_pool.py +++ b/tests/unit/test_host_connection_pool.py @@ -59,7 +59,7 @@ def test_borrow_and_return(self): pool.return_connection(conn) assert 0 == conn.in_flight if not self.uses_single_connection: - self.assertNotIn(conn, pool._trash) + assert conn not in pool._trash def test_failed_wait_for_connection(self): host = Mock(spec=Host, address='ip1') diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 84a31fad9a..052830d058 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -710,13 +710,10 @@ def _function_with_kwargs(self, **kwargs): ) def test_non_monotonic(self): - self.assertNotIn( - 'MONOTONIC', - self._function_with_kwargs( - monotonic=False, - monotonic_on=() - ).export_as_string() - ) + assert 'MONOTONIC' not in self._function_with_kwargs( + monotonic=False, + monotonic_on=() + ).export_as_string() def test_monotonic_all(self): mono_function = self._function_with_kwargs( @@ -735,12 +732,9 @@ def test_monotonic_one(self): assert 'MONOTONIC ON x\n LANG' in mono_on_function.as_cql_query(formatted=True) def test_nondeterministic(self): - self.assertNotIn( - 'DETERMINISTIC', - self._function_with_kwargs( - deterministic=False - ).as_cql_query(formatted=False) - ) + assert 'DETERMINISTIC' not in self._function_with_kwargs( + deterministic=False + ).as_cql_query(formatted=False) def test_deterministic(self): assert 'DETERMINISTIC' in self._function_with_kwargs( @@ -770,12 +764,9 @@ def _aggregate_with_kwargs(self, **kwargs): ) def test_nondeterministic(self): - self.assertNotIn( - 'DETERMINISTIC', - self._aggregate_with_kwargs( - deterministic=False - ).as_cql_query(formatted=True) - ) + assert 'DETERMINISTIC' not in self._aggregate_with_kwargs( + deterministic=False + ).as_cql_query(formatted=True) def test_deterministic(self): for formatted in (True, False): diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index 834e4eea7a..8a9d1e8b56 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -635,7 +635,7 @@ def get_replicas(keyspace, packed_key): assert qplan[0].datacenter == "dc1" # then the local non-replica - self.assertNotIn(qplan[1], replicas) + assert qplan[1] not in replicas assert qplan[1].datacenter == "dc1" # then one of the remotes (used_hosts_per_remote_dc is 1, so we diff --git a/tests/unit/test_sortedset.py b/tests/unit/test_sortedset.py index 2a063b4274..381b1eaa45 100644 --- a/tests/unit/test_sortedset.py +++ b/tests/unit/test_sortedset.py @@ -378,7 +378,7 @@ def _test_uncomparable_types(self, items): for x in ss: assert x in ss ss.remove(x) - self.assertNotIn(x, ss) + assert x not in ss def test_uncomparable_types_with_tuples(self): # PYTHON-1087 - make set handle uncomparable types From 26e90ca3e5cf73d7eec506772dbf0739aba21dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 20:19:21 +0200 Subject: [PATCH 049/298] Replace self.assertFalse with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used commands: ``` ast-grep run --pattern 'self.assertFalse($A)' --rewrite 'assert not $A' --lang python ./tests -U ast-grep run --pattern 'self.assertFalse($A, $B)' --rewrite 'assert not $A, $B' --lang python ./tests -U ``` --- tests/integration/cqlengine/base.py | 3 +- .../columns/test_container_columns.py | 2 +- .../management/test_compaction_settings.py | 4 +- .../model/test_class_construction.py | 2 +- .../cqlengine/query/test_updates.py | 2 +- .../statements/test_update_statement.py | 8 +-- .../integration/cqlengine/test_batch_query.py | 2 +- tests/integration/long/test_failure_types.py | 2 +- .../long/test_loadbalancingpolicies.py | 4 +- .../integration/long/test_topology_change.py | 2 +- tests/integration/standard/test_cluster.py | 36 +++++------ tests/integration/standard/test_concurrent.py | 16 ++--- tests/integration/standard/test_connection.py | 4 +- .../standard/test_cython_protocol_handlers.py | 2 +- tests/integration/standard/test_metadata.py | 15 ++--- tests/integration/standard/test_query.py | 18 +++--- .../integration/standard/test_query_paging.py | 20 +++---- tests/integration/standard/test_udts.py | 4 +- tests/stress_tests/test_multi_inserts.py | 2 +- tests/unit/advanced/test_geometry.py | 2 +- tests/unit/advanced/test_graph.py | 16 ++--- tests/unit/column_encryption/test_policies.py | 8 +-- tests/unit/cqlengine/test_columns.py | 2 +- tests/unit/cqlengine/test_connection.py | 5 +- tests/unit/io/test_asyncioreactor.py | 2 +- tests/unit/io/test_twistedreactor.py | 2 +- tests/unit/io/utils.py | 16 ++--- tests/unit/test_cluster.py | 4 +- tests/unit/test_concurrent.py | 2 +- tests/unit/test_control_connection.py | 14 ++--- tests/unit/test_host_connection_pool.py | 6 +- tests/unit/test_metadata.py | 2 +- tests/unit/test_orderedmap.py | 8 +-- tests/unit/test_query.py | 8 +-- tests/unit/test_response_future.py | 4 +- tests/unit/test_resultset.py | 12 ++-- tests/unit/test_sortedset.py | 60 +++++++++---------- tests/unit/test_util_types.py | 2 +- 38 files changed, 158 insertions(+), 165 deletions(-) diff --git a/tests/integration/cqlengine/base.py b/tests/integration/cqlengine/base.py index 13aa64f17b..ee1d79ff8c 100644 --- a/tests/integration/cqlengine/base.py +++ b/tests/integration/cqlengine/base.py @@ -45,8 +45,7 @@ def assertHasAttr(self, obj, attr): assert hasattr(obj, attr), "{0} doesn't have attribute: {1}".format(obj, attr) def assertNotHasAttr(self, obj, attr): - self.assertFalse(hasattr(obj, attr), - "{0} shouldn't have the attribute: {1}".format(obj, attr)) + assert not hasattr(obj, attr), "{0} shouldn't have the attribute: {1}".format(obj, attr) if sys.version_info > (3, 0): def assertItemsEqual(self, first, second, msg=None): diff --git a/tests/integration/cqlengine/columns/test_container_columns.py b/tests/integration/cqlengine/columns/test_container_columns.py index 4dc05e2569..e0a30d8b6c 100644 --- a/tests/integration/cqlengine/columns/test_container_columns.py +++ b/tests/integration/cqlengine/columns/test_container_columns.py @@ -142,7 +142,7 @@ def test_element_count_validation(self): del tb except OperationTimedOut: #This will happen if the host is remote - self.assertFalse(CASSANDRA_IP.startswith("127.0.0.")) + assert not CASSANDRA_IP.startswith("127.0.0.") self.assertRaises(ValidationError, TestSetModel.create, **{'text_set': set(str(uuid4()) for i in range(65536))}) def test_partial_updates(self): diff --git a/tests/integration/cqlengine/management/test_compaction_settings.py b/tests/integration/cqlengine/management/test_compaction_settings.py index 97872e86db..f705949cfb 100644 --- a/tests/integration/cqlengine/management/test_compaction_settings.py +++ b/tests/integration/cqlengine/management/test_compaction_settings.py @@ -53,7 +53,7 @@ class LeveledCompactionChangesDetectionTest(Model): drop_table(LeveledCompactionChangesDetectionTest) sync_table(LeveledCompactionChangesDetectionTest) - self.assertFalse(_update_options(LeveledCompactionChangesDetectionTest)) + assert not _update_options(LeveledCompactionChangesDetectionTest) def test_compaction_not_altered_without_changes_sizetiered(self): class SizeTieredCompactionChangesDetectionTest(Model): @@ -71,7 +71,7 @@ class SizeTieredCompactionChangesDetectionTest(Model): drop_table(SizeTieredCompactionChangesDetectionTest) sync_table(SizeTieredCompactionChangesDetectionTest) - self.assertFalse(_update_options(SizeTieredCompactionChangesDetectionTest)) + assert not _update_options(SizeTieredCompactionChangesDetectionTest) def test_alter_actually_alters(self): tmp = copy.deepcopy(LeveledCompactionTestTable) diff --git a/tests/integration/cqlengine/model/test_class_construction.py b/tests/integration/cqlengine/model/test_class_construction.py index 50fbf38290..9f31bc0168 100644 --- a/tests/integration/cqlengine/model/test_class_construction.py +++ b/tests/integration/cqlengine/model/test_class_construction.py @@ -171,7 +171,7 @@ class ModelWithPartitionKeys(Model): cols = ModelWithPartitionKeys._columns assert cols['c1'].primary_key - self.assertFalse(cols['c1'].partition_key) + assert not cols['c1'].partition_key assert cols['p1'].primary_key assert cols['p1'].partition_key diff --git a/tests/integration/cqlengine/query/test_updates.py b/tests/integration/cqlengine/query/test_updates.py index 9b7d5174df..5ffc2da6f5 100644 --- a/tests/integration/cqlengine/query/test_updates.py +++ b/tests/integration/cqlengine/query/test_updates.py @@ -308,7 +308,7 @@ def test_an_extra_delete_is_not_sent(self): obj = TestQueryUpdateModel.objects( partition=partition, cluster=cluster).first() - self.assertFalse({k: v for (k, v) in obj._values.items() if v.deleted}) + assert not {k: v for (k, v) in obj._values.items() if v.deleted} obj.text = 'foo' obj.save() diff --git a/tests/integration/cqlengine/statements/test_update_statement.py b/tests/integration/cqlengine/statements/test_update_statement.py index 338d92872a..6529b73558 100644 --- a/tests/integration/cqlengine/statements/test_update_statement.py +++ b/tests/integration/cqlengine/statements/test_update_statement.py @@ -68,19 +68,19 @@ def test_update_set_add(self): def test_update_empty_set_add_does_not_assign(self): us = UpdateStatement('table') us.add_update(Set(Text, db_field='a'), set(), 'add') - self.assertFalse(us.assignments) + assert not us.assignments def test_update_empty_set_removal_does_not_assign(self): us = UpdateStatement('table') us.add_update(Set(Text, db_field='a'), set(), 'remove') - self.assertFalse(us.assignments) + assert not us.assignments def test_update_list_prepend_with_empty_list(self): us = UpdateStatement('table') us.add_update(List(Text, db_field='a'), [], 'prepend') - self.assertFalse(us.assignments) + assert not us.assignments def test_update_list_append_with_empty_list(self): us = UpdateStatement('table') us.add_update(List(Text, db_field='a'), [], 'append') - self.assertFalse(us.assignments) + assert not us.assignments diff --git a/tests/integration/cqlengine/test_batch_query.py b/tests/integration/cqlengine/test_batch_query.py index 9647643a1b..e8f47642b7 100644 --- a/tests/integration/cqlengine/test_batch_query.py +++ b/tests/integration/cqlengine/test_batch_query.py @@ -250,4 +250,4 @@ def my_callback(*args, **kwargs): batch.add_callback(my_callback) batch.execute() batch.execute() - self.assertFalse(w) + assert not w diff --git a/tests/integration/long/test_failure_types.py b/tests/integration/long/test_failure_types.py index 920f98731e..7ec9a14c50 100644 --- a/tests/integration/long/test_failure_types.py +++ b/tests/integration/long/test_failure_types.py @@ -404,4 +404,4 @@ def test_async_timeouts(self): # check timeout and ensure it's within a reasonable range self.assertAlmostEqual(expected_time, total_time, delta=.05) assert mock_errorback.called - self.assertFalse(mock_callback.called) + assert not mock_callback.called diff --git a/tests/integration/long/test_loadbalancingpolicies.py b/tests/integration/long/test_loadbalancingpolicies.py index 884e20beaf..8a85c579cb 100644 --- a/tests/integration/long/test_loadbalancingpolicies.py +++ b/tests/integration/long/test_loadbalancingpolicies.py @@ -711,7 +711,7 @@ def test_white_list(self): # white list policy should not allow reconnecting to ignored hosts force_stop(3) self._wait_for_nodes_down([3]) - self.assertFalse(cluster.metadata.get_host(IP_FORMAT % 3).is_currently_reconnecting()) + assert not cluster.metadata.get_host(IP_FORMAT % 3).is_currently_reconnecting() self.coordinator_stats.reset_counts() force_stop(2) @@ -769,4 +769,4 @@ def test_black_list_with_host_filter_policy(self): # policy should not allow reconnecting to ignored host force_stop(2) self._wait_for_nodes_down([2]) - self.assertFalse(cluster.metadata.get_host(ignored_address).is_currently_reconnecting()) + assert not cluster.metadata.get_host(ignored_address).is_currently_reconnecting() diff --git a/tests/integration/long/test_topology_change.py b/tests/integration/long/test_topology_change.py index 21c1ca10b3..53da367026 100644 --- a/tests/integration/long/test_topology_change.py +++ b/tests/integration/long/test_topology_change.py @@ -45,4 +45,4 @@ def test_removed_node_stops_reconnecting(self): wait_until(condition=lambda: state_listener.removed_host is not None, delay=2, max_attempts=50) self.assertIs(state_listener.downed_host, state_listener.removed_host) # Just a sanity check - self.assertFalse(state_listener.removed_host.is_currently_reconnecting()) + assert not state_listener.removed_host.is_currently_reconnecting() diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 81b332d436..4d69ac1bc7 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -168,7 +168,7 @@ def test_basic(self): CREATE KEYSPACE clustertests WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} """) - self.assertFalse(result) + assert not result result = execute_with_long_wait_retry(session, """ @@ -179,13 +179,13 @@ def test_basic(self): PRIMARY KEY (a, b) ) """) - self.assertFalse(result) + assert not result result = session.execute( """ INSERT INTO clustertests.cf0 (a, b, c) VALUES ('a', 'b', 'c') """) - self.assertFalse(result) + assert not result result = session.execute("SELECT * FROM clustertests.cf0") assert [('a', 'b', 'c')] == result @@ -331,7 +331,7 @@ def test_connect_on_keyspace(self): """ INSERT INTO test1rf.test (k, v) VALUES (8889, 8889) """) - self.assertFalse(result) + assert not result result = session.execute("SELECT * FROM test1rf.test") assert [(8889, 8889)] == result, "Rows in ResultSet are {0}".format(result.current_rows) @@ -729,7 +729,7 @@ def test_idle_heartbeat(self): for h in cluster.get_connection_holders(): for c in h.get_connections(): # make sure none are idle (should have startup messages - self.assertFalse(c.is_idle) + assert not c.is_idle with c.lock: connection_request_ids[id(c)] = deque(c.request_ids) # copy of request ids @@ -755,7 +755,7 @@ def test_idle_heartbeat(self): assert success # assert not idle status - self.assertFalse(any(c.is_idle if not c.is_control_connection else False for c in connections)) + assert not any(c.is_idle if not c.is_control_connection else False for c in connections) # holders include session pools and cc holders = cluster.get_connection_holders() @@ -790,7 +790,7 @@ def test_idle_heartbeat_disabled(self): connections = [c for holders in cluster.get_connection_holders() for c in holders.get_connections()] # assert not idle status (should never get reset because there is not heartbeat) - self.assertFalse(any(c.is_idle for c in connections)) + assert not any(c.is_idle for c in connections) cluster.shutdown() @@ -1327,7 +1327,7 @@ def test_no_connect(self): @test_category configuration """ with TestCluster() as cluster: - self.assertFalse(cluster.is_shutdown) + assert not cluster.is_shutdown assert cluster.is_shutdown def test_simple_nested(self): @@ -1342,8 +1342,8 @@ def test_simple_nested(self): """ with TestCluster(**self.cluster_kwargs) as cluster: with cluster.connect() as session: - self.assertFalse(cluster.is_shutdown) - self.assertFalse(session.is_shutdown) + assert not cluster.is_shutdown + assert not session.is_shutdown assert session.execute('select release_version from system.local').one() assert session.is_shutdown assert cluster.is_shutdown @@ -1360,8 +1360,8 @@ def test_cluster_no_session(self): """ with TestCluster(**self.cluster_kwargs) as cluster: session = cluster.connect() - self.assertFalse(cluster.is_shutdown) - self.assertFalse(session.is_shutdown) + assert not cluster.is_shutdown + assert not session.is_shutdown assert session.execute('select release_version from system.local').one() assert session.is_shutdown assert cluster.is_shutdown @@ -1379,16 +1379,16 @@ def test_session_no_cluster(self): cluster = TestCluster(**self.cluster_kwargs) unmanaged_session = cluster.connect() with cluster.connect() as session: - self.assertFalse(cluster.is_shutdown) - self.assertFalse(session.is_shutdown) - self.assertFalse(unmanaged_session.is_shutdown) + assert not cluster.is_shutdown + assert not session.is_shutdown + assert not unmanaged_session.is_shutdown assert session.execute('select release_version from system.local').one() assert session.is_shutdown - self.assertFalse(cluster.is_shutdown) - self.assertFalse(unmanaged_session.is_shutdown) + assert not cluster.is_shutdown + assert not unmanaged_session.is_shutdown unmanaged_session.shutdown() assert unmanaged_session.is_shutdown - self.assertFalse(cluster.is_shutdown) + assert not cluster.is_shutdown cluster.shutdown() assert cluster.is_shutdown diff --git a/tests/integration/standard/test_concurrent.py b/tests/integration/standard/test_concurrent.py index 5cad8a79f7..4271004d55 100644 --- a/tests/integration/standard/test_concurrent.py +++ b/tests/integration/standard/test_concurrent.py @@ -93,7 +93,7 @@ def execute_concurrent_base(self, test_fn, validate_fn, zip_args=True): assert num_statements == len(results) for success, result in results: assert success - self.assertFalse(result) + assert not result # read statement = SimpleStatement( @@ -156,13 +156,13 @@ def test_execute_concurrent_with_args_generator(self): results = self.execute_concurrent_args_helper(self.session, statement, parameters, results_generator=True) for success, result in results: assert success - self.assertFalse(result) + assert not result results = self.execute_concurrent_args_helper(self.session, statement, parameters, results_generator=True) for result in results: assert isinstance(result, ExecutionResult) assert result.success - self.assertFalse(result.result_or_exc) + assert not result.result_or_exc # read statement = SimpleStatement( @@ -193,7 +193,7 @@ def test_execute_concurrent_paged_result(self): assert num_statements == len(results) for success, result in results: assert success - self.assertFalse(result) + assert not result # read statement = SimpleStatement( @@ -290,11 +290,11 @@ def test_no_raise_on_first_failure(self): results = execute_concurrent(self.session, list(zip(statements, parameters)), raise_on_first_error=False) for i, (success, result) in enumerate(results): if i == 57: - self.assertFalse(success) + assert not success assert isinstance(result, InvalidRequest) else: assert success - self.assertFalse(result) + assert not result def test_no_raise_on_first_failure_client_side(self): statement = SimpleStatement( @@ -309,8 +309,8 @@ def test_no_raise_on_first_failure_client_side(self): results = execute_concurrent(self.session, list(zip(statements, parameters)), raise_on_first_error=False) for i, (success, result) in enumerate(results): if i == 57: - self.assertFalse(success) + assert not success assert isinstance(result, TypeError) else: assert success - self.assertFalse(result) + assert not result diff --git a/tests/integration/standard/test_connection.py b/tests/integration/standard/test_connection.py index 91f19d2b14..292edf746d 100644 --- a/tests/integration/standard/test_connection.py +++ b/tests/integration/standard/test_connection.py @@ -156,10 +156,10 @@ def test_heart_beat_timeout(self): time.sleep(.1) self.assertLess(count, 100, "Never connected to the first node") new_connections = self.wait_for_connections(host, self.cluster) - self.assertFalse(test_listener.host_down) + assert not test_listener.host_down # Make sure underlying new connections don't match previous ones for connection in initial_connections: - self.assertFalse(connection in new_connections) + assert not connection in new_connections def fetch_connections(self, host, cluster): # Given a cluster object and host grab all connection associated with that host diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index 6de7248675..760b54b9a6 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -86,7 +86,7 @@ def test_numpy_parser(self): """ # arrays = { 'a': arr1, 'b': arr2, ... } result = get_data(NumpyProtocolHandler) - self.assertFalse(result.has_more_pages) + assert not result.has_more_pages self._verify_numpy_page(result[0]) @notprotocolv1 diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 2b4c29f268..aecb7c37d3 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -344,7 +344,7 @@ def test_cluster_column_ordering_reversed_metadata(self): self.session.execute(create_statement) tablemeta = self.get_table_metadata() b_column = tablemeta.columns['b'] - self.assertFalse(b_column.is_reversed) + assert not b_column.is_reversed c_column = tablemeta.columns['c'] assert c_column.is_reversed @@ -609,7 +609,7 @@ def test_refresh_schema_metadata(self): self.session.execute("ALTER KEYSPACE {0} WITH durable_writes = false".format(self.keyspace_name)) assert cluster2.metadata.keyspaces[self.keyspace_name].durable_writes cluster2.refresh_schema_metadata() - self.assertFalse(cluster2.metadata.keyspaces[self.keyspace_name].durable_writes) + assert not cluster2.metadata.keyspaces[self.keyspace_name].durable_writes # Table metadata modification table_name = "test" @@ -683,7 +683,7 @@ def test_refresh_keyspace_metadata(self): self.session.execute("ALTER KEYSPACE {0} WITH durable_writes = false".format(self.keyspace_name)) assert cluster2.metadata.keyspaces[self.keyspace_name].durable_writes cluster2.refresh_keyspace_metadata(self.keyspace_name) - self.assertFalse(cluster2.metadata.keyspaces[self.keyspace_name].durable_writes) + assert not cluster2.metadata.keyspaces[self.keyspace_name].durable_writes cluster2.shutdown() @@ -2276,7 +2276,7 @@ def test_create_view_metadata(self): assert mv.keyspace_name == self.keyspace_name assert mv.name == "monthlyhigh" assert mv.base_table_name == "scores" - self.assertFalse(mv.include_all_columns) + assert not mv.include_all_columns # Validate that all columns are preset and correct mv_columns = list(mv.columns.values()) @@ -2489,7 +2489,7 @@ def test_metadata_with_quoted_identifiers(self): assert mv.keyspace_name == self.keyspace_name assert mv.name == "mv1" assert mv.base_table_name == "t1" - self.assertFalse(mv.include_all_columns) + assert not mv.include_all_columns # Validate that all columns are preset and correct mv_columns = list(mv.columns.values()) @@ -2569,7 +2569,4 @@ def test_existing_keyspaces_have_correct_virtual_tags(self): if name in self.virtual_ks_names: assert ks.virtual, 'incorrect .virtual value for {}'.format(name) else: - self.assertFalse( - ks.virtual, - 'incorrect .virtual value for {}'.format(name) - ) + assert not ks.virtual, 'incorrect .virtual value for {}'.format(name) diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 9695c29483..5d849eef70 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -233,7 +233,7 @@ def test_incomplete_query_trace(self): # should raise because duration is not set self.assertRaises(TraceUnavailable, trace.populate, max_wait=0.2, wait_for_complete=True) - self.assertFalse(trace.events) + assert not trace.events # should get the events with wait False trace.populate(wait_for_complete=False) @@ -358,7 +358,7 @@ def test_host_targeting_query(self): # check we're using the selected host assert host == future.coordinator_host # check that this bypasses the LBP - self.assertFalse(checkable_ep.load_balancing_policy.make_query_plan.called) + assert not checkable_ep.load_balancing_policy.make_query_plan.called class PreparedStatementTests(unittest.TestCase): @@ -812,7 +812,7 @@ def test_conditional_update(self): result = future.result() assert future.message.serial_consistency_level == ConsistencyLevel.SERIAL assert result - self.assertFalse(result.one().applied) + assert not result.one().applied statement = SimpleStatement( "UPDATE test3rf.test SET v=1 WHERE k=0 IF v=0", @@ -834,7 +834,7 @@ def test_conditional_update_with_prepared_statements(self): result = future.result() assert future.message.serial_consistency_level == ConsistencyLevel.SERIAL assert result - self.assertFalse(result.one().applied) + assert not result.one().applied statement = self.session.prepare( "UPDATE test3rf.test SET v=1 WHERE k=0 IF v=0") @@ -855,7 +855,7 @@ def test_conditional_update_with_batch_statements(self): result = future.result() assert future.message.serial_consistency_level == ConsistencyLevel.SERIAL assert result - self.assertFalse(result.one().applied) + assert not result.one().applied statement = BatchStatement(serial_consistency_level=ConsistencyLevel.LOCAL_SERIAL) statement.add("UPDATE test3rf.test SET v=1 WHERE k=0 IF v=0") @@ -982,7 +982,7 @@ def test_was_applied_batch_stmt(self): "INSERT INTO test3rf.lwt_clustering (k, c, v) VALUES (0, 4, 10);", "INSERT INTO test3rf.lwt_clustering (k, c, v) VALUES (0, 5, 10) IF NOT EXISTS;"], [None] * 4) result = self.session.execute(batch_statement) - self.assertFalse(result.was_applied) + assert not result.was_applied all_rows = self.session.execute("SELECT * from test3rf.lwt_clustering", execution_profile='serial') # Verify the non conditional insert hasn't been inserted @@ -994,12 +994,12 @@ def test_was_applied_batch_stmt(self): "INSERT INTO test3rf.lwt_clustering (k, c, v) VALUES (0, 3, 10) IF NOT EXISTS;", "INSERT INTO test3rf.lwt_clustering (k, c, v) VALUES (0, 5, 10) IF NOT EXISTS;"], [None] * 3) result = self.session.execute(batch_statement) - self.assertFalse(result.was_applied) + assert not result.was_applied # Should fail since (0, 0, 10) have already been written batch_statement.add("INSERT INTO test3rf.lwt_clustering (k, c, v) VALUES (0, 0, 10) IF NOT EXISTS;") result = self.session.execute(batch_statement) - self.assertFalse(result.was_applied) + assert not result.was_applied # Should succeed batch_statement = BatchStatement(batch_type) @@ -1049,7 +1049,7 @@ def test_was_applied_batch_string(self): APPLY batch; """ result = self.session.execute(batch_str) - self.assertFalse(result.was_applied) + assert not result.was_applied batch_str = """ BEGIN unlogged batch diff --git a/tests/integration/standard/test_query_paging.py b/tests/integration/standard/test_query_paging.py index 1d2a40e0b3..6b7c5aba87 100644 --- a/tests/integration/standard/test_query_paging.py +++ b/tests/integration/standard/test_query_paging.py @@ -350,21 +350,21 @@ def test_fetch_size(self): self.session.default_fetch_size = 2000 result = self.session.execute(prepared, []) - self.assertFalse(result.has_more_pages) + assert not result.has_more_pages self.session.default_fetch_size = None result = self.session.execute(prepared, []) - self.assertFalse(result.has_more_pages) + assert not result.has_more_pages self.session.default_fetch_size = 10 prepared.fetch_size = 2000 result = self.session.execute(prepared, []) - self.assertFalse(result.has_more_pages) + assert not result.has_more_pages prepared.fetch_size = None result = self.session.execute(prepared, []) - self.assertFalse(result.has_more_pages) + assert not result.has_more_pages prepared.fetch_size = 10 result = self.session.execute(prepared, []) @@ -373,12 +373,12 @@ def test_fetch_size(self): prepared.fetch_size = 2000 bound = prepared.bind([]) result = self.session.execute(bound, []) - self.assertFalse(result.has_more_pages) + assert not result.has_more_pages prepared.fetch_size = None bound = prepared.bind([]) result = self.session.execute(bound, []) - self.assertFalse(result.has_more_pages) + assert not result.has_more_pages prepared.fetch_size = 10 bound = prepared.bind([]) @@ -387,11 +387,11 @@ def test_fetch_size(self): bound.fetch_size = 2000 result = self.session.execute(bound, []) - self.assertFalse(result.has_more_pages) + assert not result.has_more_pages bound.fetch_size = None result = self.session.execute(bound, []) - self.assertFalse(result.has_more_pages) + assert not result.has_more_pages bound.fetch_size = 10 result = self.session.execute(bound, []) @@ -399,7 +399,7 @@ def test_fetch_size(self): s = SimpleStatement("SELECT * FROM test3rf.test", fetch_size=None) result = self.session.execute(s, []) - self.assertFalse(result.has_more_pages) + assert not result.has_more_pages s = SimpleStatement("SELECT * FROM test3rf.test") result = self.session.execute(s, []) @@ -408,4 +408,4 @@ def test_fetch_size(self): s = SimpleStatement("SELECT * FROM test3rf.test") s.fetch_size = None result = self.session.execute(s, []) - self.assertFalse(result.has_more_pages) + assert not result.has_more_pages diff --git a/tests/integration/standard/test_udts.py b/tests/integration/standard/test_udts.py index 37ed4cc38a..e20d02813d 100644 --- a/tests/integration/standard/test_udts.py +++ b/tests/integration/standard/test_udts.py @@ -65,7 +65,7 @@ def test_non_frozen_udts(self): self.session.execute("INSERT INTO {0} (a, b) VALUES (%s, %s)".format(self.function_table_name), (0, User("Nebraska", True))) self.session.execute("UPDATE {0} SET b.has_corn = False where a = 0".format(self.function_table_name)) result = self.session.execute("SELECT * FROM {0}".format(self.function_table_name)) - self.assertFalse(result.one().b.has_corn) + assert not result.one().b.has_corn table_sql = self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].as_cql_query() assert "" not in table_sql @@ -737,7 +737,7 @@ def test_alter_udt(self): results = self.session.execute("SELECT * from {0}".format(self.function_table_name)) for result in results: assert hasattr(result.typetoalter, 'a') - self.assertFalse(hasattr(result.typetoalter, 'b')) + assert not hasattr(result.typetoalter, 'b') # Alter UDT and ensure the alter is honored in results self.session.execute("ALTER TYPE typetoalter add b int") diff --git a/tests/stress_tests/test_multi_inserts.py b/tests/stress_tests/test_multi_inserts.py index 84dfc5e6f7..1a5a596aec 100644 --- a/tests/stress_tests/test_multi_inserts.py +++ b/tests/stress_tests/test_multi_inserts.py @@ -77,4 +77,4 @@ def test_in_flight_is_one(self): break i = i + 1 - self.assertFalse(leaking_connections, 'Detected leaking connection after %s iterations' % i) + assert not leaking_connections, 'Detected leaking connection after %s iterations' % i diff --git a/tests/unit/advanced/test_geometry.py b/tests/unit/advanced/test_geometry.py index 059a1f2b06..3a207528da 100644 --- a/tests/unit/advanced/test_geometry.py +++ b/tests/unit/advanced/test_geometry.py @@ -98,7 +98,7 @@ def test_eq(self): # does not blow up on other types # specifically use assertFalse(eq) to make sure we're using the geo __eq__ operator - self.assertFalse(geo == object()) + assert not geo == object() @unittest.skipUnless(_HAS_GEOMET, "Skip wkt geometry tests when geomet is not installed") class WKTTest(unittest.TestCase): diff --git a/tests/unit/advanced/test_graph.py b/tests/unit/advanced/test_graph.py index 8e1012845b..6efcd2d10d 100644 --- a/tests/unit/advanced/test_graph.py +++ b/tests/unit/advanced/test_graph.py @@ -350,27 +350,27 @@ def test_consistency_levels(self): def test_graph_source_convenience_attributes(self): opts = GraphOptions() assert opts.graph_source == b'g' - self.assertFalse(opts.is_analytics_source) + assert not opts.is_analytics_source assert opts.is_graph_source - self.assertFalse(opts.is_default_source) + assert not opts.is_default_source opts.set_source_default() self.assertIsNotNone(opts.graph_source) - self.assertFalse(opts.is_analytics_source) - self.assertFalse(opts.is_graph_source) + assert not opts.is_analytics_source + assert not opts.is_graph_source assert opts.is_default_source opts.set_source_analytics() self.assertIsNotNone(opts.graph_source) assert opts.is_analytics_source - self.assertFalse(opts.is_graph_source) - self.assertFalse(opts.is_default_source) + assert not opts.is_graph_source + assert not opts.is_default_source opts.set_source_graph() self.assertIsNotNone(opts.graph_source) - self.assertFalse(opts.is_analytics_source) + assert not opts.is_analytics_source assert opts.is_graph_source - self.assertFalse(opts.is_default_source) + assert not opts.is_default_source class GraphStatementTests(unittest.TestCase): diff --git a/tests/unit/column_encryption/test_policies.py b/tests/unit/column_encryption/test_policies.py index 00ac219c0c..c3d8302bd5 100644 --- a/tests/unit/column_encryption/test_policies.py +++ b/tests/unit/column_encryption/test_policies.py @@ -122,10 +122,10 @@ def test_contains_column(self): policy = AES256ColumnEncryptionPolicy() policy.add_column(coldesc, self._random_key(), "blob") assert policy.contains_column(coldesc) - self.assertFalse(policy.contains_column(ColDesc('ks2','table1','col1'))) - self.assertFalse(policy.contains_column(ColDesc('ks1','table2','col1'))) - self.assertFalse(policy.contains_column(ColDesc('ks1','table1','col2'))) - self.assertFalse(policy.contains_column(ColDesc('ks2','table2','col2'))) + assert not policy.contains_column(ColDesc('ks2','table1','col1')) + assert not policy.contains_column(ColDesc('ks1','table2','col1')) + assert not policy.contains_column(ColDesc('ks1','table1','col2')) + assert not policy.contains_column(ColDesc('ks2','table2','col2')) def test_encrypt_unknown_column(self): with self.assertRaises(ValueError): diff --git a/tests/unit/cqlengine/test_columns.py b/tests/unit/cqlengine/test_columns.py index f40ff01312..18d0c89e26 100644 --- a/tests/unit/cqlengine/test_columns.py +++ b/tests/unit/cqlengine/test_columns.py @@ -30,7 +30,7 @@ def test_comparisons(self): # __eq__ assert c0 == c0 - self.assertFalse(c0 == object()) + assert not c0 == object() # __lt__ self.assertLess(c0, c1) diff --git a/tests/unit/cqlengine/test_connection.py b/tests/unit/cqlengine/test_connection.py index 76266cff23..eca73531cf 100644 --- a/tests/unit/cqlengine/test_connection.py +++ b/tests/unit/cqlengine/test_connection.py @@ -26,10 +26,7 @@ class ConnectionTest(unittest.TestCase): def setUp(self): super(ConnectionTest, self).setUp() - self.assertFalse( - connection._connections, - 'Test precondition not met: connections are registered: {cs}'.format(cs=connection._connections) - ) + assert not connection._connections, 'Test precondition not met: connections are registered: {cs}'.format(cs=connection._connections) def test_set_session_without_existing_connection(self): """ diff --git a/tests/unit/io/test_asyncioreactor.py b/tests/unit/io/test_asyncioreactor.py index a6179e122d..c189aa3d74 100644 --- a/tests/unit/io/test_asyncioreactor.py +++ b/tests/unit/io/test_asyncioreactor.py @@ -74,4 +74,4 @@ def test_timer_cancellation(self): # Release context allow for timer thread to run. time.sleep(.2) # Assert that the cancellation was honored - self.assertFalse(callback.was_invoked()) + assert not callback.was_invoked() diff --git a/tests/unit/io/test_twistedreactor.py b/tests/unit/io/test_twistedreactor.py index 414bc57389..54abe884ae 100644 --- a/tests/unit/io/test_twistedreactor.py +++ b/tests/unit/io/test_twistedreactor.py @@ -157,7 +157,7 @@ def test_handle_read__incomplete(self): assert self.obj_ut._current_frame.end_pos == 30 # verify we never attempted to process the incomplete message - self.assertFalse(self.obj_ut.process_msg.called) + assert not self.obj_ut.process_msg.called def test_handle_read__fullmessage(self): """ diff --git a/tests/unit/io/utils.py b/tests/unit/io/utils.py index 6b83c34dac..a2ca133b67 100644 --- a/tests/unit/io/utils.py +++ b/tests/unit/io/utils.py @@ -181,9 +181,9 @@ def test_timer_cancellation(self): time.sleep(timeout * 2) timer_manager = self._timers # Assert that the cancellation was honored - self.assertFalse(timer_manager._queue) - self.assertFalse(timer_manager._new_timers) - self.assertFalse(callback.was_invoked()) + assert not timer_manager._queue + assert not timer_manager._new_timers + assert not callback.was_invoked() class ReactorTestMixin(object): @@ -378,12 +378,12 @@ def test_blocking_on_write(self): "socket busy") c.handle_write(*self.null_handle_function_args) - self.assertFalse(c.is_defunct) + assert not c.is_defunct # try again with normal behavior self.get_socket(c).send.side_effect = lambda x: len(x) c.handle_write(*self.null_handle_function_args) - self.assertFalse(c.is_defunct) + assert not c.is_defunct assert self.get_socket(c).send.call_args is not None def test_partial_send(self): @@ -399,7 +399,7 @@ def test_partial_send(self): expected_writes = int(math.ceil(float(msg_size) / write_size)) size_mod = msg_size % write_size last_write_size = size_mod if size_mod else write_size - self.assertFalse(c.is_defunct) + assert not c.is_defunct assert expected_writes == self.get_socket(c).send.call_count assert last_write_size == len(self.get_socket(c).send.call_args[0][0]) @@ -442,7 +442,7 @@ def test_partial_header_read(self): c.handle_read(*self.null_handle_function_args) assert c.connected_event.is_set() - self.assertFalse(c.is_defunct) + assert not c.is_defunct def test_partial_message_read(self): c = self.make_connection() @@ -469,7 +469,7 @@ def test_partial_message_read(self): c.handle_read(*self.null_handle_function_args) assert c.connected_event.is_set() - self.assertFalse(c.is_defunct) + assert not c.is_defunct def test_mixed_message_and_buffer_sizes(self): """ diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index d8bec326a1..55c807362b 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -221,8 +221,8 @@ def test_protocol_downgrade_test(self): assert ProtocolVersion.uses_error_code_map(ProtocolVersion.DSE_V1) assert ProtocolVersion.uses_int_query_flags(ProtocolVersion.DSE_V1) - self.assertFalse(ProtocolVersion.uses_error_code_map(ProtocolVersion.V4)) - self.assertFalse(ProtocolVersion.uses_int_query_flags(ProtocolVersion.V4)) + assert not ProtocolVersion.uses_error_code_map(ProtocolVersion.V4) + assert not ProtocolVersion.uses_int_query_flags(ProtocolVersion.V4) class ExecutionProfileTest(unittest.TestCase): diff --git a/tests/unit/test_concurrent.py b/tests/unit/test_concurrent.py index 9df733b551..4c8d0bdabe 100644 --- a/tests/unit/test_concurrent.py +++ b/tests/unit/test_concurrent.py @@ -253,5 +253,5 @@ def test_recursion_limited(self): results = execute_concurrent_with_args(s, "doesn't matter", [('param',)] * max_recursion, raise_on_first_error=False) # previously assert len(results) == max_recursion for r in results: - self.assertFalse(r[0]) + assert not r[0] assert isinstance(r[1], TypeError) diff --git a/tests/unit/test_control_connection.py b/tests/unit/test_control_connection.py index ecfcf1dee9..5c03315140 100644 --- a/tests/unit/test_control_connection.py +++ b/tests/unit/test_control_connection.py @@ -241,7 +241,7 @@ def test_wait_for_schema_agreement_fails(self): """ # change the schema version on one node self.connection.peer_results[1][1][2] = 'b' - self.assertFalse(self.control_connection.wait_for_schema_agreement()) + assert not self.control_connection.wait_for_schema_agreement() # the control connection should have slept until it hit the limit self.assertGreaterEqual(self.time.clock, self.cluster.max_schema_agreement_wait) @@ -284,7 +284,7 @@ def test_wait_for_schema_agreement_rpc_lookup(self): # but once we mark it up, the control connection will care host.is_up = True - self.assertFalse(self.control_connection.wait_for_schema_agreement()) + assert not self.control_connection.wait_for_schema_agreement() self.assertGreaterEqual(self.time.clock, self.cluster.max_schema_agreement_wait) def test_refresh_nodes_and_tokens(self): @@ -489,7 +489,7 @@ def test_handle_status_change(self): 'address': ('1.2.3.4', 9000) } self.control_connection._handle_status_change(event) - self.assertFalse(self.cluster.scheduler.schedule.called) + assert not self.cluster.scheduler.schedule.called # do the same with a known Host event = { @@ -545,8 +545,8 @@ def test_refresh_disabled(self): # no call on schema refresh cc_no_schema_refresh._handle_schema_change(schema_event) - self.assertFalse(cluster.scheduler.schedule.called) - self.assertFalse(cluster.scheduler.schedule_unique.called) + assert not cluster.scheduler.schedule.called + assert not cluster.scheduler.schedule_unique.called # topo and status changes as normal cc_no_schema_refresh._handle_status_change(status_event) @@ -559,8 +559,8 @@ def test_refresh_disabled(self): # no call on topo refresh cc_no_topo_refresh._handle_topology_change(topo_event) - self.assertFalse(cluster.scheduler.schedule.called) - self.assertFalse(cluster.scheduler.schedule_unique.called) + assert not cluster.scheduler.schedule.called + assert not cluster.scheduler.schedule_unique.called # schema and status change refresh as normal cc_no_topo_refresh._handle_status_change(status_event) diff --git a/tests/unit/test_host_connection_pool.py b/tests/unit/test_host_connection_pool.py index 5246b7a218..3b3192b36c 100644 --- a/tests/unit/test_host_connection_pool.py +++ b/tests/unit/test_host_connection_pool.py @@ -148,7 +148,7 @@ def test_return_defunct_connection(self): # the connection should be closed a new creation scheduled assert session.submit.call_args - self.assertFalse(pool.is_shutdown) + assert not pool.is_shutdown def test_return_defunct_connection_on_down_host(self): host = Mock(spec=Host, address='ip1') @@ -175,7 +175,7 @@ def test_return_defunct_connection_on_down_host(self): assert host.signal_connection_failure.call_args assert session.submit.called else: - self.assertFalse(session.submit.called) + assert not session.submit.called assert session.cluster.signal_connection_failure.call_args assert pool.is_shutdown @@ -197,7 +197,7 @@ def test_return_closed_connection(self): # a new creation should be scheduled assert session.submit.call_args - self.assertFalse(pool.is_shutdown) + assert not pool.is_shutdown def test_host_instantiations(self): """ diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 052830d058..867513848d 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -512,7 +512,7 @@ def test_comparison_unicode(self): t0 = BytesToken(value) t1 = BytesToken.from_string('00') self.assertGreater(t0, t1) - self.assertFalse(t0 < t1) + assert not t0 < t1 class KeyspaceMetadataTest(unittest.TestCase): diff --git a/tests/unit/test_orderedmap.py b/tests/unit/test_orderedmap.py index fca04cbb32..b43d3c002a 100644 --- a/tests/unit/test_orderedmap.py +++ b/tests/unit/test_orderedmap.py @@ -45,10 +45,10 @@ def test_contains(self): for k in keys: assert k in om - self.assertFalse(k not in om) + assert not k not in om assert 'notthere' not in om - self.assertFalse('notthere' in om) + assert not 'notthere' in om def test_keys(self): keys = ['first', 'middle', 'last'] @@ -97,7 +97,7 @@ def test_equal(self): assert om12 != d1 assert om1 != EMPTY - self.assertFalse(OrderedMap([('three', 3), ('four', 4)]) == d12) + assert not OrderedMap([('three', 3), ('four', 4)]) == d12 def test_getitem(self): keys = ['first', 'middle', 'last'] @@ -154,7 +154,7 @@ def test_delitem(self): del om[1] assert om == {2: 2} del om[2] - self.assertFalse(om) + assert not om self.assertRaises(KeyError, om.__delitem__, 1) diff --git a/tests/unit/test_query.py b/tests/unit/test_query.py index 27e268d117..29c800b99c 100644 --- a/tests/unit/test_query.py +++ b/tests/unit/test_query.py @@ -36,20 +36,20 @@ def test_clear(self): assert batch.custom_payload == custom_payload batch.clear() - self.assertFalse(batch._statements_and_parameters) + assert not batch._statements_and_parameters assert batch.keyspace is None assert batch.routing_key is None - self.assertFalse(batch.custom_payload) + assert not batch.custom_payload batch.add(ss) def test_clear_empty(self): batch = BatchStatement() batch.clear() - self.assertFalse(batch._statements_and_parameters) + assert not batch._statements_and_parameters assert batch.keyspace is None assert batch.routing_key is None - self.assertFalse(batch.custom_payload) + assert not batch.custom_payload batch.add('something') diff --git a/tests/unit/test_response_future.py b/tests/unit/test_response_future.py index 54d3e19b7a..c1c8dc41ac 100644 --- a/tests/unit/test_response_future.py +++ b/tests/unit/test_response_future.py @@ -104,7 +104,7 @@ def test_set_keyspace_result(self): results="keyspace1") rf._set_result(None, None, None, result) rf._set_keyspace_completed({}) - self.assertFalse(rf.result()) + assert not rf.result() def test_schema_change_result(self): session = self.make_session() @@ -237,7 +237,7 @@ def test_retry_policy_says_ignore(self): result = Mock(spec=UnavailableErrorMessage, info={}) rf._set_result(None, None, None, result) - self.assertFalse(rf.result()) + assert not rf.result() def test_retry_policy_says_retry(self): session = self.make_session() diff --git a/tests/unit/test_resultset.py b/tests/unit/test_resultset.py index bdf8c621d6..ac9df2193b 100644 --- a/tests/unit/test_resultset.py +++ b/tests/unit/test_resultset.py @@ -75,7 +75,7 @@ def test_has_more_pages(self): rs = ResultSet(response_future, []) type(response_future).has_more_pages = PropertyMock(side_effect=(True, False)) # after init to avoid side effects being consumed by init assert rs.has_more_pages - self.assertFalse(rs.has_more_pages) + assert not rs.has_more_pages def test_iterate_then_index(self): # RuntimeError if indexing with no pages @@ -90,8 +90,8 @@ def test_iterate_then_index(self): with self.assertRaises(RuntimeError): rs[0] - self.assertFalse(rs) - self.assertFalse(list(rs)) + assert not rs + assert not list(rs) # RuntimeError if indexing during or after pages response_future = Mock(has_more_pages=True, _continuous_paging_session=None) @@ -109,8 +109,8 @@ def test_iterate_then_index(self): # after consuming with self.assertRaises(RuntimeError): rs[0] - self.assertFalse(rs) - self.assertFalse(list(rs)) + assert not rs + assert not list(rs) def test_index_list_mode(self): # no pages @@ -168,7 +168,7 @@ def test_eq(self): assert rs def test_bool(self): - self.assertFalse(ResultSet(Mock(has_more_pages=False), [])) + assert not ResultSet(Mock(has_more_pages=False), []) assert ResultSet(Mock(has_more_pages=False), [1]) def test_was_applied(self): diff --git a/tests/unit/test_sortedset.py b/tests/unit/test_sortedset.py index 381b1eaa45..da3afddaf6 100644 --- a/tests/unit/test_sortedset.py +++ b/tests/unit/test_sortedset.py @@ -38,13 +38,13 @@ def test_contains(self): for i in expected: assert i in ss - self.assertFalse(i not in ss) + assert not i not in ss hi = max(expected)+1 lo = min(expected)-1 - self.assertFalse(hi in ss) - self.assertFalse(lo in ss) + assert not hi in ss + assert not lo in ss def test_mutable_contents(self): ba = bytearray(b'some data here') @@ -95,22 +95,22 @@ def test_isdisjoint(self): assert s2.isdisjoint(ss1) assert s2.isdisjoint(ss13) # s ss not disjoint - self.assertFalse(s12.isdisjoint(ss1)) - self.assertFalse(s12.isdisjoint(ss13)) + assert not s12.isdisjoint(ss1) + assert not s12.isdisjoint(ss13) # ss s disjoint assert ss1.isdisjoint(s2) assert ss13.isdisjoint(s2) # ss s not disjoint - self.assertFalse(ss1.isdisjoint(s12)) - self.assertFalse(ss13.isdisjoint(s12)) + assert not ss1.isdisjoint(s12) + assert not ss13.isdisjoint(s12) # ss ss disjoint assert ss1.isdisjoint(ss3) assert ss3.isdisjoint(ss1) # ss ss not disjoint - self.assertFalse(ss1.isdisjoint(ss13)) - self.assertFalse(ss13.isdisjoint(ss1)) - self.assertFalse(ss3.isdisjoint(ss13)) - self.assertFalse(ss13.isdisjoint(ss3)) + assert not ss1.isdisjoint(ss13) + assert not ss13.isdisjoint(ss1) + assert not ss3.isdisjoint(ss13) + assert not ss13.isdisjoint(ss3) def test_issubset(self): s12 = set([1, 2]) @@ -121,10 +121,10 @@ def test_issubset(self): assert ss1.issubset(s12) assert ss1.issubset(ss13) - self.assertFalse(ss1.issubset(ss3)) - self.assertFalse(ss13.issubset(ss3)) - self.assertFalse(ss13.issubset(ss1)) - self.assertFalse(ss13.issubset(s12)) + assert not ss1.issubset(ss3) + assert not ss13.issubset(ss3) + assert not ss13.issubset(ss1) + assert not ss13.issubset(s12) def test_issuperset(self): s12 = set([1, 2]) @@ -136,9 +136,9 @@ def test_issuperset(self): assert ss13.issuperset(ss3) assert ss13.issuperset(ss13) - self.assertFalse(s12.issuperset(ss13)) - self.assertFalse(ss1.issuperset(ss3)) - self.assertFalse(ss1.issuperset(ss13)) + assert not s12.issuperset(ss13) + assert not ss1.issuperset(ss3) + assert not ss1.issuperset(ss13) def test_union(self): s1 = set([1]) @@ -176,7 +176,7 @@ def test_symmetric_difference(self): ss2 = sortedset([5, 6, 7]) assert ss.symmetric_difference(s) == sortedset([1, 2, 4, 5]) - self.assertFalse(ss.symmetric_difference(ss)) + assert not ss.symmetric_difference(ss) assert ss.symmetric_difference(s) == sortedset([1, 2, 4, 5]) assert ss2.symmetric_difference(ss) == sortedset([2, 3, 4, 5, 6, 7]) @@ -198,9 +198,9 @@ def test_remove(self): ss.remove(1) assert len(ss) == 1 ss.remove(2) - self.assertFalse(ss) + assert not ss self.assertRaises(KeyError, ss.remove, 2) - self.assertFalse(ss) + assert not ss def test_getitem(self): ss = sortedset(range(3)) @@ -228,7 +228,7 @@ def test_delslice(self): del ss[1:] self.assertListEqual(list(ss), [1]) del ss[:] - self.assertFalse(ss) + assert not ss with self.assertRaises(IndexError): del ss[0] @@ -241,28 +241,28 @@ def test_operators(self): ss1 = sortedset([1]) ss12 = sortedset([1, 2]) # __ne__ - self.assertFalse(ss12 != ss12) - self.assertFalse(ss12 != sortedset([1, 2])) + assert not ss12 != ss12 + assert not ss12 != sortedset([1, 2]) assert ss12 != sortedset() # __le__ assert ss1 <= ss12 assert ss12 <= ss12 - self.assertFalse(ss12 <= ss1) + assert not ss12 <= ss1 # __lt__ assert ss1 < ss12 - self.assertFalse(ss12 < ss12) - self.assertFalse(ss12 < ss1) + assert not ss12 < ss12 + assert not ss12 < ss1 # __ge__ - self.assertFalse(ss1 >= ss12) + assert not ss1 >= ss12 assert ss12 >= ss12 assert ss12 >= ss1 # __gt__ - self.assertFalse(ss1 > ss12) - self.assertFalse(ss12 > ss12) + assert not ss1 > ss12 + assert not ss12 > ss12 assert ss12 > ss1 # __and__ diff --git a/tests/unit/test_util_types.py b/tests/unit/test_util_types.py index c1d575f687..d8383c4fb5 100644 --- a/tests/unit/test_util_types.py +++ b/tests/unit/test_util_types.py @@ -70,7 +70,7 @@ def test_out_of_range(self): def test_equals(self): assert Date(1234) == 1234 assert Date(1) == datetime.date(1970, 1, 2) - self.assertFalse(Date(2932897) == datetime.date(9999, 12, 31)) # date can't represent year > 9999 + assert not Date(2932897) == datetime.date(9999, 12, 31) # date can't represent year > 9999 assert Date(2932897) == 2932897 From 0173721c5e276d25e6556a79fab0564fecbd3742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 20:20:59 +0200 Subject: [PATCH 050/298] Replace self.assertLessEqual with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used command: ``` ast-grep run --pattern 'self.assertLessEqual($A, $B)' --rewrite 'assert $A <= $B' --lang python ./tests -U ``` --- tests/integration/cqlengine/management/test_management.py | 2 +- tests/integration/cqlengine/model/test_model.py | 2 +- tests/integration/cqlengine/model/test_model_io.py | 2 +- tests/integration/standard/test_cluster.py | 2 +- tests/integration/standard/test_cython_protocol_handlers.py | 2 +- tests/integration/standard/test_metadata.py | 4 ++-- tests/unit/cqlengine/test_columns.py | 4 ++-- tests/unit/test_concurrent.py | 2 +- tests/unit/test_connection.py | 2 +- tests/unit/test_policies.py | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/integration/cqlengine/management/test_management.py b/tests/integration/cqlengine/management/test_management.py index 8944a35566..406a747cc3 100644 --- a/tests/integration/cqlengine/management/test_management.py +++ b/tests/integration/cqlengine/management/test_management.py @@ -254,7 +254,7 @@ def test_table_property_update(self): table_options = management._get_table_metadata(ModelWithTableProperties).options - self.assertLessEqual(ModelWithTableProperties.__options__.items(), table_options.items()) + assert ModelWithTableProperties.__options__.items() <= table_options.items() def test_bogus_option_update(self): sync_table(ModelWithTableProperties) diff --git a/tests/integration/cqlengine/model/test_model.py b/tests/integration/cqlengine/model/test_model.py index b8f9d3cd92..81dc68fd47 100644 --- a/tests/integration/cqlengine/model/test_model.py +++ b/tests/integration/cqlengine/model/test_model.py @@ -218,7 +218,7 @@ def test_comparison(self): assert l == sorted(l) assert TestQueryUpdateModel.partition.column != TestQueryUpdateModel.cluster.column - self.assertLessEqual(TestQueryUpdateModel.partition.column, TestQueryUpdateModel.cluster.column) + assert TestQueryUpdateModel.partition.column <= TestQueryUpdateModel.cluster.column self.assertGreater(TestQueryUpdateModel.cluster.column, TestQueryUpdateModel.partition.column) self.assertGreaterEqual(TestQueryUpdateModel.cluster.column, TestQueryUpdateModel.partition.column) diff --git a/tests/integration/cqlengine/model/test_model_io.py b/tests/integration/cqlengine/model/test_model_io.py index d31ced662a..71ab40a3fc 100644 --- a/tests/integration/cqlengine/model/test_model_io.py +++ b/tests/integration/cqlengine/model/test_model_io.py @@ -508,7 +508,7 @@ class TestDefaultValueTracking(Model): self.assertIsNotNone(instance.int4) assert isinstance(instance.int4, int) self.assertGreaterEqual(instance.int4, 0) - self.assertLessEqual(instance.int4, 1000) + assert instance.int4 <= 1000 assert instance.int5 == 5555 assert instance.int6 is None diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 4d69ac1bc7..0eb74e1cbe 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -250,7 +250,7 @@ def test_protocol_negotiation(self): """ cluster = Cluster() - self.assertLessEqual(cluster.protocol_version, cassandra.ProtocolVersion.MAX_SUPPORTED) + assert cluster.protocol_version <= cassandra.ProtocolVersion.MAX_SUPPORTED session = cluster.connect() updated_protocol_version = session._protocol_version updated_cluster_version = cluster.protocol_version diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index 760b54b9a6..8b1dc3e890 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -115,7 +115,7 @@ def test_numpy_results_paged(self): for colname, arr in page.items(): if count <= expected_pages: self.assertGreater(len(arr), 0, "page count: %d" % (count,)) - self.assertLessEqual(len(arr), session.default_fetch_size) + assert len(arr) <= session.default_fetch_size else: # we get one extra item out of this iteration because of the way NumpyParser returns results # The last page is returned as a dict with zero-length arrays diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index aecb7c37d3..247290f336 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -1842,9 +1842,9 @@ def test_init_cond(self): cql_init = encoder.cql_encode_all_types(init_cond) with self.VerifiedAggregate(self, **self.make_aggregate_kwargs('update_map', 'map', init_cond=cql_init)) as va: map_res = s.execute("SELECT %s(v) AS map_res FROM t" % va.function_kwargs['name']).one().map_res - self.assertLessEqual(expected_map_values.items(), map_res.items()) + assert expected_map_values.items() <= map_res.items() init_not_updated = dict((k, init_cond[k]) for k in set(init_cond) - expected_key_set) - self.assertLessEqual(init_not_updated.items(), map_res.items()) + assert init_not_updated.items() <= map_res.items() c.shutdown() def test_aggregates_after_functions(self): diff --git a/tests/unit/cqlengine/test_columns.py b/tests/unit/cqlengine/test_columns.py index 18d0c89e26..4810d82bfb 100644 --- a/tests/unit/cqlengine/test_columns.py +++ b/tests/unit/cqlengine/test_columns.py @@ -40,8 +40,8 @@ def test_comparisons(self): pass # __le__ - self.assertLessEqual(c0, c1) - self.assertLessEqual(c0, c0) + assert c0 <= c1 + assert c0 <= c0 try: c0 <= object() # this raises for Python 3 except TypeError: diff --git a/tests/unit/test_concurrent.py b/tests/unit/test_concurrent.py index 4c8d0bdabe..e70a276236 100644 --- a/tests/unit/test_concurrent.py +++ b/tests/unit/test_concurrent.py @@ -234,7 +234,7 @@ def validate_result_ordering(self, results): #Windows clock granularity makes this equal most of the times if "Windows" in platform.system(): - self.assertLessEqual(last_time_added, current_time_added) + assert last_time_added <= current_time_added else: self.assertLess(last_time_added, current_time_added) last_time_added = current_time_added diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 9e2a6a784c..785cf94fc4 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -287,7 +287,7 @@ def test_empty_connections(self, *args): self.run_heartbeat(get_holders, count) self.assertGreaterEqual(get_holders.call_count, count-1) - self.assertLessEqual(get_holders.call_count, count) + assert get_holders.call_count <= count holder = get_holders.return_value[0] holder.get_connections.assert_has_calls([call()] * get_holders.call_count) diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index 8a9d1e8b56..77f4da9d95 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -996,7 +996,7 @@ def test_schedule_overflow(self): policy = ExponentialReconnectionPolicy(base_delay=base_delay, max_delay=max_delay, max_attempts=max_attempts) schedule = list(policy.new_schedule()) for number in schedule: - self.assertLessEqual(number, sys.float_info.max) + assert number <= sys.float_info.max def test_schedule_with_jitter(self): """ From e7cfaae1e7ee2bede4f69109dacfed084e5a7520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 20:22:25 +0200 Subject: [PATCH 051/298] Replace self.assertGreater with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used commands: ``` ast-grep run --pattern 'self.assertGreater($A, $B)' --rewrite 'assert $A > $B' --lang python ./tests -U ast-grep run --pattern 'self.assertGreater($A, $B, $C)' --rewrite 'assert $A > $B, $C' --lang python ./tests -U ``` --- .../cqlengine/management/test_management.py | 2 +- .../cqlengine/model/test_class_construction.py | 2 +- tests/integration/cqlengine/model/test_model.py | 2 +- tests/integration/simulacron/test_backpressure.py | 4 ++-- tests/integration/simulacron/test_connection.py | 2 +- tests/integration/standard/test_cluster.py | 8 ++++---- .../standard/test_cython_protocol_handlers.py | 2 +- tests/integration/standard/test_metadata.py | 14 +++++++------- tests/integration/standard/test_metrics.py | 2 +- tests/integration/upgrade/test_upgrade.py | 4 ++-- tests/unit/cqlengine/test_columns.py | 2 +- tests/unit/test_metadata.py | 4 ++-- tests/unit/test_segment.py | 2 +- 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/integration/cqlengine/management/test_management.py b/tests/integration/cqlengine/management/test_management.py index 406a747cc3..ec9e49e5c1 100644 --- a/tests/integration/cqlengine/management/test_management.py +++ b/tests/integration/cqlengine/management/test_management.py @@ -474,7 +474,7 @@ class StaticModel(Model): with mock.patch.object(session, "execute", wraps=session.execute) as m: sync_table(StaticModel) - self.assertGreater(m.call_count, 0) + assert m.call_count > 0 statement = m.call_args[0][0].query_string assert '"name" text static' in statement diff --git a/tests/integration/cqlengine/model/test_class_construction.py b/tests/integration/cqlengine/model/test_class_construction.py index 9f31bc0168..6097b6f544 100644 --- a/tests/integration/cqlengine/model/test_class_construction.py +++ b/tests/integration/cqlengine/model/test_class_construction.py @@ -422,4 +422,4 @@ def test_subclassing(self): class AlreadyLoadedTest(ConcreteModelWithCol): new_field = columns.Integer() - self.assertGreater(len(AlreadyLoadedTest()), length) + assert len(AlreadyLoadedTest()) > length diff --git a/tests/integration/cqlengine/model/test_model.py b/tests/integration/cqlengine/model/test_model.py index 81dc68fd47..f395c97171 100644 --- a/tests/integration/cqlengine/model/test_model.py +++ b/tests/integration/cqlengine/model/test_model.py @@ -219,7 +219,7 @@ def test_comparison(self): assert l == sorted(l) assert TestQueryUpdateModel.partition.column != TestQueryUpdateModel.cluster.column assert TestQueryUpdateModel.partition.column <= TestQueryUpdateModel.cluster.column - self.assertGreater(TestQueryUpdateModel.cluster.column, TestQueryUpdateModel.partition.column) + assert TestQueryUpdateModel.cluster.column > TestQueryUpdateModel.partition.column self.assertGreaterEqual(TestQueryUpdateModel.cluster.column, TestQueryUpdateModel.partition.column) diff --git a/tests/integration/simulacron/test_backpressure.py b/tests/integration/simulacron/test_backpressure.py index 2169c056a7..541611cf7c 100644 --- a/tests/integration/simulacron/test_backpressure.py +++ b/tests/integration/simulacron/test_backpressure.py @@ -70,10 +70,10 @@ def test_paused_connections(self): # Make sure we actually have some stuck in-flight requests for in_flight in [pool._connection.in_flight for pool in session.get_pools()]: - self.assertGreater(in_flight, 100) + assert in_flight > 100 time.sleep(.5) for in_flight in [pool._connection.in_flight for pool in session.get_pools()]: - self.assertGreater(in_flight, 100) + assert in_flight > 100 prime_request(ResumeReads()) diff --git a/tests/integration/simulacron/test_connection.py b/tests/integration/simulacron/test_connection.py index bba87f0ea0..8eb9edf14d 100644 --- a/tests/integration/simulacron/test_connection.py +++ b/tests/integration/simulacron/test_connection.py @@ -345,7 +345,7 @@ def test_retry_after_defunct(self): response_future = session.execute_async(query_to_prime, timeout=4 * idle_heartbeat_interval + idle_heartbeat_timeout) response_future.result() - self.assertGreater(len(response_future.attempted_hosts), 1) + assert len(response_future.attempted_hosts) > 1 # No error should be raised here since the hosts have been marked # as down and there's still 1 DC available diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 0eb74e1cbe..f8294412b3 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -955,7 +955,7 @@ 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") + assert len(cluster.metadata.all_hosts()) > 1, "We only have one host connected at this point" rr1_clone = session.execution_profile_clone_update('rr1', row_factory=tuple_factory) cluster.add_execution_profile("rr1_clone", rr1_clone) @@ -1012,7 +1012,7 @@ def test_profile_pool_management(self): session = cluster.connect(wait_for_all_pools=True) pools = session.get_pool_state() # there are more hosts, but we connected to the ones in the lbp aggregate - self.assertGreater(len(cluster.metadata.all_hosts()), 2) + assert len(cluster.metadata.all_hosts()) > 2 assert set(h.address for h in pools) == set(('127.0.0.1', '127.0.0.2')) # dynamically update pools on add @@ -1046,7 +1046,7 @@ def test_add_profile_timeout(self): with TestCluster(execution_profiles={EXEC_PROFILE_DEFAULT: node1}) as cluster: session = cluster.connect(wait_for_all_pools=True) pools = session.get_pool_state() - self.assertGreater(len(cluster.metadata.all_hosts()), 2) + assert len(cluster.metadata.all_hosts()) > 2 assert set(h.address for h in pools) == set(('127.0.0.1',)) node2 = ExecutionProfile( @@ -1242,7 +1242,7 @@ def _assert_replica_queried(self, trace, only_replicas=True): if only_replicas: assert len(queried_hosts) == 1, "The hosts queried where {}".format(queried_hosts) else: - self.assertGreater(len(queried_hosts), 1, "The host queried was {}".format(queried_hosts)) + assert len(queried_hosts) > 1, "The host queried was {}".format(queried_hosts) return queried_hosts def _check_trace(self, trace): diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index 8b1dc3e890..0e8f1446e5 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -114,7 +114,7 @@ def test_numpy_results_paged(self): assert isinstance(page, dict) for colname, arr in page.items(): if count <= expected_pages: - self.assertGreater(len(arr), 0, "page count: %d" % (count,)) + assert len(arr) > 0, "page count: %d" % (count,) assert len(arr) <= session.default_fetch_size else: # we get one extra item out of this iteration because of the way NumpyParser returns results diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 247290f336..2f7d4306a5 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -1630,7 +1630,7 @@ def test_functions_after_udt(self): type_idx = keyspace_cql.rfind("CREATE TYPE") func_idx = keyspace_cql.find("CREATE FUNCTION") assert -1 not in (type_idx, func_idx), "TYPE or FUNCTION not found in keyspace_cql: " + keyspace_cql - self.assertGreater(func_idx, type_idx) + assert func_idx > type_idx def test_function_same_name_diff_types(self): """ @@ -1650,8 +1650,8 @@ def test_function_same_name_diff_types(self): with self.VerifiedFunction(self, **kwargs): # another function: same name, different type sig. - self.assertGreater(len(kwargs['argument_types']), 1) - self.assertGreater(len(kwargs['argument_names']), 1) + assert len(kwargs['argument_types']) > 1 + assert len(kwargs['argument_names']) > 1 kwargs['argument_types'] = kwargs['argument_types'][:1] kwargs['argument_names'] = kwargs['argument_names'][:1] @@ -1867,7 +1867,7 @@ def test_aggregates_after_functions(self): func_idx = keyspace_cql.find("CREATE FUNCTION") aggregate_idx = keyspace_cql.rfind("CREATE AGGREGATE") assert -1 not in (aggregate_idx, func_idx), "AGGREGATE or FUNCTION not found in keyspace_cql: " + keyspace_cql - self.assertGreater(aggregate_idx, func_idx) + assert aggregate_idx > func_idx def test_same_name_diff_types(self): """ @@ -1951,7 +1951,7 @@ def test_cql_optional_params(self): assert meta.final_func is None cql = meta.as_cql_query() search_string = "INITCOND %s" % kwargs['initial_condition'] - self.assertGreater(cql.find(search_string), 0, '"%s" search string not found in cql:\n%s' % (search_string, cql)) + assert cql.find(search_string) > 0, '"%s" search string not found in cql:\n%s' % (search_string, cql) assert cql.find('FINALFUNC') == -1 # no initial condition, final func @@ -1964,7 +1964,7 @@ def test_cql_optional_params(self): cql = meta.as_cql_query() assert cql.find('INITCOND') == -1 search_string = 'FINALFUNC "%s"' % kwargs['final_func'] - self.assertGreater(cql.find(search_string), 0, '"%s" search string not found in cql:\n%s' % (search_string, cql)) + assert cql.find(search_string) > 0, '"%s" search string not found in cql:\n%s' % (search_string, cql) # both kwargs['initial_condition'] = encoder.cql_encode_all_types(['init', 'cond']) @@ -1977,7 +1977,7 @@ def test_cql_optional_params(self): init_cond_idx = cql.find("INITCOND %s" % kwargs['initial_condition']) final_func_idx = cql.find('FINALFUNC "%s"' % kwargs['final_func']) assert -1 not in (init_cond_idx, final_func_idx) - self.assertGreater(init_cond_idx, final_func_idx) + assert init_cond_idx > final_func_idx class BadMetaTest(unittest.TestCase): diff --git a/tests/integration/standard/test_metrics.py b/tests/integration/standard/test_metrics.py index 4923218533..5c26fb4ddd 100644 --- a/tests/integration/standard/test_metrics.py +++ b/tests/integration/standard/test_metrics.py @@ -77,7 +77,7 @@ def test_connection_error(self): # Give some time for the cluster to come back up, for the next test time.sleep(5) - self.assertGreater(self.cluster.metrics.stats.connection_errors, 0) + assert self.cluster.metrics.stats.connection_errors > 0 def test_write_timeout(self): """ diff --git a/tests/integration/upgrade/test_upgrade.py b/tests/integration/upgrade/test_upgrade.py index 51b3bd3850..d1f5235bcc 100644 --- a/tests/integration/upgrade/test_upgrade.py +++ b/tests/integration/upgrade/test_upgrade.py @@ -79,7 +79,7 @@ def connect_and_shutdown(): queried_hosts = set() for _ in range(10): results = session.execute("SELECT * from system.local WHERE key='local'") - self.assertGreater(len(results.current_rows), 0) + assert len(results.current_rows) > 0 assert len(results.response_future.attempted_hosts) == 1 queried_hosts.add(results.response_future.attempted_hosts[0]) assert len(queried_hosts) == 3 @@ -243,7 +243,7 @@ def connect_and_shutdown(self, auth_provider): queried_hosts = set() for _ in range(10): results = session.execute("SELECT * from system.local WHERE key='local'") - self.assertGreater(len(results.current_rows), 0) + assert len(results.current_rows) > 0 assert len(results.response_future.attempted_hosts) == 1 queried_hosts.add(results.response_future.attempted_hosts[0]) assert len(queried_hosts) == 3 diff --git a/tests/unit/cqlengine/test_columns.py b/tests/unit/cqlengine/test_columns.py index 4810d82bfb..fb5605b87c 100644 --- a/tests/unit/cqlengine/test_columns.py +++ b/tests/unit/cqlengine/test_columns.py @@ -48,7 +48,7 @@ def test_comparisons(self): pass # __gt__ - self.assertGreater(c1, c0) + assert c1 > c0 try: c1 > object() # this raises for Python 3 except TypeError: diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 867513848d..5415f914a7 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -505,13 +505,13 @@ def test_comparison(self): tok = BytesToken.from_string('0123456789abcdef') token_high_order = uint16_unpack(tok.value[0:2]) self.assertLess(BytesToken(uint16_pack(token_high_order - 1)), tok) - self.assertGreater(BytesToken(uint16_pack(token_high_order + 1)), tok) + assert BytesToken(uint16_pack(token_high_order + 1)) > tok def test_comparison_unicode(self): value = b'\'_-()"\xc2\xac' t0 = BytesToken(value) t1 = BytesToken.from_string('00') - self.assertGreater(t0, t1) + assert t0 > t1 assert not t0 < t1 diff --git a/tests/unit/test_segment.py b/tests/unit/test_segment.py index fb8ef0c6c5..aeca631162 100644 --- a/tests/unit/test_segment.py +++ b/tests/unit/test_segment.py @@ -158,7 +158,7 @@ def test_decode_compressed_self_contained_segment(self): assert header.is_self_contained == True assert header.uncompressed_payload_length == len(self.small_msg) - self.assertGreater(header.uncompressed_payload_length, header.payload_length) + assert header.uncompressed_payload_length > header.payload_length assert segment.payload == self.small_msg def test_decode_multi_segments(self): From ce235ba406254ec71993a88f674bb6dd55d9c248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 20:23:37 +0200 Subject: [PATCH 052/298] Replace self.assertGreaterEqual with plain assert >Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used commands: ``` ast-grep run --pattern 'self.assertGreaterEqual($A, $B)' --rewrite 'assert $A >= $B' --lang python ./tests -U ast-grep run --pattern 'self.assertGreaterEqual($A, $B, $C)' --rewrite 'assert $A >= $B, $C' --lang python ./tests -U ``` --- tests/integration/cqlengine/model/test_model.py | 2 +- tests/integration/cqlengine/model/test_model_io.py | 2 +- tests/integration/long/test_large_data.py | 2 +- tests/integration/standard/test_cluster.py | 12 ++++++------ tests/unit/cqlengine/test_columns.py | 4 ++-- tests/unit/test_connection.py | 2 +- tests/unit/test_control_connection.py | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/integration/cqlengine/model/test_model.py b/tests/integration/cqlengine/model/test_model.py index f395c97171..420a5c88c6 100644 --- a/tests/integration/cqlengine/model/test_model.py +++ b/tests/integration/cqlengine/model/test_model.py @@ -220,7 +220,7 @@ def test_comparison(self): assert TestQueryUpdateModel.partition.column != TestQueryUpdateModel.cluster.column assert TestQueryUpdateModel.partition.column <= TestQueryUpdateModel.cluster.column assert TestQueryUpdateModel.cluster.column > TestQueryUpdateModel.partition.column - self.assertGreaterEqual(TestQueryUpdateModel.cluster.column, TestQueryUpdateModel.partition.column) + assert TestQueryUpdateModel.cluster.column >= TestQueryUpdateModel.partition.column class TestDeprecationWarning(unittest.TestCase): diff --git a/tests/integration/cqlengine/model/test_model_io.py b/tests/integration/cqlengine/model/test_model_io.py index 71ab40a3fc..e22731dc2e 100644 --- a/tests/integration/cqlengine/model/test_model_io.py +++ b/tests/integration/cqlengine/model/test_model_io.py @@ -507,7 +507,7 @@ class TestDefaultValueTracking(Model): assert instance.int3 == 7777 self.assertIsNotNone(instance.int4) assert isinstance(instance.int4, int) - self.assertGreaterEqual(instance.int4, 0) + assert instance.int4 >= 0 assert instance.int4 <= 1000 assert instance.int5 == 5555 assert instance.int6 is None diff --git a/tests/integration/long/test_large_data.py b/tests/integration/long/test_large_data.py index 5fc79a4669..b8fba38f6e 100644 --- a/tests/integration/long/test_large_data.py +++ b/tests/integration/long/test_large_data.py @@ -202,7 +202,7 @@ def test_wide_byte_rows(self): for i, row in enumerate(results): assert row['v'] == bb - self.assertGreaterEqual(i, expected_results, "Verification failed only found {0} inserted we were expecting {1}".format(i,expected_results)) + assert i >= expected_results, "Verification failed only found {0} inserted we were expecting {1}".format(i,expected_results) session.cluster.shutdown() diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index f8294412b3..4e62ec0d49 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -505,7 +505,7 @@ def patched_wait_for_responses(*args, **kwargs): start_time = time.time() self.assertRaisesRegex(Exception, r"Schema metadata was not refreshed.*", c.refresh_schema_metadata) end_time = time.time() - self.assertGreaterEqual(end_time - start_time, agreement_timeout) + assert end_time - start_time >= agreement_timeout self.assertIs(original_meta, c.metadata.keyspaces) # refresh wait overrides cluster value @@ -543,7 +543,7 @@ def patched_wait_for_responses(*args, **kwargs): self.assertRaisesRegex(Exception, r"Schema metadata was not refreshed.*", c.refresh_schema_metadata, max_schema_agreement_wait=agreement_timeout) end_time = time.time() - self.assertGreaterEqual(end_time - start_time, agreement_timeout) + assert end_time - start_time >= agreement_timeout self.assertIs(original_meta, c.metadata.keyspaces) c.shutdown() @@ -712,7 +712,7 @@ def _warning_are_issued_when_auth(self, auth_provider): # Three conenctions to nodes plus the control connection auth_warning = mock_handler.get_message_count('warning', "An authentication challenge was not sent") - self.assertGreaterEqual(auth_warning, 4) + assert auth_warning >= 4 assert auth_warning == mock_handler.get_message_count("debug", "Got ReadyMessage on new connection") def test_idle_heartbeat(self): @@ -1524,7 +1524,7 @@ def test_deprecation_warnings_legacy_parameters(self): with warnings.catch_warnings(record=True) as w: TestCluster(load_balancing_policy=RoundRobinPolicy()) logging.info(w) - self.assertGreaterEqual(len(w), 1) + assert len(w) >= 1 assert any(["Legacy execution parameters will be removed in 4.0. " "Consider using execution profiles." in str(wa.message) for wa in w]) @@ -1544,7 +1544,7 @@ def test_deprecation_warnings_meta_refreshed(self): cluster = TestCluster() cluster.set_meta_refresh_enabled(True) logging.info(w) - self.assertGreaterEqual(len(w), 1) + assert len(w) >= 1 assert any(["Cluster.set_meta_refresh_enabled is deprecated and will be removed in 4.0." in str(wa.message) for wa in w]) @@ -1563,6 +1563,6 @@ def test_deprecation_warning_default_consistency_level(self): cluster = TestCluster() session = cluster.connect() session.default_consistency_level = ConsistencyLevel.ONE - self.assertGreaterEqual(len(w), 1) + assert len(w) >= 1 assert any(["Setting the consistency level at the session level will be removed in 4.0" in str(wa.message) for wa in w]) diff --git a/tests/unit/cqlengine/test_columns.py b/tests/unit/cqlengine/test_columns.py index fb5605b87c..669a3f522e 100644 --- a/tests/unit/cqlengine/test_columns.py +++ b/tests/unit/cqlengine/test_columns.py @@ -55,8 +55,8 @@ def test_comparisons(self): pass # __ge__ - self.assertGreaterEqual(c1, c0) - self.assertGreaterEqual(c1, c1) + assert c1 >= c0 + assert c1 >= c1 try: c1 >= object() # this raises for Python 3 except TypeError: diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 785cf94fc4..0ebcb8307f 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -286,7 +286,7 @@ def test_empty_connections(self, *args): self.run_heartbeat(get_holders, count) - self.assertGreaterEqual(get_holders.call_count, count-1) + assert get_holders.call_count >= count-1 assert get_holders.call_count <= count holder = get_holders.return_value[0] holder.get_connections.assert_has_calls([call()] * get_holders.call_count) diff --git a/tests/unit/test_control_connection.py b/tests/unit/test_control_connection.py index 5c03315140..3a6337700d 100644 --- a/tests/unit/test_control_connection.py +++ b/tests/unit/test_control_connection.py @@ -243,7 +243,7 @@ def test_wait_for_schema_agreement_fails(self): self.connection.peer_results[1][1][2] = 'b' assert not self.control_connection.wait_for_schema_agreement() # the control connection should have slept until it hit the limit - self.assertGreaterEqual(self.time.clock, self.cluster.max_schema_agreement_wait) + assert self.time.clock >= self.cluster.max_schema_agreement_wait def test_wait_for_schema_agreement_skipping(self): """ @@ -285,7 +285,7 @@ def test_wait_for_schema_agreement_rpc_lookup(self): # but once we mark it up, the control connection will care host.is_up = True assert not self.control_connection.wait_for_schema_agreement() - self.assertGreaterEqual(self.time.clock, self.cluster.max_schema_agreement_wait) + assert self.time.clock >= self.cluster.max_schema_agreement_wait def test_refresh_nodes_and_tokens(self): self.control_connection.refresh_node_list_and_token_map() From 641153d88e4c9d2af7e8e5f09049bf60320822d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 20:27:38 +0200 Subject: [PATCH 053/298] Replace self.assertIsNotNone with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used commands: ``` ast-grep run --pattern 'self.assertIsNotNone($A)' --rewrite 'assert $A is not None' --lang python ./tests -U ast-grep run --pattern 'self.assertIsNotNone($A, $B)' --rewrite 'assert $A is not None, $B' --lang python ./tests -U ``` --- .../cqlengine/connections/test_connection.py | 4 +- .../cqlengine/management/test_management.py | 16 +++--- .../model/test_class_construction.py | 2 +- .../cqlengine/model/test_model_io.py | 6 +- .../integration/cqlengine/model/test_udts.py | 4 +- .../integration/cqlengine/test_connections.py | 4 +- .../cqlengine/test_lwt_conditional.py | 4 +- .../integration/simulacron/test_connection.py | 2 +- tests/integration/simulacron/test_endpoint.py | 8 +-- .../column_encryption/test_policies.py | 4 +- .../standard/test_client_warnings.py | 4 +- tests/integration/standard/test_cluster.py | 12 ++-- .../standard/test_cython_protocol_handlers.py | 3 +- tests/integration/standard/test_metadata.py | 56 +++++++++---------- tests/integration/standard/test_query.py | 34 +++++------ tests/unit/advanced/test_graph.py | 6 +- 16 files changed, 85 insertions(+), 84 deletions(-) diff --git a/tests/integration/cqlengine/connections/test_connection.py b/tests/integration/cqlengine/connections/test_connection.py index e5dea115ed..78d5133e63 100644 --- a/tests/integration/cqlengine/connections/test_connection.py +++ b/tests/integration/cqlengine/connections/test_connection.py @@ -42,12 +42,12 @@ def tearDown(self): @local def test_connection_setup_with_setup(self): connection.setup(hosts=None, default_keyspace=None) - self.assertIsNotNone(connection.get_connection("default").cluster.metadata.get_host("127.0.0.1")) + assert connection.get_connection("default").cluster.metadata.get_host("127.0.0.1") is not None @local def test_connection_setup_with_default(self): connection.default() - self.assertIsNotNone(connection.get_connection("default").cluster.metadata.get_host("127.0.0.1")) + assert connection.get_connection("default").cluster.metadata.get_host("127.0.0.1") is not None def test_only_one_connection_is_created(self): """ diff --git a/tests/integration/cqlengine/management/test_management.py b/tests/integration/cqlengine/management/test_management.py index ec9e49e5c1..b24d61acaa 100644 --- a/tests/integration/cqlengine/management/test_management.py +++ b/tests/integration/cqlengine/management/test_management.py @@ -400,12 +400,12 @@ def test_sync_index(self): """ sync_table(IndexModel) table_meta = management._get_table_metadata(IndexModel) - self.assertIsNotNone(management._get_index_name_by_column(table_meta, 'second_key')) + assert management._get_index_name_by_column(table_meta, 'second_key') is not None # index already exists sync_table(IndexModel) table_meta = management._get_table_metadata(IndexModel) - self.assertIsNotNone(management._get_index_name_by_column(table_meta, 'second_key')) + assert management._get_index_name_by_column(table_meta, 'second_key') is not None def test_sync_index_case_sensitive(self): """ @@ -420,12 +420,12 @@ def test_sync_index_case_sensitive(self): """ sync_table(IndexCaseSensitiveModel) table_meta = management._get_table_metadata(IndexCaseSensitiveModel) - self.assertIsNotNone(management._get_index_name_by_column(table_meta, 'second_key')) + assert management._get_index_name_by_column(table_meta, 'second_key') is not None # index already exists sync_table(IndexCaseSensitiveModel) table_meta = management._get_table_metadata(IndexCaseSensitiveModel) - self.assertIsNotNone(management._get_index_name_by_column(table_meta, 'second_key')) + assert management._get_index_name_by_column(table_meta, 'second_key') is not None @greaterthancass20 @requires_collection_indexes @@ -442,10 +442,10 @@ def test_sync_indexed_set(self): """ sync_table(TestIndexSetModel) table_meta = management._get_table_metadata(TestIndexSetModel) - self.assertIsNotNone(management._get_index_name_by_column(table_meta, 'int_set')) - self.assertIsNotNone(management._get_index_name_by_column(table_meta, 'int_list')) - self.assertIsNotNone(management._get_index_name_by_column(table_meta, 'text_map')) - self.assertIsNotNone(management._get_index_name_by_column(table_meta, 'mixed_tuple')) + assert management._get_index_name_by_column(table_meta, 'int_set') is not None + assert management._get_index_name_by_column(table_meta, 'int_list') is not None + assert management._get_index_name_by_column(table_meta, 'text_map') is not None + assert management._get_index_name_by_column(table_meta, 'mixed_tuple') is not None class NonModelFailureTest(BaseCassEngTestCase): diff --git a/tests/integration/cqlengine/model/test_class_construction.py b/tests/integration/cqlengine/model/test_class_construction.py index 6097b6f544..8f1b4de488 100644 --- a/tests/integration/cqlengine/model/test_class_construction.py +++ b/tests/integration/cqlengine/model/test_class_construction.py @@ -47,7 +47,7 @@ class TestModel(Model): inst = TestModel() self.assertHasAttr(inst, 'id') self.assertHasAttr(inst, 'text') - self.assertIsNotNone(inst.id) + assert inst.id is not None assert inst.text is None def test_values_on_instantiation(self): diff --git a/tests/integration/cqlengine/model/test_model_io.py b/tests/integration/cqlengine/model/test_model_io.py index e22731dc2e..96c0647e3b 100644 --- a/tests/integration/cqlengine/model/test_model_io.py +++ b/tests/integration/cqlengine/model/test_model_io.py @@ -88,7 +88,7 @@ def test_model_instantiation_save_and_load(self): """ tm = TestModel(count=8, text='123456789') # Tests that values are available on instantiation. - self.assertIsNotNone(tm['id']) + assert tm['id'] is not None assert tm.count == 8 assert tm.text == '123456789' tm.save() @@ -217,7 +217,7 @@ class AllDatatypesModel(Model): assert input[i] == output[chr(i_char)] def test_can_specify_none_instead_of_default(self): - self.assertIsNotNone(TestModel.a_bool.column.default) + assert TestModel.a_bool.column.default is not None # override default inst = TestModel.create(a_bool=None) @@ -505,7 +505,7 @@ class TestDefaultValueTracking(Model): assert instance.int1 == 9999 assert instance.int2 == 456 assert instance.int3 == 7777 - self.assertIsNotNone(instance.int4) + assert instance.int4 is not None assert isinstance(instance.int4, int) assert instance.int4 >= 0 assert instance.int4 <= 1000 diff --git a/tests/integration/cqlengine/model/test_udts.py b/tests/integration/cqlengine/model/test_udts.py index b80b70899e..22452ba720 100644 --- a/tests/integration/cqlengine/model/test_udts.py +++ b/tests/integration/cqlengine/model/test_udts.py @@ -526,9 +526,9 @@ class OuterModel(Model): t.nested = [NestedUdt(something='test')] t.simple = NestedUdt(something="") t.save() - self.assertIsNotNone(t.nested[0].test_id) + assert t.nested[0].test_id is not None assert t.nested[0].default_text == "default text" - self.assertIsNotNone(t.simple.test_id) + assert t.simple.test_id is not None assert t.simple.default_text == "default text" def test_udt_validate(self): diff --git a/tests/integration/cqlengine/test_connections.py b/tests/integration/cqlengine/test_connections.py index 839b180e2c..006e0f68fa 100644 --- a/tests/integration/cqlengine/test_connections.py +++ b/tests/integration/cqlengine/test_connections.py @@ -230,7 +230,7 @@ def test_connection_creation_from_session(self): session = cluster.connect() connection_name = 'from_session' conn.register_connection(connection_name, session=session) - self.assertIsNotNone(conn.get_connection(connection_name).cluster.metadata.get_host(CASSANDRA_IP)) + assert conn.get_connection(connection_name).cluster.metadata.get_host(CASSANDRA_IP) is not None self.addCleanup(conn.unregister_connection, connection_name) cluster.shutdown() @@ -245,7 +245,7 @@ def test_connection_from_hosts(self): """ connection_name = 'from_hosts' conn.register_connection(connection_name, hosts=[CASSANDRA_IP]) - self.assertIsNotNone(conn.get_connection(connection_name).cluster.metadata.get_host(CASSANDRA_IP)) + assert conn.get_connection(connection_name).cluster.metadata.get_host(CASSANDRA_IP) is not None self.addCleanup(conn.unregister_connection, connection_name) def test_connection_param_validation(self): diff --git a/tests/integration/cqlengine/test_lwt_conditional.py b/tests/integration/cqlengine/test_lwt_conditional.py index f66785bc71..8aaf0ff44d 100644 --- a/tests/integration/cqlengine/test_lwt_conditional.py +++ b/tests/integration/cqlengine/test_lwt_conditional.py @@ -248,7 +248,7 @@ def test_update_to_none(self): assert TestConditionalModel.objects(id=t.id).count() == 1 with self.assertRaises(LWTException): t.iff(count=9999).update(text=None) - self.assertIsNotNone(TestConditionalModel.objects(id=t.id).first().text) + assert TestConditionalModel.objects(id=t.id).first().text is not None t.iff(count=5).update(text=None) assert TestConditionalModel.objects(id=t.id).first().text is None @@ -257,7 +257,7 @@ def test_update_to_none(self): assert TestConditionalModel.objects(id=t.id).count() == 1 with self.assertRaises(LWTException): TestConditionalModel.objects(id=t.id).iff(count=9999).update(text=None) - self.assertIsNotNone(TestConditionalModel.objects(id=t.id).first().text) + assert TestConditionalModel.objects(id=t.id).first().text is not None TestConditionalModel.objects(id=t.id).iff(count=5).update(text=None) assert TestConditionalModel.objects(id=t.id).first().text is None diff --git a/tests/integration/simulacron/test_connection.py b/tests/integration/simulacron/test_connection.py index 8eb9edf14d..d09980c230 100644 --- a/tests/integration/simulacron/test_connection.py +++ b/tests/integration/simulacron/test_connection.py @@ -470,7 +470,7 @@ def test_driver_recovers_nework_isolation(self): time.sleep(idle_heartbeat_timeout + idle_heartbeat_interval + 2) - self.assertIsNotNone(session.execute("SELECT * from system.local WHERE key='local'")) + assert session.execute("SELECT * from system.local WHERE key='local'") is not None def test_max_in_flight(self): """ Verify we don't exceed max_in_flight when borrowing connections or sending heartbeats """ diff --git a/tests/integration/simulacron/test_endpoint.py b/tests/integration/simulacron/test_endpoint.py index d2059b6172..5af38a9f6b 100644 --- a/tests/integration/simulacron/test_endpoint.py +++ b/tests/integration/simulacron/test_endpoint.py @@ -78,13 +78,13 @@ def test_default_endpoint(self): hosts = self.cluster.metadata.all_hosts() assert len(hosts) == 3 for host in hosts: - self.assertIsNotNone(host.endpoint) + assert host.endpoint is not None assert isinstance(host.endpoint, DefaultEndPoint) assert host.address == host.endpoint.address assert host.broadcast_rpc_address == host.endpoint.address assert isinstance(self.cluster.control_connection._connection.endpoint, DefaultEndPoint) - self.assertIsNotNone(self.cluster.control_connection._connection.endpoint) + assert self.cluster.control_connection._connection.endpoint is not None endpoints = [host.endpoint for host in hosts] assert self.cluster.control_connection._connection.endpoint in endpoints @@ -100,14 +100,14 @@ def test_custom_endpoint(self): hosts = cluster.metadata.all_hosts() assert len(hosts) == 3 for host in hosts: - self.assertIsNotNone(host.endpoint) + assert host.endpoint is not None assert isinstance(host.endpoint, AddressEndPoint) assert str(host.endpoint) == host.endpoint.address assert host.address == host.endpoint.address assert host.broadcast_rpc_address == host.endpoint.address assert isinstance(cluster.control_connection._connection.endpoint, AddressEndPoint) - self.assertIsNotNone(cluster.control_connection._connection.endpoint) + assert cluster.control_connection._connection.endpoint is not None endpoints = [host.endpoint for host in hosts] assert cluster.control_connection._connection.endpoint in endpoints diff --git a/tests/integration/standard/column_encryption/test_policies.py b/tests/integration/standard/column_encryption/test_policies.py index 7f0f593b13..9a1d186895 100644 --- a/tests/integration/standard/column_encryption/test_policies.py +++ b/tests/integration/standard/column_encryption/test_policies.py @@ -80,7 +80,7 @@ def test_end_to_end_simple(self): # Use encode_and_encrypt helper function to populate date for i in range(1,100): - self.assertIsNotNone(i) + assert i is not None encrypted = cl_policy.encode_and_encrypt(col_desc, i) session.execute("insert into foo.bar (encrypted, unencrypted) values (%s,%s)", (encrypted, i)) @@ -119,7 +119,7 @@ def test_end_to_end_different_cle_contexts_different_ivs(self): # Use encode_and_encrypt helper function to populate date for i in range(1,100): - self.assertIsNotNone(i) + assert i is not None encrypted = cl_policy1.encode_and_encrypt(col_desc1, i) session1.execute("insert into foo.bar (encrypted, unencrypted) values (%s,%s)", (encrypted, i)) session1.shutdown() diff --git a/tests/integration/standard/test_client_warnings.py b/tests/integration/standard/test_client_warnings.py index f84bda456e..2fa1d1a29b 100644 --- a/tests/integration/standard/test_client_warnings.py +++ b/tests/integration/standard/test_client_warnings.py @@ -88,7 +88,7 @@ def test_warning_with_trace(self): future.result() assert len(future.warnings) == 1 self.assertRegex(future.warnings[0], 'Batch.*exceeding.*') - self.assertIsNotNone(future.get_query_trace()) + assert future.get_query_trace() is not None @local @requires_custom_payload @@ -128,5 +128,5 @@ def test_warning_with_trace_and_custom_payload(self): future.result() assert len(future.warnings) == 1 self.assertRegex(future.warnings[0], 'Batch.*exceeding.*') - self.assertIsNotNone(future.get_query_trace()) + assert future.get_query_trace() is not None self.assertDictEqual(future.custom_payload, payload) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 4e62ec0d49..895053c679 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -708,7 +708,7 @@ def _warning_are_issued_when_auth(self, auth_provider): with MockLoggingHandler().set_module_name(connection.__name__) as mock_handler: with TestCluster(auth_provider=auth_provider) as cluster: session = cluster.connect() - self.assertIsNotNone(session.execute("SELECT * from system.local WHERE key='local'")) + assert session.execute("SELECT * from system.local WHERE key='local'") is not None # Three conenctions to nodes plus the control connection auth_warning = mock_handler.get_message_count('warning', "An authentication challenge was not sent") @@ -1246,11 +1246,11 @@ def _assert_replica_queried(self, trace, only_replicas=True): return queried_hosts def _check_trace(self, trace): - self.assertIsNotNone(trace.request_type) - self.assertIsNotNone(trace.duration) - self.assertIsNotNone(trace.started_at) - self.assertIsNotNone(trace.coordinator) - self.assertIsNotNone(trace.events) + assert trace.request_type is not None + assert trace.duration is not None + assert trace.started_at is not None + assert trace.coordinator is not None + assert trace.events is not None class LocalHostAdressTranslator(AddressTranslator): diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index 0e8f1446e5..9f082f6a8a 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -254,7 +254,8 @@ def test_null_types(self): [self.assertIs(col_array[i], masked) for i in mapped_index[begin_unset:]] else: had_none = True - [self.assertIsNotNone(col_array[i]) for i in mapped_index[:begin_unset]] + for i in mapped_index[:begin_unset]: + assert col_array[i] is not None for i in mapped_index[begin_unset:]: assert col_array[i] is None assert had_masked diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 2f7d4306a5..983874d693 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -71,13 +71,13 @@ def test_host_addresses(self): """ # All nodes should have the broadcast_address, rpc_address and host_id set for host in self.cluster.metadata.all_hosts(): - self.assertIsNotNone(host.broadcast_address) - self.assertIsNotNone(host.broadcast_rpc_address) - self.assertIsNotNone(host.host_id) + assert host.broadcast_address is not None + assert host.broadcast_rpc_address is not None + assert host.host_id is not None if CASSANDRA_VERSION >= Version('4-a'): - self.assertIsNotNone(host.broadcast_port) - self.assertIsNotNone(host.broadcast_rpc_port) + assert host.broadcast_port is not None + assert host.broadcast_rpc_port is not None con = self.cluster.control_connection.get_connections()[0] local_host = con.host @@ -164,8 +164,8 @@ def test_schema_metadata_disable(self): query = "SELECT * FROM system.local WHERE key='local'" no_schema_rs = no_schema_session.execute(query) no_token_rs = no_token_session.execute(query) - self.assertIsNotNone(no_schema_rs.one()) - self.assertIsNotNone(no_token_rs.one()) + assert no_schema_rs.one() is not None + assert no_token_rs.one() is not None no_schema.shutdown() no_token.shutdown() @@ -2247,30 +2247,30 @@ def test_create_view_metadata(self): score_table = self.cluster.metadata.keyspaces[self.keyspace_name].tables['scores'] mv = self.cluster.metadata.keyspaces[self.keyspace_name].views['monthlyhigh'] - self.assertIsNotNone(score_table.views["monthlyhigh"]) - self.assertIsNotNone(len(score_table.views), 1) + assert score_table.views["monthlyhigh"] is not None + assert len(score_table.views) is not None, 1 # Make sure user is a partition key, and not null assert len(score_table.partition_key) == 1 - self.assertIsNotNone(score_table.columns['user']) + assert score_table.columns['user'] is not None assert score_table.columns['user'], score_table.partition_key[0] # Validate clustering keys assert len(score_table.clustering_key) == 4 - self.assertIsNotNone(score_table.columns['game']) + assert score_table.columns['game'] is not None assert score_table.columns['game'], score_table.clustering_key[0] - self.assertIsNotNone(score_table.columns['year']) + assert score_table.columns['year'] is not None assert score_table.columns['year'], score_table.clustering_key[1] - self.assertIsNotNone(score_table.columns['month']) + assert score_table.columns['month'] is not None assert score_table.columns['month'], score_table.clustering_key[2] - self.assertIsNotNone(score_table.columns['day']) + assert score_table.columns['day'] is not None assert score_table.columns['day'], score_table.clustering_key[3] - self.assertIsNotNone(score_table.columns['score']) + assert score_table.columns['score'] is not None # Validate basic mv information assert mv.keyspace_name == self.keyspace_name @@ -2283,17 +2283,17 @@ def test_create_view_metadata(self): assert len(mv_columns) == 6 game_column = mv_columns[0] - self.assertIsNotNone(game_column) + assert game_column is not None assert game_column.name == 'game' assert game_column == mv.partition_key[0] year_column = mv_columns[1] - self.assertIsNotNone(year_column) + assert year_column is not None assert year_column.name == 'year' assert year_column == mv.partition_key[1] month_column = mv_columns[2] - self.assertIsNotNone(month_column) + assert month_column is not None assert month_column.name == 'month' assert month_column == mv.partition_key[2] @@ -2357,8 +2357,8 @@ def test_base_table_column_addition_mv(self): score_table = self.cluster.metadata.keyspaces[self.keyspace_name].tables['scores'] - self.assertIsNotNone(score_table.views["monthlyhigh"]) - self.assertIsNotNone(score_table.views["alltimehigh"]) + assert score_table.views["monthlyhigh"] is not None + assert score_table.views["alltimehigh"] is not None assert len(self.cluster.metadata.keyspaces[self.keyspace_name].views) == 2 insert_fouls = """ALTER TABLE {0}.scores ADD fouls INT""".format((self.keyspace_name)) @@ -2469,21 +2469,21 @@ def test_metadata_with_quoted_identifiers(self): t1_table = self.cluster.metadata.keyspaces[self.keyspace_name].tables['t1'] mv = self.cluster.metadata.keyspaces[self.keyspace_name].views['mv1'] - self.assertIsNotNone(t1_table.views["mv1"]) - self.assertIsNotNone(len(t1_table.views), 1) + assert t1_table.views["mv1"] is not None + assert len(t1_table.views) is not None, 1 # Validate partition key, and not null assert len(t1_table.partition_key) == 1 - self.assertIsNotNone(t1_table.columns['theKey']) + assert t1_table.columns['theKey'] is not None assert t1_table.columns['theKey'], t1_table.partition_key[0] # Validate clustering key column assert len(t1_table.clustering_key) == 1 - self.assertIsNotNone(t1_table.columns['the;Clustering']) + assert t1_table.columns['the;Clustering'] is not None assert t1_table.columns['the;Clustering'], t1_table.clustering_key[0] # Validate regular column - self.assertIsNotNone(t1_table.columns['the Value']) + assert t1_table.columns['the Value'] is not None # Validate basic mv information assert mv.keyspace_name == self.keyspace_name @@ -2496,12 +2496,12 @@ def test_metadata_with_quoted_identifiers(self): assert len(mv_columns) == 3 theKey_column = mv_columns[0] - self.assertIsNotNone(theKey_column) + assert theKey_column is not None assert theKey_column.name == 'theKey' assert theKey_column == mv.partition_key[0] cluster_column = mv_columns[1] - self.assertIsNotNone(cluster_column) + assert cluster_column is not None assert cluster_column.name == 'the;Clustering' assert cluster_column.name == mv.clustering_key[0].name assert cluster_column.table == mv.clustering_key[0].table @@ -2509,7 +2509,7 @@ def test_metadata_with_quoted_identifiers(self): assert cluster_column.is_reversed == mv.clustering_key[0].is_reversed value_column = mv_columns[2] - self.assertIsNotNone(value_column) + assert value_column is not None assert value_column.name == 'the Value' diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 5d849eef70..61c0a4eacc 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -109,7 +109,7 @@ def test_trace_id_to_resultset(self): # future should have the current trace rs = future.result() future_trace = future.get_query_trace() - self.assertIsNotNone(future_trace) + assert future_trace is not None rs_trace = rs.get_query_trace() assert rs_trace == future_trace @@ -170,7 +170,7 @@ def test_client_ip_in_trace(self): pat = re.compile(r'127.0.0.\d{1,3}') # Ensure that ip is set - self.assertIsNotNone(client_ip, "Client IP was not set in trace with C* >= 2.2") + assert client_ip is not None, "Client IP was not set in trace with C* >= 2.2" assert pat.match(client_ip), "Client IP from trace did not match the expected value" def test_trace_cl(self): @@ -189,15 +189,15 @@ def test_trace_cl(self): with self.assertRaises(Unavailable): response_future.get_query_trace(query_cl=ConsistencyLevel.THREE) # Try again with a smattering of other CL's - self.assertIsNotNone(response_future.get_query_trace(max_wait=2.0, query_cl=ConsistencyLevel.TWO).trace_id) + assert response_future.get_query_trace(max_wait=2.0, query_cl=ConsistencyLevel.TWO).trace_id is not None response_future = self.session.execute_async(statement, trace=True) response_future.result() - self.assertIsNotNone(response_future.get_query_trace(max_wait=2.0, query_cl=ConsistencyLevel.ONE).trace_id) + assert response_future.get_query_trace(max_wait=2.0, query_cl=ConsistencyLevel.ONE).trace_id is not None response_future = self.session.execute_async(statement, trace=True) response_future.result() with self.assertRaises(InvalidRequest): - self.assertIsNotNone(response_future.get_query_trace(max_wait=2.0, query_cl=ConsistencyLevel.ANY).trace_id) - self.assertIsNotNone(response_future.get_query_trace(max_wait=2.0, query_cl=ConsistencyLevel.QUORUM).trace_id) + assert response_future.get_query_trace(max_wait=2.0, query_cl=ConsistencyLevel.ANY).trace_id is not None + assert response_future.get_query_trace(max_wait=2.0, query_cl=ConsistencyLevel.QUORUM).trace_id is not None @notwindows def test_incomplete_query_trace(self): @@ -238,11 +238,11 @@ def test_incomplete_query_trace(self): # should get the events with wait False trace.populate(wait_for_complete=False) assert trace.duration is None - self.assertIsNotNone(trace.trace_id) - self.assertIsNotNone(trace.request_type) - self.assertIsNotNone(trace.parameters) + assert trace.trace_id is not None + assert trace.request_type is not None + assert trace.parameters is not None assert trace.events # non-zero list len - self.assertIsNotNone(trace.started_at) + assert trace.started_at is not None def _wait_for_trace_to_populate(self, trace_id): count = 0 @@ -283,7 +283,7 @@ def test_query_by_id(self): self.session.execute("insert into "+self.keyspace_name+"."+self.function_table_name+" (id, m) VALUES ( 1, {1: 'one', 2: 'two', 3:'three'})") results1 = self.session.execute("select id, m from {0}.{1}".format(self.keyspace_name, self.function_table_name)) - self.assertIsNotNone(results1.column_types) + assert results1.column_types is not None assert results1.column_types[0].typename == 'int' assert results1.column_types[1].typename == 'map' assert results1.column_types[0].cassname == 'Int32Type' @@ -319,7 +319,7 @@ def test_column_names(self): self.session.execute(create_table) result_set = self.session.execute("SELECT * FROM {0}.{1}".format(self.keyspace_name, self.function_table_name)) - self.assertIsNotNone(result_set.column_types) + assert result_set.column_types is not None assert result_set.column_names == [u'user', u'game', u'year', u'month', u'day', u'score'] @@ -1088,7 +1088,7 @@ def test_rk_from_bound(self): bound = self.prepared.bind((1, None)) batch = BatchStatement() batch.add(bound) - self.assertIsNotNone(batch.routing_key) + assert batch.routing_key is not None assert batch.routing_key == bound.routing_key def test_rk_from_simple(self): @@ -1097,7 +1097,7 @@ def test_rk_from_simple(self): """ batch = BatchStatement() batch.add(self.simple_statement) - self.assertIsNotNone(batch.routing_key) + assert batch.routing_key is not None assert batch.routing_key == self.simple_statement.routing_key def test_inherit_first_rk_bound(self): @@ -1113,7 +1113,7 @@ def test_inherit_first_rk_bound(self): for i in range(3): batch.add(self.prepared, (i, i)) - self.assertIsNotNone(batch.routing_key) + assert batch.routing_key is not None assert batch.routing_key == bound.routing_key def test_inherit_first_rk_simple_statement(self): @@ -1129,7 +1129,7 @@ def test_inherit_first_rk_simple_statement(self): for i in range(10): batch.add(self.prepared, (i, i)) - self.assertIsNotNone(batch.routing_key) + assert batch.routing_key is not None assert batch.routing_key == self.simple_statement.routing_key def test_inherit_first_rk_prepared_param(self): @@ -1143,7 +1143,7 @@ def test_inherit_first_rk_prepared_param(self): batch.add(bound) batch.add(self.simple_statement) - self.assertIsNotNone(batch.routing_key) + assert batch.routing_key is not None assert batch.routing_key == self.prepared.bind((1, 0)).routing_key diff --git a/tests/unit/advanced/test_graph.py b/tests/unit/advanced/test_graph.py index 6efcd2d10d..c9049a1519 100644 --- a/tests/unit/advanced/test_graph.py +++ b/tests/unit/advanced/test_graph.py @@ -355,19 +355,19 @@ def test_graph_source_convenience_attributes(self): assert not opts.is_default_source opts.set_source_default() - self.assertIsNotNone(opts.graph_source) + assert opts.graph_source is not None assert not opts.is_analytics_source assert not opts.is_graph_source assert opts.is_default_source opts.set_source_analytics() - self.assertIsNotNone(opts.graph_source) + assert opts.graph_source is not None assert opts.is_analytics_source assert not opts.is_graph_source assert not opts.is_default_source opts.set_source_graph() - self.assertIsNotNone(opts.graph_source) + assert opts.graph_source is not None assert not opts.is_analytics_source assert opts.is_graph_source assert not opts.is_default_source From 7f63c41cf092429dd6ab27fe609f7ab731011fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 20:29:42 +0200 Subject: [PATCH 054/298] Replace self.assertHasAttr with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used commands: ``` ast-grep run --pattern 'self.assertHasAttr($A, $B)' --rewrite 'assert hasattr($A, $B)' --lang python ./tests -U ``` --- .../cqlengine/model/test_class_construction.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/integration/cqlengine/model/test_class_construction.py b/tests/integration/cqlengine/model/test_class_construction.py index 8f1b4de488..1dc15e5300 100644 --- a/tests/integration/cqlengine/model/test_class_construction.py +++ b/tests/integration/cqlengine/model/test_class_construction.py @@ -39,14 +39,14 @@ class TestModel(Model): text = columns.Text() # check class attibutes - self.assertHasAttr(TestModel, '_columns') - self.assertHasAttr(TestModel, 'id') - self.assertHasAttr(TestModel, 'text') + assert hasattr(TestModel, '_columns') + assert hasattr(TestModel, 'id') + assert hasattr(TestModel, 'text') # check instance attributes inst = TestModel() - self.assertHasAttr(inst, 'id') - self.assertHasAttr(inst, 'text') + assert hasattr(inst, 'id') + assert hasattr(inst, 'text') assert inst.id is not None assert inst.text is None @@ -61,8 +61,8 @@ class TestPerson(Model): # Check that defaults are available at instantiation. inst1 = TestPerson() - self.assertHasAttr(inst1, 'first_name') - self.assertHasAttr(inst1, 'last_name') + assert hasattr(inst1, 'first_name') + assert hasattr(inst1, 'last_name') assert inst1.first_name == 'kevin' assert inst1.last_name == 'deldycke' From 42fe6409c673482471c7d4706fdbdb059adb0fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 20:31:14 +0200 Subject: [PATCH 055/298] Replace self.assertIs with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used commands: ``` ast-grep run --pattern 'self.assertIs($A, $B)' --rewrite 'assert $A is $B' --lang python ./tests -U ``` --- tests/integration/long/test_topology_change.py | 2 +- tests/integration/standard/test_cluster.py | 12 ++++++------ .../standard/test_cython_protocol_handlers.py | 3 ++- tests/integration/standard/test_metadata.py | 18 +++++++++--------- tests/integration/standard/test_types.py | 2 +- tests/unit/advanced/test_graph.py | 2 +- tests/unit/advanced/test_insights.py | 12 ++++-------- tests/unit/test_cluster.py | 2 +- tests/unit/test_control_connection.py | 2 +- tests/unit/test_host_connection_pool.py | 4 ++-- tests/unit/test_orderedmap.py | 4 ++-- tests/unit/test_policies.py | 4 ++-- tests/unit/test_resultset.py | 2 +- 13 files changed, 33 insertions(+), 36 deletions(-) diff --git a/tests/integration/long/test_topology_change.py b/tests/integration/long/test_topology_change.py index 53da367026..80540cfb2f 100644 --- a/tests/integration/long/test_topology_change.py +++ b/tests/integration/long/test_topology_change.py @@ -44,5 +44,5 @@ def test_removed_node_stops_reconnecting(self): decommission(3) wait_until(condition=lambda: state_listener.removed_host is not None, delay=2, max_attempts=50) - self.assertIs(state_listener.downed_host, state_listener.removed_host) # Just a sanity check + assert state_listener.downed_host is state_listener.removed_host # Just a sanity check assert not state_listener.removed_host.is_currently_reconnecting() diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 895053c679..fe271e8c71 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -424,7 +424,7 @@ def test_refresh_schema_keyspace(self): # only refresh one keyspace cluster.refresh_keyspace_metadata('system') current_meta = cluster.metadata.keyspaces - self.assertIs(original_meta, current_meta) + assert original_meta is current_meta current_system_meta = current_meta['system'] self.assertIsNot(original_system_meta, current_system_meta) assert original_system_meta.as_cql_query() == current_system_meta.as_cql_query() @@ -443,8 +443,8 @@ def test_refresh_schema_table(self): current_meta = cluster.metadata.keyspaces current_system_meta = current_meta['system'] current_system_schema_meta = current_system_meta.tables['local'] - self.assertIs(original_meta, current_meta) - self.assertIs(original_system_meta, current_system_meta) + assert original_meta is current_meta + assert original_system_meta is current_system_meta self.assertIsNot(original_system_schema_meta, current_system_schema_meta) assert original_system_schema_meta.as_cql_query() == current_system_schema_meta.as_cql_query() cluster.shutdown() @@ -473,7 +473,7 @@ def test_refresh_schema_type(self): current_meta = cluster.metadata.keyspaces current_test1rf_meta = current_meta[keyspace_name] current_type_meta = current_test1rf_meta.user_types[type_name] - self.assertIs(original_meta, current_meta) + assert original_meta is current_meta assert original_test1rf_meta.export_as_string() == current_test1rf_meta.export_as_string() self.assertIsNot(original_type_meta, current_type_meta) assert original_type_meta.as_cql_query() == current_type_meta.as_cql_query() @@ -506,7 +506,7 @@ def patched_wait_for_responses(*args, **kwargs): self.assertRaisesRegex(Exception, r"Schema metadata was not refreshed.*", c.refresh_schema_metadata) end_time = time.time() assert end_time - start_time >= agreement_timeout - self.assertIs(original_meta, c.metadata.keyspaces) + assert original_meta is c.metadata.keyspaces # refresh wait overrides cluster value original_meta = c.metadata.keyspaces @@ -544,7 +544,7 @@ def patched_wait_for_responses(*args, **kwargs): max_schema_agreement_wait=agreement_timeout) end_time = time.time() assert end_time - start_time >= agreement_timeout - self.assertIs(original_meta, c.metadata.keyspaces) + assert original_meta is c.metadata.keyspaces c.shutdown() def test_trace(self): diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index 9f082f6a8a..c7357759d3 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -251,7 +251,8 @@ def test_null_types(self): if isinstance(col_array, MaskedArray): had_masked = True [self.assertIsNot(col_array[i], masked) for i in mapped_index[:begin_unset]] - [self.assertIs(col_array[i], masked) for i in mapped_index[begin_unset:]] + for i in mapped_index[begin_unset:]: + assert col_array[i] is masked else: had_none = True for i in mapped_index[:begin_unset]: diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 983874d693..48e938ba0f 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -757,7 +757,7 @@ def test_refresh_metadata_for_mv(self): cluster2.shutdown() original_meta = self.cluster.metadata.keyspaces[self.keyspace_name].views['mv1'] - self.assertIs(original_meta, self.session.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views['mv1']) + assert original_meta is self.session.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views['mv1'] self.cluster.refresh_materialized_view_metadata(self.keyspace_name, 'mv1') current_meta = self.cluster.metadata.keyspaces[self.keyspace_name].views['mv1'] @@ -1705,7 +1705,7 @@ def test_functions_follow_keyspace_alter(self): try: new_keyspace_meta = self.cluster.metadata.keyspaces[self.keyspace_name] assert original_keyspace_meta != new_keyspace_meta - self.assertIs(original_keyspace_meta.functions, new_keyspace_meta.functions) + assert original_keyspace_meta.functions is new_keyspace_meta.functions finally: self.session.execute('ALTER KEYSPACE %s WITH durable_writes = true' % self.keyspace_name) @@ -1911,7 +1911,7 @@ def test_aggregates_follow_keyspace_alter(self): try: new_keyspace_meta = self.cluster.metadata.keyspaces[self.keyspace_name] assert original_keyspace_meta != new_keyspace_meta - self.assertIs(original_keyspace_meta.aggregates, new_keyspace_meta.aggregates) + assert original_keyspace_meta.aggregates is new_keyspace_meta.aggregates finally: self.session.execute('ALTER KEYSPACE %s WITH durable_writes = true' % self.keyspace_name) @@ -2020,7 +2020,7 @@ def test_bad_keyspace(self): with patch.object(self.parser_class, '_build_keyspace_metadata_internal', side_effect=self.BadMetaException): self.cluster.refresh_keyspace_metadata(self.keyspace_name) m = self.cluster.metadata.keyspaces[self.keyspace_name] - self.assertIs(m._exc_info[0], self.BadMetaException) + assert m._exc_info[0] is self.BadMetaException assert "/*\nWarning:" in m.export_as_string() def test_bad_table(self): @@ -2028,7 +2028,7 @@ def test_bad_table(self): with patch.object(self.parser_class, '_build_column_metadata', side_effect=self.BadMetaException): self.cluster.refresh_table_metadata(self.keyspace_name, self.function_name) m = self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_name] - self.assertIs(m._exc_info[0], self.BadMetaException) + assert m._exc_info[0] is self.BadMetaException assert "/*\nWarning:" in m.export_as_string() def test_bad_index(self): @@ -2037,7 +2037,7 @@ def test_bad_index(self): with patch.object(self.parser_class, '_build_index_metadata', side_effect=self.BadMetaException): self.cluster.refresh_table_metadata(self.keyspace_name, self.function_name) m = self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_name] - self.assertIs(m._exc_info[0], self.BadMetaException) + assert m._exc_info[0] is self.BadMetaException assert "/*\nWarning:" in m.export_as_string() @greaterthancass20 @@ -2046,7 +2046,7 @@ def test_bad_user_type(self): with patch.object(self.parser_class, '_build_user_type', side_effect=self.BadMetaException): self.cluster.refresh_schema_metadata() # presently do not capture these errors on udt direct refresh -- make sure it's contained during full refresh m = self.cluster.metadata.keyspaces[self.keyspace_name] - self.assertIs(m._exc_info[0], self.BadMetaException) + assert m._exc_info[0] is self.BadMetaException assert "/*\nWarning:" in m.export_as_string() @greaterthancass21 @@ -2065,7 +2065,7 @@ def test_bad_user_function(self): with patch.object(self.parser_class, '_build_function', side_effect=self.BadMetaException): self.cluster.refresh_schema_metadata() # presently do not capture these errors on udt direct refresh -- make sure it's contained during full refresh m = self.cluster.metadata.keyspaces[self.keyspace_name] - self.assertIs(m._exc_info[0], self.BadMetaException) + assert m._exc_info[0] is self.BadMetaException assert "/*\nWarning:" in m.export_as_string() @greaterthancass21 @@ -2084,7 +2084,7 @@ def test_bad_user_aggregate(self): with patch.object(self.parser_class, '_build_aggregate', side_effect=self.BadMetaException): self.cluster.refresh_schema_metadata() # presently do not capture these errors on udt direct refresh -- make sure it's contained during full refresh m = self.cluster.metadata.keyspaces[self.keyspace_name] - self.assertIs(m._exc_info[0], self.BadMetaException) + assert m._exc_info[0] is self.BadMetaException assert "/*\nWarning:" in m.export_as_string() diff --git a/tests/integration/standard/test_types.py b/tests/integration/standard/test_types.py index 6152fb4da9..4f206dbc01 100644 --- a/tests/integration/standard/test_types.py +++ b/tests/integration/standard/test_types.py @@ -394,7 +394,7 @@ def test_can_insert_empty_values_for_int32(self): try: Int32Type.support_empty_values = True results = execute_until_pass(s, "SELECT b FROM empty_values WHERE a='a'").one() - self.assertIs(EMPTY, results.b) + assert EMPTY is results.b finally: Int32Type.support_empty_values = False diff --git a/tests/unit/advanced/test_graph.py b/tests/unit/advanced/test_graph.py index c9049a1519..fe5fb1b876 100644 --- a/tests/unit/advanced/test_graph.py +++ b/tests/unit/advanced/test_graph.py @@ -384,7 +384,7 @@ def test_init(self): 'custom_payload': object()} statement = SimpleGraphStatement(**kwargs) for k, v in kwargs.items(): - self.assertIs(getattr(statement, k), v) + assert getattr(statement, k) is v # but not a bogus parameter kwargs['bogus'] = object() diff --git a/tests/unit/advanced/test_insights.py b/tests/unit/advanced/test_insights.py index 066b591f64..6646e6746f 100644 --- a/tests/unit/advanced/test_insights.py +++ b/tests/unit/advanced/test_insights.py @@ -74,8 +74,7 @@ class NoConfAsDict(object): 'namespace': ns, } # with default - self.assertIs(insights_registry.serialize(obj, default=sentinel.attr_err_default), - sentinel.attr_err_default) + assert insights_registry.serialize(obj, default=sentinel.attr_err_default) is sentinel.attr_err_default def test_successful_return(self): @@ -89,14 +88,11 @@ class SubclassSentinel(SuperclassSentinel): def superclass_sentinel_serializer(obj): return sentinel.serialized_superclass - self.assertIs(insights_registry.serialize(SuperclassSentinel()), - sentinel.serialized_superclass) - self.assertIs(insights_registry.serialize(SubclassSentinel()), - sentinel.serialized_superclass) + assert insights_registry.serialize(SuperclassSentinel()) is sentinel.serialized_superclass + assert insights_registry.serialize(SubclassSentinel()) is sentinel.serialized_superclass # with default -- same behavior - self.assertIs(insights_registry.serialize(SubclassSentinel(), default=object()), - sentinel.serialized_superclass) + assert insights_registry.serialize(SubclassSentinel(), default=object()) is sentinel.serialized_superclass class TestConfigAsDict(unittest.TestCase): diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 55c807362b..91927854e3 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -433,7 +433,7 @@ def test_exec_profile_clone(self): for attr, value in profile_attrs.items(): assert getattr(clone, attr) == getattr(active, attr) if attr in reference_attributes: - self.assertIs(getattr(clone, attr), getattr(active, attr)) + assert getattr(clone, attr) is getattr(active, attr) assert getattr(all_updated, attr) != getattr(active, attr) # cannot clone nonexistent profile diff --git a/tests/unit/test_control_connection.py b/tests/unit/test_control_connection.py index 3a6337700d..84e0740f92 100644 --- a/tests/unit/test_control_connection.py +++ b/tests/unit/test_control_connection.py @@ -498,7 +498,7 @@ def test_handle_status_change(self): } self.control_connection._handle_status_change(event) host = self.cluster.metadata.get_host(DefaultEndPoint('192.168.1.0')) - self.assertIs(host, self.cluster.down_host) + assert host is self.cluster.down_host def test_handle_schema_change(self): diff --git a/tests/unit/test_host_connection_pool.py b/tests/unit/test_host_connection_pool.py index 3b3192b36c..9fbd2debe6 100644 --- a/tests/unit/test_host_connection_pool.py +++ b/tests/unit/test_host_connection_pool.py @@ -52,7 +52,7 @@ def test_borrow_and_return(self): session.cluster.connection_factory.assert_called_once_with(host.endpoint, on_orphaned_stream_released=pool.on_orphaned_stream_released) c, request_id = pool.borrow_connection(timeout=0.01) - self.assertIs(c, conn) + assert c is conn assert 1 == conn.in_flight conn.set_keyspace_blocking.assert_called_once_with('foobarkeyspace') @@ -94,7 +94,7 @@ def test_successful_wait_for_connection(self): def get_second_conn(): c, request_id = pool.borrow_connection(1.0) - self.assertIs(conn, c) + assert conn is c pool.return_connection(c) t = Thread(target=get_second_conn) diff --git a/tests/unit/test_orderedmap.py b/tests/unit/test_orderedmap.py index b43d3c002a..01adc0d14f 100644 --- a/tests/unit/test_orderedmap.py +++ b/tests/unit/test_orderedmap.py @@ -175,6 +175,6 @@ def test_normalized_lookup(self): # type lookup is normalized by key_type # PYTHON-231 - self.assertIs(om[{'one': 1}], om[{u'one': 1}]) - self.assertIs(om[{'two': 2}], om[{u'two': 2}]) + assert om[{'one': 1}] is om[{u'one': 1}] + assert om[{'two': 2}] is om[{u'two': 2}] self.assertIsNot(om[{'one': 1}], om[{'two': 2}]) diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index 77f4da9d95..aff2e5000e 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -1356,7 +1356,7 @@ def setUp(self): Mock(name='predicate')) def _check_init(self, hfp): - self.assertIs(hfp._child_policy, self.child_policy) + assert hfp._child_policy is self.child_policy assert isinstance(hfp._hosts_lock, LockType) # we can't use a simple assertIs because we wrap the function @@ -1408,7 +1408,7 @@ def _check_host_triggered_method(self, policy, name): # method calls the child policy's method... child_policy_method.assert_called_once_with(arg, kw=kwarg) # and returns its return value - self.assertIs(result, child_policy_method.return_value) + assert result is child_policy_method.return_value def test_defer_on_up_to_child_policy(self): self._check_host_triggered_method(self.passthrough_hfp, 'on_up') diff --git a/tests/unit/test_resultset.py b/tests/unit/test_resultset.py index ac9df2193b..d5b8b2ed9f 100644 --- a/tests/unit/test_resultset.py +++ b/tests/unit/test_resultset.py @@ -221,4 +221,4 @@ def test_indexing_deprecation(self, mocked_warn): assert len(mocked_warn.mock_calls) == 1 index_warning_args = tuple(mocked_warn.mock_calls[0])[1] assert 'indexing support will be removed in 4.0' in str(index_warning_args[0]) - self.assertIs(index_warning_args[1], DeprecationWarning) + assert index_warning_args[1] is DeprecationWarning From 75f86759c5e15da3057f92844278f48ea196fa2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 20:32:42 +0200 Subject: [PATCH 056/298] Replace self.assertLess with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used commands: ``` ast-grep run --pattern 'self.assertLess($A, $B)' --rewrite 'assert $A < $B' --lang python ./tests -U ast-grep run --pattern 'self.assertLess($A, $B, $C)' --rewrite 'assert $A < $B, $C' --lang python ./tests -U ``` --- tests/integration/simulacron/test_backpressure.py | 4 ++-- tests/integration/simulacron/test_policies.py | 2 +- tests/integration/standard/test_cluster.py | 6 +++--- tests/integration/standard/test_connection.py | 2 +- tests/integration/standard/test_cython_protocol_handlers.py | 4 ++-- tests/unit/cqlengine/test_columns.py | 2 +- tests/unit/test_concurrent.py | 2 +- tests/unit/test_control_connection.py | 2 +- tests/unit/test_metadata.py | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/integration/simulacron/test_backpressure.py b/tests/integration/simulacron/test_backpressure.py index 541611cf7c..458151c25e 100644 --- a/tests/integration/simulacron/test_backpressure.py +++ b/tests/integration/simulacron/test_backpressure.py @@ -121,8 +121,8 @@ def test_queued_requests_timeout(self): # Simulacron will respond to a couple queries before cutting off reads, so we'll just verify # that only "a few" successes happened here - self.assertLess(successes, 50) - self.assertLess(self.callback_successes, 50) + assert successes < 50 + assert self.callback_successes < 50 assert self.callback_errors == len(futures) - self.callback_successes def test_cluster_busy(self): diff --git a/tests/integration/simulacron/test_policies.py b/tests/integration/simulacron/test_policies.py index 617047831e..45a76449c9 100644 --- a/tests/integration/simulacron/test_policies.py +++ b/tests/integration/simulacron/test_policies.py @@ -139,7 +139,7 @@ def test_speculative_execution(self): # idempotent prepared_statement.is_idempotent = True result = self.session.execute(prepared_statement, ("0",), execution_profile='spec_ep_brr') - self.assertLess(1, len(result.response_future.attempted_hosts)) + assert 1 < len(result.response_future.attempted_hosts) def test_speculative_and_timeout(self): """ diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index fe271e8c71..97e6c37d1c 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -513,7 +513,7 @@ def patched_wait_for_responses(*args, **kwargs): start_time = time.time() c.refresh_schema_metadata(max_schema_agreement_wait=0) end_time = time.time() - self.assertLess(end_time - start_time, agreement_timeout) + assert end_time - start_time < agreement_timeout self.assertIsNot(original_meta, c.metadata.keyspaces) assert original_meta == c.metadata.keyspaces @@ -525,7 +525,7 @@ def patched_wait_for_responses(*args, **kwargs): start_time = time.time() s = c.connect() end_time = time.time() - self.assertLess(end_time - start_time, refresh_threshold) + assert end_time - start_time < refresh_threshold assert c.metadata.keyspaces # cluster agreement wait used for refresh @@ -533,7 +533,7 @@ def patched_wait_for_responses(*args, **kwargs): start_time = time.time() c.refresh_schema_metadata() end_time = time.time() - self.assertLess(end_time - start_time, refresh_threshold) + assert end_time - start_time < refresh_threshold self.assertIsNot(original_meta, c.metadata.keyspaces) assert original_meta == c.metadata.keyspaces diff --git a/tests/integration/standard/test_connection.py b/tests/integration/standard/test_connection.py index 292edf746d..cc686a04bf 100644 --- a/tests/integration/standard/test_connection.py +++ b/tests/integration/standard/test_connection.py @@ -154,7 +154,7 @@ def test_heart_beat_timeout(self): current_host = str(rs._current_host) count += 1 time.sleep(.1) - self.assertLess(count, 100, "Never connected to the first node") + assert count < 100, "Never connected to the first node" new_connections = self.wait_for_connections(host, self.cluster) assert not test_listener.host_down # Make sure underlying new connections don't match previous ones diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index c7357759d3..a62823f238 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -69,7 +69,7 @@ def test_cython_lazy_results_paged(self): session.client_protocol_handler = LazyProtocolHandler session.default_fetch_size = 2 - self.assertLess(session.default_fetch_size, self.N_ITEMS) + assert session.default_fetch_size < self.N_ITEMS results = session.execute("SELECT * FROM test_table") @@ -105,7 +105,7 @@ def test_numpy_results_paged(self): expected_pages = (self.N_ITEMS + session.default_fetch_size - 1) // session.default_fetch_size - self.assertLess(session.default_fetch_size, self.N_ITEMS) + assert session.default_fetch_size < self.N_ITEMS results = session.execute("SELECT * FROM test_table") diff --git a/tests/unit/cqlengine/test_columns.py b/tests/unit/cqlengine/test_columns.py index 669a3f522e..cba57e88a6 100644 --- a/tests/unit/cqlengine/test_columns.py +++ b/tests/unit/cqlengine/test_columns.py @@ -33,7 +33,7 @@ def test_comparisons(self): assert not c0 == object() # __lt__ - self.assertLess(c0, c1) + assert c0 < c1 try: c0 < object() # this raises for Python 3 except TypeError: diff --git a/tests/unit/test_concurrent.py b/tests/unit/test_concurrent.py index e70a276236..0d97fb2c62 100644 --- a/tests/unit/test_concurrent.py +++ b/tests/unit/test_concurrent.py @@ -236,7 +236,7 @@ def validate_result_ordering(self, results): if "Windows" in platform.system(): assert last_time_added <= current_time_added else: - self.assertLess(last_time_added, current_time_added) + assert last_time_added < current_time_added last_time_added = current_time_added @mock_session_pools diff --git a/tests/unit/test_control_connection.py b/tests/unit/test_control_connection.py index 84e0740f92..d759e12332 100644 --- a/tests/unit/test_control_connection.py +++ b/tests/unit/test_control_connection.py @@ -644,5 +644,5 @@ def test_event_delay_timing(self): self.cluster.scheduler.mock_calls # Grabs the delay parameter from the scheduler invocation current_delay = self.cluster.scheduler.mock_calls[0][1][0] - self.assertLess(prior_delay, current_delay) + assert prior_delay < current_delay prior_delay = current_delay diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 5415f914a7..2d7b153530 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -504,7 +504,7 @@ def test_from_string(self): def test_comparison(self): tok = BytesToken.from_string('0123456789abcdef') token_high_order = uint16_unpack(tok.value[0:2]) - self.assertLess(BytesToken(uint16_pack(token_high_order - 1)), tok) + assert BytesToken(uint16_pack(token_high_order - 1)) < tok assert BytesToken(uint16_pack(token_high_order + 1)) > tok def test_comparison_unicode(self): From f3f320500fe7aa6e850613ac8f5a500f939a6019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Tue, 8 Jul 2025 20:34:01 +0200 Subject: [PATCH 057/298] Replace self.assertIsNot with plain assert Part of effort to migrate to pytest. Change was done automatically, using ast-grep tool. Used commands: ``` ast-grep run --pattern 'self.assertIsNot($A, $B)' --rewrite 'assert $A is not $B' --lang python ./tests -U ``` --- tests/integration/standard/test_cluster.py | 12 ++++++------ .../standard/test_cython_protocol_handlers.py | 3 ++- tests/integration/standard/test_metadata.py | 6 +++--- .../integration/standard/test_prepared_statements.py | 2 +- tests/integration/upgrade/test_upgrade.py | 2 +- tests/unit/test_cluster.py | 4 ++-- tests/unit/test_orderedmap.py | 2 +- tests/unit/test_parameter_binding.py | 4 ++-- tests/unit/test_policies.py | 2 +- 9 files changed, 19 insertions(+), 18 deletions(-) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 97e6c37d1c..2113a808e1 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -409,7 +409,7 @@ def test_refresh_schema(self): original_meta = cluster.metadata.keyspaces # full schema refresh, with wait cluster.refresh_schema_metadata() - self.assertIsNot(original_meta, cluster.metadata.keyspaces) + assert original_meta is not cluster.metadata.keyspaces assert original_meta == cluster.metadata.keyspaces cluster.shutdown() @@ -426,7 +426,7 @@ def test_refresh_schema_keyspace(self): current_meta = cluster.metadata.keyspaces assert original_meta is current_meta current_system_meta = current_meta['system'] - self.assertIsNot(original_system_meta, current_system_meta) + assert original_system_meta is not current_system_meta assert original_system_meta.as_cql_query() == current_system_meta.as_cql_query() cluster.shutdown() @@ -445,7 +445,7 @@ def test_refresh_schema_table(self): current_system_schema_meta = current_system_meta.tables['local'] assert original_meta is current_meta assert original_system_meta is current_system_meta - self.assertIsNot(original_system_schema_meta, current_system_schema_meta) + assert original_system_schema_meta is not current_system_schema_meta assert original_system_schema_meta.as_cql_query() == current_system_schema_meta.as_cql_query() cluster.shutdown() @@ -475,7 +475,7 @@ def test_refresh_schema_type(self): current_type_meta = current_test1rf_meta.user_types[type_name] assert original_meta is current_meta assert original_test1rf_meta.export_as_string() == current_test1rf_meta.export_as_string() - self.assertIsNot(original_type_meta, current_type_meta) + assert original_type_meta is not current_type_meta assert original_type_meta.as_cql_query() == current_type_meta.as_cql_query() cluster.shutdown() @@ -514,7 +514,7 @@ def patched_wait_for_responses(*args, **kwargs): c.refresh_schema_metadata(max_schema_agreement_wait=0) end_time = time.time() assert end_time - start_time < agreement_timeout - self.assertIsNot(original_meta, c.metadata.keyspaces) + assert original_meta is not c.metadata.keyspaces assert original_meta == c.metadata.keyspaces c.shutdown() @@ -534,7 +534,7 @@ def patched_wait_for_responses(*args, **kwargs): c.refresh_schema_metadata() end_time = time.time() assert end_time - start_time < refresh_threshold - self.assertIsNot(original_meta, c.metadata.keyspaces) + assert original_meta is not c.metadata.keyspaces assert original_meta == c.metadata.keyspaces # refresh wait overrides cluster value diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index a62823f238..05ceb8adc1 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -250,7 +250,8 @@ def test_null_types(self): # because None and `masked` have different identity and equals semantics if isinstance(col_array, MaskedArray): had_masked = True - [self.assertIsNot(col_array[i], masked) for i in mapped_index[:begin_unset]] + for i in mapped_index[:begin_unset]: + assert col_array[i] is not masked for i in mapped_index[begin_unset:]: assert col_array[i] is masked else: diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 48e938ba0f..3af0a9224f 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -761,8 +761,8 @@ def test_refresh_metadata_for_mv(self): self.cluster.refresh_materialized_view_metadata(self.keyspace_name, 'mv1') current_meta = self.cluster.metadata.keyspaces[self.keyspace_name].views['mv1'] - self.assertIsNot(current_meta, original_meta) - self.assertIsNot(original_meta, self.session.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views['mv1']) + assert current_meta is not original_meta + assert original_meta is not self.session.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views['mv1'] assert original_meta.as_cql_query() == current_meta.as_cql_query() cluster3 = TestCluster(schema_event_refresh_window=-1) @@ -1502,7 +1502,7 @@ def test_index_follows_alter(self): self.session.execute('ALTER KEYSPACE %s WITH durable_writes = false' % self.keyspace_name) old_meta = ks_meta ks_meta = self.cluster.metadata.keyspaces[self.keyspace_name] - self.assertIsNot(ks_meta, old_meta) + assert ks_meta is not old_meta table_meta = ks_meta.tables[self.table_name] assert isinstance(ks_meta.indexes[idx], IndexMetadata) assert isinstance(table_meta.indexes[idx], IndexMetadata) diff --git a/tests/integration/standard/test_prepared_statements.py b/tests/integration/standard/test_prepared_statements.py index 47419e5aa0..cac8d40fcd 100644 --- a/tests/integration/standard/test_prepared_statements.py +++ b/tests/integration/standard/test_prepared_statements.py @@ -453,7 +453,7 @@ def test_invalidated_result_metadata(self): for f in futures: assert f.result()[0] == (1, 1) - self.assertIsNot(wildcard_prepared.result_metadata, original_result_metadata) + assert wildcard_prepared.result_metadata is not original_result_metadata def test_prepared_id_is_update(self): """ diff --git a/tests/integration/upgrade/test_upgrade.py b/tests/integration/upgrade/test_upgrade.py index d1f5235bcc..6a539db60d 100644 --- a/tests/integration/upgrade/test_upgrade.py +++ b/tests/integration/upgrade/test_upgrade.py @@ -176,7 +176,7 @@ def test_schema_nodes_gets_refreshed(self): self._assert_same_token_map(token_map, self.cluster_driver.metadata.token_map) def _assert_same_token_map(self, original, new): - self.assertIsNot(original, new) + assert original is not new assert original.tokens_to_hosts_by_ks == new.tokens_to_hosts_by_ks assert original.token_to_host_owner == new.token_to_host_owner assert original.ring == new.ring diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 91927854e3..7fcdd642e9 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -426,10 +426,10 @@ def test_exec_profile_clone(self): for profile in (EXEC_PROFILE_DEFAULT, 'one'): active = session.get_execution_profile(profile) clone = session.execution_profile_clone_update(profile) - self.assertIsNot(clone, active) + assert clone is not active all_updated = session.execution_profile_clone_update(clone, **profile_attrs) - self.assertIsNot(all_updated, clone) + assert all_updated is not clone for attr, value in profile_attrs.items(): assert getattr(clone, attr) == getattr(active, attr) if attr in reference_attributes: diff --git a/tests/unit/test_orderedmap.py b/tests/unit/test_orderedmap.py index 01adc0d14f..653f427453 100644 --- a/tests/unit/test_orderedmap.py +++ b/tests/unit/test_orderedmap.py @@ -177,4 +177,4 @@ def test_normalized_lookup(self): # PYTHON-231 assert om[{'one': 1}] is om[{u'one': 1}] assert om[{'two': 2}] is om[{u'two': 2}] - self.assertIsNot(om[{'one': 1}], om[{'two': 2}]) + assert om[{'one': 1}] is not om[{'two': 2}] diff --git a/tests/unit/test_parameter_binding.py b/tests/unit/test_parameter_binding.py index 7827e354d2..dfd1ffff87 100644 --- a/tests/unit/test_parameter_binding.py +++ b/tests/unit/test_parameter_binding.py @@ -170,7 +170,7 @@ def test_bind_none(self): old_values = self.bound.values self.bound.bind((0, 0, 0, None)) - self.assertIsNot(self.bound.values, old_values) + assert self.bound.values is not old_values assert self.bound.values[-1] == None def test_unset_value(self): @@ -194,7 +194,7 @@ def test_missing_value(self): old_values = self.bound.values self.bound.bind((0, 0, 0)) - self.assertIsNot(self.bound.values, old_values) + assert self.bound.values is not old_values assert self.bound.values[-1] == UNSET_VALUE def test_unset_value(self): diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index aff2e5000e..de06a9b59c 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -1345,7 +1345,7 @@ def test_ec2_multi_region_translator(self, *_): ec2t = EC2MultiRegionTranslator() addr = '127.0.0.1' translated = ec2t.translate(addr) - self.assertIsNot(translated, addr) # verifies that the resolver path is followed + assert translated is not addr # verifies that the resolver path is followed assert translated == addr # and that it resolves to the same address From 2fe564e1270df0c13dd543819d23a4c6dbc47284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 13:09:01 +0200 Subject: [PATCH 058/298] Replace self.assertRegex with new util function This is part of migration effort to pytest. This commit was done by hand. I introduced a new util function, and replaced the usages of self.assertRegex with it. Why this way? If we ever need better error messages for those asserts, we'll be able to do this by just modifying this function, not all callsites. --- .../cqlengine/management/test_compaction_settings.py | 9 +++++---- tests/integration/cqlengine/test_batch_query.py | 3 ++- tests/integration/standard/test_client_warnings.py | 9 +++++---- tests/integration/standard/test_metadata.py | 8 ++++---- tests/unit/advanced/test_graph.py | 4 ++-- tests/unit/test_connection.py | 4 ++-- tests/unit/test_timestamps.py | 3 ++- tests/util.py | 6 +++++- 8 files changed, 27 insertions(+), 19 deletions(-) diff --git a/tests/integration/cqlengine/management/test_compaction_settings.py b/tests/integration/cqlengine/management/test_compaction_settings.py index f705949cfb..25484b30c9 100644 --- a/tests/integration/cqlengine/management/test_compaction_settings.py +++ b/tests/integration/cqlengine/management/test_compaction_settings.py @@ -20,6 +20,7 @@ from cassandra.cqlengine.models import Model from tests.integration.cqlengine.base import BaseCassEngTestCase +from tests.util import assertRegex class LeveledCompactionTestTable(Model): @@ -82,7 +83,7 @@ def test_alter_actually_alters(self): table_meta = _get_table_metadata(tmp) - self.assertRegex(table_meta.export_as_string(), '.*SizeTieredCompactionStrategy.*') + assertRegex(table_meta.export_as_string(), '.*SizeTieredCompactionStrategy.*') def test_alter_options(self): @@ -96,11 +97,11 @@ class AlterTable(Model): drop_table(AlterTable) sync_table(AlterTable) table_meta = _get_table_metadata(AlterTable) - self.assertRegex(table_meta.export_as_string(), ".*'sstable_size_in_mb': '64'.*") + assertRegex(table_meta.export_as_string(), ".*'sstable_size_in_mb': '64'.*") AlterTable.__options__['compaction']['sstable_size_in_mb'] = '128' sync_table(AlterTable) table_meta = _get_table_metadata(AlterTable) - self.assertRegex(table_meta.export_as_string(), ".*'sstable_size_in_mb': '128'.*") + assertRegex(table_meta.export_as_string(), ".*'sstable_size_in_mb': '128'.*") class OptionsTest(BaseCassEngTestCase): @@ -124,7 +125,7 @@ def _verify_options(self, table_meta, expected_options): attr = "'%s': '%s'" % (subname, subvalue.split('.')[-1]) found_at = cql.find(attr, start) else: - + assert found_at > start assert found_at < end diff --git a/tests/integration/cqlengine/test_batch_query.py b/tests/integration/cqlengine/test_batch_query.py index e8f47642b7..6cf33c9ef9 100644 --- a/tests/integration/cqlengine/test_batch_query.py +++ b/tests/integration/cqlengine/test_batch_query.py @@ -20,6 +20,7 @@ from cassandra.cqlengine.models import Model from cassandra.cqlengine.query import BatchQuery from tests.integration.cqlengine.base import BaseCassEngTestCase +from tests.util import assertRegex from unittest.mock import patch @@ -225,7 +226,7 @@ def my_callback(*args, **kwargs): batch.execute() batch.execute() assert len(w) == 2 # package filter setup to warn always - self.assertRegex(str(w[0].message), r"^Batch.*multiple.*") + assertRegex(str(w[0].message), r"^Batch.*multiple.*") def test_disable_multiple_callback_warning(self): """ diff --git a/tests/integration/standard/test_client_warnings.py b/tests/integration/standard/test_client_warnings.py index 2fa1d1a29b..f96a8ba8ad 100644 --- a/tests/integration/standard/test_client_warnings.py +++ b/tests/integration/standard/test_client_warnings.py @@ -19,6 +19,7 @@ from tests.integration import (use_singledc, PROTOCOL_VERSION, local, TestCluster, requires_custom_payload, xfail_scylla) +from tests.util import assertRegex def setup_module(): @@ -71,7 +72,7 @@ def test_warning_basic(self): future = self.session.execute_async(self.warn_batch) future.result() assert len(future.warnings) == 1 - self.assertRegex(future.warnings[0], 'Batch.*exceeding.*') + assertRegex(future.warnings[0], 'Batch.*exceeding.*') def test_warning_with_trace(self): """ @@ -87,7 +88,7 @@ def test_warning_with_trace(self): future = self.session.execute_async(self.warn_batch, trace=True) future.result() assert len(future.warnings) == 1 - self.assertRegex(future.warnings[0], 'Batch.*exceeding.*') + assertRegex(future.warnings[0], 'Batch.*exceeding.*') assert future.get_query_trace() is not None @local @@ -107,7 +108,7 @@ def test_warning_with_custom_payload(self): future = self.session.execute_async(self.warn_batch, custom_payload=payload) future.result() assert len(future.warnings) == 1 - self.assertRegex(future.warnings[0], 'Batch.*exceeding.*') + assertRegex(future.warnings[0], 'Batch.*exceeding.*') self.assertDictEqual(future.custom_payload, payload) @local @@ -127,6 +128,6 @@ def test_warning_with_trace_and_custom_payload(self): future = self.session.execute_async(self.warn_batch, trace=True, custom_payload=payload) future.result() assert len(future.warnings) == 1 - self.assertRegex(future.warnings[0], 'Batch.*exceeding.*') + assertRegex(future.warnings[0], 'Batch.*exceeding.*') assert future.get_query_trace() is not None self.assertDictEqual(future.custom_payload, payload) diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 3af0a9224f..e480588c7a 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -45,7 +45,7 @@ requires_collection_indexes, SCYLLA_VERSION, xfail_scylla, xfail_scylla_version_lt, requirescompactstorage) -from tests.util import wait_until +from tests.util import wait_until, assertRegex log = logging.getLogger(__name__) @@ -1680,7 +1680,7 @@ def test_function_no_parameters(self): with self.VerifiedFunction(self, **kwargs) as vf: fn_meta = self.keyspace_function_meta[vf.signature] - self.assertRegex(fn_meta.as_cql_query(), r'CREATE FUNCTION.*%s\(\) .*' % kwargs['name']) + assertRegex(fn_meta.as_cql_query(), r'CREATE FUNCTION.*%s\(\) .*' % kwargs['name']) def test_functions_follow_keyspace_alter(self): """ @@ -1728,12 +1728,12 @@ def test_function_cql_called_on_null(self): kwargs['called_on_null_input'] = True with self.VerifiedFunction(self, **kwargs) as vf: fn_meta = self.keyspace_function_meta[vf.signature] - self.assertRegex(fn_meta.as_cql_query(), r'CREATE FUNCTION.*\) CALLED ON NULL INPUT RETURNS .*') + assertRegex(fn_meta.as_cql_query(), r'CREATE FUNCTION.*\) CALLED ON NULL INPUT RETURNS .*') kwargs['called_on_null_input'] = False with self.VerifiedFunction(self, **kwargs) as vf: fn_meta = self.keyspace_function_meta[vf.signature] - self.assertRegex(fn_meta.as_cql_query(), r'CREATE FUNCTION.*\) RETURNS NULL ON NULL INPUT RETURNS .*') + assertRegex(fn_meta.as_cql_query(), r'CREATE FUNCTION.*\) RETURNS NULL ON NULL INPUT RETURNS .*') @requires_java_udf diff --git a/tests/unit/advanced/test_graph.py b/tests/unit/advanced/test_graph.py index fe5fb1b876..696b26661c 100644 --- a/tests/unit/advanced/test_graph.py +++ b/tests/unit/advanced/test_graph.py @@ -23,7 +23,7 @@ graph_result_row_factory, single_object_row_factory, Vertex, Edge, Path, VertexProperty) from cassandra.datastax.graph.query import _graph_options - +from tests.util import assertRegex class GraphResultTests(unittest.TestCase): @@ -257,7 +257,7 @@ def test_init_unknown_kwargs(self): with warnings.catch_warnings(record=True) as w: GraphOptions(unknown_param=42) assert len(w) == 1 - self.assertRegex(str(w[0].message), r"^Unknown keyword.*GraphOptions.*") + assertRegex(str(w[0].message), r"^Unknown keyword.*GraphOptions.*") def test_update(self): opts = GraphOptions(**self.api_params) diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 0ebcb8307f..10233f09f1 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -27,7 +27,7 @@ from cassandra.protocol import (write_stringmultimap, write_int, write_string, SupportedMessage, ProtocolHandler) -from tests.util import wait_until +from tests.util import wait_until, assertRegex class ConnectionTest(unittest.TestCase): @@ -383,7 +383,7 @@ def send_msg(msg, req_id, msg_callback): connection.defunct.assert_has_calls([call(ANY)] * get_holders.call_count) exc = connection.defunct.call_args_list[0][0][0] assert isinstance(exc, ConnectionException) - self.assertRegex(exc.args[0], r'^Received unexpected response to OptionsMessage.*') + assertRegex(exc.args[0], r'^Received unexpected response to OptionsMessage.*') holder.return_connection.assert_has_calls( [call(connection)] * get_holders.call_count) diff --git a/tests/unit/test_timestamps.py b/tests/unit/test_timestamps.py index 3dfb23270e..70280a3eb6 100644 --- a/tests/unit/test_timestamps.py +++ b/tests/unit/test_timestamps.py @@ -16,6 +16,7 @@ from unittest import mock from cassandra import timestamps +from tests.util import assertRegex from threading import Thread, Lock @@ -104,7 +105,7 @@ def assertLastCallArgRegex(self, call, pattern): last_warn_args, last_warn_kwargs = call assert len(last_warn_args) == 1 assert len(last_warn_kwargs) == 0 - self.assertRegex(last_warn_args[0], pattern) + assertRegex(last_warn_args[0], pattern) def test_basic_log_content(self): """ diff --git a/tests/util.py b/tests/util.py index 5c7ac2416f..ef6cb5b857 100644 --- a/tests/util.py +++ b/tests/util.py @@ -11,9 +11,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - + import time from functools import wraps +import re def wait_until(condition, delay, max_attempts): @@ -72,3 +73,6 @@ def wrapper(*args, **kwargs): func(*args, **kwargs) return wrapper return decorator + +def assertRegex(text: str, pattern: str): + assert re.search(pattern, text) From daafc958a127fde23115d0807062067ccf44741d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 13:53:47 +0200 Subject: [PATCH 059/298] Replace collection asserts with util methods Replaces usages of self.assertListEqual and other collection-related asserts with newly introduced util methods. Those new metods still call unittest ones under the hood, so why do this? As with previous commits, the goal is to migrate to pytest, which requires no using subclasses of TestCase. Offocial migration tool (unittest2pytest) replaces those methods with just equality asserts. This does not preseve semantics perfectly, so I opted for a safer option for now. We can of course change that in the future. --- .../cqlengine/model/test_model_io.py | 3 ++- .../standard/test_client_warnings.py | 6 ++--- tests/integration/standard/test_cluster.py | 3 ++- tests/integration/standard/test_metadata.py | 8 +++--- tests/integration/standard/test_query.py | 3 ++- .../integration/standard/test_query_paging.py | 26 ++++++++++--------- tests/unit/test_metadata.py | 23 ++++++++-------- tests/unit/test_orderedmap.py | 7 ++--- tests/unit/test_parameter_binding.py | 4 ++- tests/unit/test_resultset.py | 20 +++++++------- tests/unit/test_sortedset.py | 12 +++++---- tests/util.py | 18 +++++++++++++ 12 files changed, 82 insertions(+), 51 deletions(-) diff --git a/tests/integration/cqlengine/model/test_model_io.py b/tests/integration/cqlengine/model/test_model_io.py index 96c0647e3b..f55815310a 100644 --- a/tests/integration/cqlengine/model/test_model_io.py +++ b/tests/integration/cqlengine/model/test_model_io.py @@ -33,6 +33,7 @@ from tests.integration import PROTOCOL_VERSION, greaterthanorequalcass3_10 from tests.integration.cqlengine.base import BaseCassEngTestCase from tests.integration.cqlengine import DEFAULT_KEYSPACE +from tests.util import assertSetEqual class TestModel(Model): @@ -110,7 +111,7 @@ def test_model_read_as_dict(self): } assert sorted(tm.keys()) == sorted(column_dict.keys()) - self.assertSetEqual(set(tm.values()), set(column_dict.values())) + assertSetEqual(set(tm.values()), set(column_dict.values())) assert sorted(tm.items(), key=itemgetter(0)) == sorted(column_dict.items(), key=itemgetter(0)) assert len(tm) == len(column_dict) for column_id in column_dict.keys(): diff --git a/tests/integration/standard/test_client_warnings.py b/tests/integration/standard/test_client_warnings.py index f96a8ba8ad..781b5b7860 100644 --- a/tests/integration/standard/test_client_warnings.py +++ b/tests/integration/standard/test_client_warnings.py @@ -19,7 +19,7 @@ from tests.integration import (use_singledc, PROTOCOL_VERSION, local, TestCluster, requires_custom_payload, xfail_scylla) -from tests.util import assertRegex +from tests.util import assertRegex, assertDictEqual def setup_module(): @@ -109,7 +109,7 @@ def test_warning_with_custom_payload(self): future.result() assert len(future.warnings) == 1 assertRegex(future.warnings[0], 'Batch.*exceeding.*') - self.assertDictEqual(future.custom_payload, payload) + assertDictEqual(future.custom_payload, payload) @local @requires_custom_payload @@ -130,4 +130,4 @@ def test_warning_with_trace_and_custom_payload(self): assert len(future.warnings) == 1 assertRegex(future.warnings[0], 'Batch.*exceeding.*') assert future.get_query_trace() is not None - self.assertDictEqual(future.custom_payload, payload) + assertDictEqual(future.custom_payload, payload) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 2113a808e1..07551ef6e2 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -44,6 +44,7 @@ get_unsupported_upper_protocol, protocolv6, local, CASSANDRA_IP, greaterthanorequalcass30, \ lessthanorequalcass40, TestCluster, PROTOCOL_VERSION, xfail_scylla, incorrect_test from tests.integration.util import assert_quiescent_pool_state +from tests.util import assertListEqual import sys log = logging.getLogger(__name__) @@ -743,7 +744,7 @@ def test_idle_heartbeat(self): expected_ids = connection_request_ids[id(c)] expected_ids.rotate(-1) with c.lock: - self.assertListEqual(list(c.request_ids), list(expected_ids)) + assertListEqual(list(c.request_ids), list(expected_ids)) # assert idle status assert all(c.is_idle for c in connections) diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index e480588c7a..c9d6da0499 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -45,7 +45,7 @@ requires_collection_indexes, SCYLLA_VERSION, xfail_scylla, xfail_scylla_version_lt, requirescompactstorage) -from tests.util import wait_until, assertRegex +from tests.util import wait_until, assertRegex, assertDictEqual, assertListEqual log = logging.getLogger(__name__) @@ -1832,7 +1832,7 @@ def test_init_cond(self): cql_init = encoder.cql_encode_all_types(init_cond) with self.VerifiedAggregate(self, **self.make_aggregate_kwargs('extend_list', 'list', init_cond=cql_init)) as va: list_res = s.execute("SELECT %s(v) AS list_res FROM t" % va.function_kwargs['name']).one().list_res - self.assertListEqual(list_res[:len(init_cond)], init_cond) + assertListEqual(list_res[:len(init_cond)], init_cond) assert set(i for i in list_res[len(init_cond):]) == set(str(i) for i in expected_values) # map @@ -2199,8 +2199,8 @@ def test_materialized_view_metadata_drop(self): assert "mv1" not in self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views assert "mv1" not in self.cluster.metadata.keyspaces[self.keyspace_name].views - self.assertDictEqual({}, self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) - self.assertDictEqual({}, self.cluster.metadata.keyspaces[self.keyspace_name].views) + assertDictEqual({}, self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) + assertDictEqual({}, self.cluster.metadata.keyspaces[self.keyspace_name].views) self.session.execute( "CREATE MATERIALIZED VIEW {0}.mv1 AS SELECT pk, c FROM {0}.{1} " diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 61c0a4eacc..fd1ce9cd65 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -29,6 +29,7 @@ USE_CASS_EXTERNAL, greaterthanorequalcass40, TestCluster, xfail_scylla from tests import notwindows from tests.integration import greaterthanorequalcass30, get_node +from tests.util import assertListEqual import time import random @@ -116,7 +117,7 @@ def test_trace_id_to_resultset(self): assert rs_trace.events assert len(rs_trace.events) == len(future_trace.events) - self.assertListEqual([rs_trace], rs.get_all_query_traces()) + assertListEqual([rs_trace], rs.get_all_query_traces()) def test_trace_ignores_row_factory(self): with TestCluster( diff --git a/tests/integration/standard/test_query_paging.py b/tests/integration/standard/test_query_paging.py index 6b7c5aba87..84df047927 100644 --- a/tests/integration/standard/test_query_paging.py +++ b/tests/integration/standard/test_query_paging.py @@ -27,6 +27,8 @@ from cassandra.policies import HostDistance from cassandra.query import SimpleStatement +from tests.util import assertSequenceEqual + def setup_module(): use_singledc() @@ -161,8 +163,8 @@ def test_paging_verify_with_composite_keys(self): result_array.append(result.k2) value_array.append(result.v) - self.assertSequenceEqual(range(100), result_array) - self.assertSequenceEqual(range(1, 101), value_array) + assertSequenceEqual(range(100), result_array) + assertSequenceEqual(range(1, 101), value_array) statement = SimpleStatement("SELECT * FROM test3rf.test_paging_verify_2") results = self.session.execute(statement) @@ -172,8 +174,8 @@ def test_paging_verify_with_composite_keys(self): result_array.append(result.k2) value_array.append(result.v) - self.assertSequenceEqual(range(100), result_array) - self.assertSequenceEqual(range(1, 101), value_array) + assertSequenceEqual(range(100), result_array) + assertSequenceEqual(range(1, 101), value_array) results = self.session.execute(prepared) result_array = [] @@ -182,8 +184,8 @@ def test_paging_verify_with_composite_keys(self): result_array.append(result.k2) value_array.append(result.v) - self.assertSequenceEqual(range(100), result_array) - self.assertSequenceEqual(range(1, 101), value_array) + assertSequenceEqual(range(100), result_array) + assertSequenceEqual(range(1, 101), value_array) def test_async_paging(self): statements_and_params = zip(cycle(["INSERT INTO test3rf.test (k, v) VALUES (%s, 0)"]), @@ -227,8 +229,8 @@ def test_async_paging_verify_writes(self): result_array.append(result.k2) value_array.append(result.v) - self.assertSequenceEqual(range(100), result_array) - self.assertSequenceEqual(range(1, 101), value_array) + assertSequenceEqual(range(100), result_array) + assertSequenceEqual(range(1, 101), value_array) statement = SimpleStatement("SELECT * FROM test3rf.test_async_paging_verify") results = self.session.execute_async(statement).result() @@ -238,8 +240,8 @@ def test_async_paging_verify_writes(self): result_array.append(result.k2) value_array.append(result.v) - self.assertSequenceEqual(range(100), result_array) - self.assertSequenceEqual(range(1, 101), value_array) + assertSequenceEqual(range(100), result_array) + assertSequenceEqual(range(1, 101), value_array) results = self.session.execute_async(prepared).result() result_array = [] @@ -248,8 +250,8 @@ def test_async_paging_verify_writes(self): result_array.append(result.k2) value_array.append(result.v) - self.assertSequenceEqual(range(100), result_array) - self.assertSequenceEqual(range(1, 101), value_array) + assertSequenceEqual(range(100), result_array) + assertSequenceEqual(range(1, 101), value_array) def test_paging_callbacks(self): """ diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 2d7b153530..05e2e50d42 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -33,6 +33,7 @@ Metadata, TokenMap, ReplicationFactor) from cassandra.policies import SimpleConvictionPolicy from cassandra.pool import Host +from tests.util import assertCountEqual log = logging.getLogger(__name__) @@ -215,7 +216,7 @@ def test_nts_make_token_replica_map(self): nts = NetworkTopologyStrategy({'dc1': 2, 'dc2': 2, 'dc3': 1}) replica_map = nts.make_token_replica_map(token_to_host_owner, ring) - self.assertItemsEqual(replica_map[MD5Token(0)], (dc1_1, dc1_2, dc2_1, dc2_2, dc3_1)) + assertCountEqual(replica_map[MD5Token(0)], (dc1_1, dc1_2, dc2_1, dc2_2, dc3_1)) def test_nts_token_performance(self): """ @@ -296,7 +297,7 @@ def test_nts_make_token_replica_map_multi_rack(self): replica_map = nts.make_token_replica_map(token_to_host_owner, ring) token_replicas = replica_map[MD5Token(0)] - self.assertItemsEqual(token_replicas, (dc1_1, dc1_2, dc1_3, dc2_1, dc2_3)) + assertCountEqual(token_replicas, (dc1_1, dc1_2, dc1_3, dc2_1, dc2_3)) def test_nts_make_token_replica_map_empty_dc(self): host = Host('1', SimpleConvictionPolicy) @@ -324,19 +325,19 @@ def test_simple_strategy_make_token_replica_map(self): ring = [MD5Token(0), MD5Token(100), MD5Token(200)] rf1_replicas = SimpleStrategy({'replication_factor': '1'}).make_token_replica_map(token_to_host_owner, ring) - self.assertItemsEqual(rf1_replicas[MD5Token(0)], [host1]) - self.assertItemsEqual(rf1_replicas[MD5Token(100)], [host2]) - self.assertItemsEqual(rf1_replicas[MD5Token(200)], [host3]) + assertCountEqual(rf1_replicas[MD5Token(0)], [host1]) + assertCountEqual(rf1_replicas[MD5Token(100)], [host2]) + assertCountEqual(rf1_replicas[MD5Token(200)], [host3]) rf2_replicas = SimpleStrategy({'replication_factor': '2'}).make_token_replica_map(token_to_host_owner, ring) - self.assertItemsEqual(rf2_replicas[MD5Token(0)], [host1, host2]) - self.assertItemsEqual(rf2_replicas[MD5Token(100)], [host2, host3]) - self.assertItemsEqual(rf2_replicas[MD5Token(200)], [host3, host1]) + assertCountEqual(rf2_replicas[MD5Token(0)], [host1, host2]) + assertCountEqual(rf2_replicas[MD5Token(100)], [host2, host3]) + assertCountEqual(rf2_replicas[MD5Token(200)], [host3, host1]) rf3_replicas = SimpleStrategy({'replication_factor': '3'}).make_token_replica_map(token_to_host_owner, ring) - self.assertItemsEqual(rf3_replicas[MD5Token(0)], [host1, host2, host3]) - self.assertItemsEqual(rf3_replicas[MD5Token(100)], [host2, host3, host1]) - self.assertItemsEqual(rf3_replicas[MD5Token(200)], [host3, host1, host2]) + assertCountEqual(rf3_replicas[MD5Token(0)], [host1, host2, host3]) + assertCountEqual(rf3_replicas[MD5Token(100)], [host2, host3, host1]) + assertCountEqual(rf3_replicas[MD5Token(200)], [host3, host1, host2]) def test_ss_equals(self): assert SimpleStrategy({'replication_factor': '1'}) != NetworkTopologyStrategy({'dc1': 2}) diff --git a/tests/unit/test_orderedmap.py b/tests/unit/test_orderedmap.py index 653f427453..f3c65c16fd 100644 --- a/tests/unit/test_orderedmap.py +++ b/tests/unit/test_orderedmap.py @@ -16,6 +16,7 @@ from cassandra.util import OrderedMap, OrderedMapSerializedKey from cassandra.cqltypes import EMPTY, UTF8Type, lookup_casstype +from tests.util import assertListEqual class OrderedMapTest(unittest.TestCase): def test_init(self): @@ -54,21 +55,21 @@ def test_keys(self): keys = ['first', 'middle', 'last'] om = OrderedMap(zip(keys, range(len(keys)))) - self.assertListEqual(list(om.keys()), keys) + assertListEqual(list(om.keys()), keys) def test_values(self): keys = ['first', 'middle', 'last'] values = list(range(len(keys))) om = OrderedMap(zip(keys, values)) - self.assertListEqual(list(om.values()), values) + assertListEqual(list(om.values()), values) def test_items(self): keys = ['first', 'middle', 'last'] items = list(zip(keys, range(len(keys)))) om = OrderedMap(items) - self.assertListEqual(list(om.items()), items) + assertListEqual(list(om.items()), items) def test_get(self): keys = ['first', 'middle', 'last'] diff --git a/tests/unit/test_parameter_binding.py b/tests/unit/test_parameter_binding.py index dfd1ffff87..03e23a172a 100644 --- a/tests/unit/test_parameter_binding.py +++ b/tests/unit/test_parameter_binding.py @@ -21,6 +21,8 @@ from cassandra.cqltypes import Int32Type from cassandra.util import OrderedDict +from tests.util import assertListEqual + class ParamBindingTest(unittest.TestCase): @@ -162,7 +164,7 @@ def test_values_none(self): result_metadata=None, result_metadata_id=None) bound = prepared_statement.bind(None) - self.assertListEqual(bound.values, []) + assertListEqual(bound.values, []) def test_bind_none(self): self.bound.bind({'rk0': 0, 'rk1': 0, 'ck0': 0, 'v0': None}) diff --git a/tests/unit/test_resultset.py b/tests/unit/test_resultset.py index d5b8b2ed9f..c1dc1afae1 100644 --- a/tests/unit/test_resultset.py +++ b/tests/unit/test_resultset.py @@ -18,6 +18,8 @@ from cassandra.cluster import ResultSet from cassandra.query import named_tuple_factory, dict_factory, tuple_factory +from tests.util import assertListEqual + class ResultSetTests(unittest.TestCase): @@ -25,7 +27,7 @@ def test_iter_non_paged(self): expected = list(range(10)) rs = ResultSet(Mock(has_more_pages=False), expected) itr = iter(rs) - self.assertListEqual(list(itr), expected) + assertListEqual(list(itr), expected) def test_iter_paged(self): expected = list(range(10)) @@ -35,7 +37,7 @@ def test_iter_paged(self): itr = iter(rs) # this is brittle, depends on internal impl details. Would like to find a better way type(response_future).has_more_pages = PropertyMock(side_effect=(True, True, False)) # after init to avoid side effects being consumed by init - self.assertListEqual(list(itr), expected) + assertListEqual(list(itr), expected) def test_iter_paged_with_empty_pages(self): expected = list(range(10)) @@ -48,7 +50,7 @@ def test_iter_paged_with_empty_pages(self): ] rs = ResultSet(response_future, []) itr = iter(rs) - self.assertListEqual(list(itr), expected) + assertListEqual(list(itr), expected) def test_list_non_paged(self): # list access on RS for backwards-compatibility @@ -121,8 +123,8 @@ def test_index_list_mode(self): assert rs[0] == expected[0] # resusable iteration - self.assertListEqual(list(rs), expected) - self.assertListEqual(list(rs), expected) + assertListEqual(list(rs), expected) + assertListEqual(list(rs), expected) assert rs @@ -136,8 +138,8 @@ def test_index_list_mode(self): assert rs[0] == expected[0] assert rs[9] == expected[9] # resusable iteration - self.assertListEqual(list(rs), expected) - self.assertListEqual(list(rs), expected) + assertListEqual(list(rs), expected) + assertListEqual(list(rs), expected) assert rs @@ -150,7 +152,7 @@ def test_eq(self): assert rs == expected # results can be iterated or indexed once we're materialized - self.assertListEqual(list(rs), expected) + assertListEqual(list(rs), expected) assert rs[9] == expected[9] assert rs @@ -163,7 +165,7 @@ def test_eq(self): assert rs == expected # results can be iterated or indexed once we're materialized - self.assertListEqual(list(rs), expected) + assertListEqual(list(rs), expected) assert rs[9] == expected[9] assert rs diff --git a/tests/unit/test_sortedset.py b/tests/unit/test_sortedset.py index da3afddaf6..915d5f52f7 100644 --- a/tests/unit/test_sortedset.py +++ b/tests/unit/test_sortedset.py @@ -17,6 +17,8 @@ from cassandra.util import sortedset from cassandra.cqltypes import EMPTY +from tests.util import assertListEqual + from datetime import datetime from itertools import permutations @@ -213,7 +215,7 @@ def test_delitem(self): expected = [1,2,3,4] ss = sortedset(expected) for i in range(len(ss)): - self.assertListEqual(list(ss), expected[i:]) + assertListEqual(list(ss), expected[i:]) del ss[0] with self.assertRaises(IndexError): ss[0] @@ -222,11 +224,11 @@ def test_delslice(self): expected = [1, 2, 3, 4, 5] ss = sortedset(expected) del ss[1:3] - self.assertListEqual(list(ss), [1, 4, 5]) + assertListEqual(list(ss), [1, 4, 5]) del ss[-1:] - self.assertListEqual(list(ss), [1, 4]) + assertListEqual(list(ss), [1, 4]) del ss[1:] - self.assertListEqual(list(ss), [1]) + assertListEqual(list(ss), [1]) del ss[:] assert not ss with self.assertRaises(IndexError): @@ -234,7 +236,7 @@ def test_delslice(self): def test_reversed(self): expected = range(10) - self.assertListEqual(list(reversed(sortedset(expected))), list(reversed(expected))) + assertListEqual(list(reversed(sortedset(expected))), list(reversed(expected))) def test_operators(self): diff --git a/tests/util.py b/tests/util.py index ef6cb5b857..998ff3609e 100644 --- a/tests/util.py +++ b/tests/util.py @@ -15,6 +15,7 @@ import time from functools import wraps import re +import unittest def wait_until(condition, delay, max_attempts): @@ -76,3 +77,20 @@ def wrapper(*args, **kwargs): def assertRegex(text: str, pattern: str): assert re.search(pattern, text) + +unittest_test_case = unittest.TestCase() + +def assertSequenceEqual(a, b, seq_type = None): + unittest_test_case.assertSequenceEqual(a, b, seq_type=seq_type) + +def assertDictEqual(a, b): + assertSequenceEqual(a, b, seq_type=dict) + +def assertListEqual(a, b): + assertSequenceEqual(a, b, seq_type=list) + +def assertSetEqual(a, b): + assertSequenceEqual(a, b, seq_type=set) + +def assertCountEqual(a, b): + unittest_test_case.assertCountEqual(a, b) From b8776a34d509a865bf545c5269374bb8f7eb9cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 14:01:35 +0200 Subject: [PATCH 060/298] Replace self.assertAlmostEqual with pytest.approx pytest.approx is more capable - it can replicate all functionality of assertAlmostEqual, but also can perform comparisons with relative error. --- .../cqlengine/columns/test_container_columns.py | 7 +++---- .../cqlengine/query/test_datetime_queries.py | 4 ++-- tests/integration/long/test_failure_types.py | 5 ++--- tests/integration/long/test_large_data.py | 3 ++- tests/integration/standard/test_cluster.py | 4 ++-- tests/integration/standard/test_connection.py | 2 +- tests/integration/standard/test_types.py | 5 +++-- tests/unit/io/utils.py | 3 ++- tests/unit/test_time_util.py | 16 ++++++++-------- tests/unit/test_types.py | 3 ++- tests/util.py | 3 +++ 11 files changed, 30 insertions(+), 25 deletions(-) diff --git a/tests/integration/cqlengine/columns/test_container_columns.py b/tests/integration/cqlengine/columns/test_container_columns.py index e0a30d8b6c..6b6cb929e7 100644 --- a/tests/integration/cqlengine/columns/test_container_columns.py +++ b/tests/integration/cqlengine/columns/test_container_columns.py @@ -30,6 +30,7 @@ from tests.integration.cqlengine import is_prepend_reversed from tests.integration.cqlengine.base import BaseCassEngTestCase from tests.integration import greaterthancass20, CASSANDRA_VERSION +import pytest log = logging.getLogger(__name__) @@ -408,8 +409,8 @@ def test_io_success(self): assert m2.int_map[1] == k1 assert m2.int_map[2] == k2 - self.assertAlmostEqual(get_total_seconds(now - m2.text_map['now']), 0, 2) - self.assertAlmostEqual(get_total_seconds(then - m2.text_map['then']), 0, 2) + assert get_total_seconds(now - m2.text_map['now']) == pytest.approx(0, abs=1e-2) + assert get_total_seconds(then - m2.text_map['then']) == pytest.approx(0, abs=1e-2) def test_type_validation(self): """ @@ -974,5 +975,3 @@ def test_insert_none(self): assert [] == tmp.list_list assert {} == tmp.map_list assert set() == tmp.set_tuple - - diff --git a/tests/integration/cqlengine/query/test_datetime_queries.py b/tests/integration/cqlengine/query/test_datetime_queries.py index ba1c90bb9e..e61e1bdd96 100644 --- a/tests/integration/cqlengine/query/test_datetime_queries.py +++ b/tests/integration/cqlengine/query/test_datetime_queries.py @@ -15,6 +15,7 @@ from datetime import datetime, timedelta from uuid import uuid4 from cassandra.cqlengine.functions import get_total_seconds +import pytest from tests.integration.cqlengine.base import BaseCassEngTestCase @@ -70,6 +71,5 @@ def test_datetime_precision(self): obj = DateTimeQueryTestModel.create(user=pk, day=now, data='energy cheese') load = DateTimeQueryTestModel.get(user=pk) - self.assertAlmostEqual(get_total_seconds(now - load.day), 0, 2) + assert get_total_seconds(now - load.day) == pytest.approx(0, abs=1e-2) obj.delete() - diff --git a/tests/integration/long/test_failure_types.py b/tests/integration/long/test_failure_types.py index 7ec9a14c50..7edb2e2790 100644 --- a/tests/integration/long/test_failure_types.py +++ b/tests/integration/long/test_failure_types.py @@ -32,7 +32,6 @@ get_node, start_cluster_wait_for_up, requiresmallclockgranularity, local, CASSANDRA_VERSION, TestCluster) - import unittest log = logging.getLogger(__name__) @@ -386,7 +385,7 @@ def test_async_timeouts(self): total_time = end_time-start_time expected_time = self.cluster.profile_manager.default.request_timeout # check timeout and ensure it's within a reasonable range - self.assertAlmostEqual(expected_time, total_time, delta=.05) + assert expected_time == pytest.approx(total_time, abs=.05) # Test with user defined timeout (Should be 1) expected_time = 1 @@ -402,6 +401,6 @@ def test_async_timeouts(self): end_time = time.time() total_time = end_time-start_time # check timeout and ensure it's within a reasonable range - self.assertAlmostEqual(expected_time, total_time, delta=.05) + assert expected_time == pytest.approx(total_time, abs=.05) assert mock_errorback.called assert not mock_callback.called diff --git a/tests/integration/long/test_large_data.py b/tests/integration/long/test_large_data.py index b8fba38f6e..a79a6da4f5 100644 --- a/tests/integration/long/test_large_data.py +++ b/tests/integration/long/test_large_data.py @@ -28,6 +28,7 @@ from tests.integration.long.utils import create_schema import unittest +import pytest log = logging.getLogger(__name__) @@ -115,7 +116,7 @@ def test_wide_rows(self): # Verify for i, row in enumerate(results): - self.assertAlmostEqual(row['i'], i, delta=3) + assert row['i'] == pytest.approx(i, abs=3) session.cluster.shutdown() diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 07551ef6e2..e89d1290b4 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -1064,7 +1064,7 @@ def test_add_profile_timeout(self): break except AssertionError: end = time.time() - self.assertAlmostEqual(start, end, 1) + assert start == pytest.approx(end, abs=1e-1) else: raise Exception("add_execution_profile didn't timeout after {0} retries".format(max_retry_count)) @@ -1123,7 +1123,7 @@ def test_execute_query_timeout(self): import traceback traceback.print_exc() end = time.time() - self.assertAlmostEqual(start, end, 1) + assert start == pytest.approx(end, abs=1e-1) else: raise Exception("session.execute didn't time out in {0} tries".format(max_retry_count)) diff --git a/tests/integration/standard/test_connection.py b/tests/integration/standard/test_connection.py index cc686a04bf..9558aeaeb0 100644 --- a/tests/integration/standard/test_connection.py +++ b/tests/integration/standard/test_connection.py @@ -396,7 +396,7 @@ def test_connect_timeout(self): conn.close() except Exception as e: end = time.time() - self.assertAlmostEqual(start, end, 1) + assert start == pytest.approx(end, abs=1e-1) exception_thrown = True break assert exception_thrown diff --git a/tests/integration/standard/test_types.py b/tests/integration/standard/test_types.py index 4f206dbc01..a8cd5f0066 100644 --- a/tests/integration/standard/test_types.py +++ b/tests/integration/standard/test_types.py @@ -36,6 +36,7 @@ from cassandra.query import dict_factory, ordered_dict_factory from cassandra.util import sortedset, Duration, OrderedMap from tests.unit.cython.utils import cythontest +from tests.util import assertEqual from tests.integration import use_singledc, execute_until_pass, notprotocolv1, \ BasicSharedKeyspaceUnitTestCase, greaterthancass21, lessthancass30, \ @@ -1043,7 +1044,7 @@ def test_round_trip_integers(self): self._round_trip_test("varint", partial(random.randint, 0, 2 ** 63), self.assertEqual) def test_round_trip_floating_point(self): - _almost_equal_test_fn = partial(self.assertAlmostEqual, places=5) + _almost_equal_test_fn = partial(pytest.approx, abs=1e-5) def _random_decimal(): return Decimal(random.uniform(0.0, 100.0)) @@ -1061,7 +1062,7 @@ def _random_string(): self._round_trip_test("text", _random_string, self.assertEqual) def test_round_trip_date_and_time(self): - _almost_equal_test_fn = partial(self.assertAlmostEqual, delta=timedelta(seconds=1)) + _almost_equal_test_fn = partial(pytest.approx, abs=timedelta(seconds=1)) def _random_datetime(): return datetime.today() - timedelta(hours=random.randint(0,18), days=random.randint(1,1000)) def _random_date(): diff --git a/tests/unit/io/utils.py b/tests/unit/io/utils.py index a2ca133b67..174137225a 100644 --- a/tests/unit/io/utils.py +++ b/tests/unit/io/utils.py @@ -36,6 +36,7 @@ from socket import error as socket_error import ssl import time +import pytest log = logging.getLogger(__name__) @@ -128,7 +129,7 @@ def submit_and_wait_for_completion(unit_test, create_timer, start, end, incremen # ensure they are all called back in a timely fashion for callback in completed_callbacks: - unit_test.assertAlmostEqual(callback.expected_wait, callback.get_wait_time(), delta=.15) + assert callback.expected_wait == pytest.approx(callback.get_wait_time(), abs=.15) def noop_if_monkey_patched(f): diff --git a/tests/unit/test_time_util.py b/tests/unit/test_time_util.py index 4e094c3bf0..29fb811b75 100644 --- a/tests/unit/test_time_util.py +++ b/tests/unit/test_time_util.py @@ -41,11 +41,11 @@ def test_times_from_uuid1(self): u = uuid.uuid1(node, 0) t = util.unix_time_from_uuid1(u) - self.assertAlmostEqual(now, t, 2) + assert now == pytest.approx(t, abs=1e-2) dt = util.datetime_from_uuid1(u) t = calendar.timegm(dt.timetuple()) + dt.microsecond / 1e6 - self.assertAlmostEqual(now, t, 2) + assert now == pytest.approx(t, abs=1e-2) def test_uuid_from_time(self): t = time.time() @@ -54,15 +54,15 @@ def test_uuid_from_time(self): u = util.uuid_from_time(t, node, seq) # using AlmostEqual because time precision is different for # some platforms - self.assertAlmostEqual(util.unix_time_from_uuid1(u), t, 4) + assert util.unix_time_from_uuid1(u) == pytest.approx(t, abs=1e-4) assert u.node == node assert u.clock_seq == seq # random node u1 = util.uuid_from_time(t, clock_seq=seq) u2 = util.uuid_from_time(t, clock_seq=seq) - self.assertAlmostEqual(util.unix_time_from_uuid1(u1), t, 4) - self.assertAlmostEqual(util.unix_time_from_uuid1(u2), t, 4) + assert util.unix_time_from_uuid1(u1) == pytest.approx(t, abs=1e-4) + assert util.unix_time_from_uuid1(u2) == pytest.approx(t, abs=1e-4) assert u.clock_seq == seq # not impossible, but we shouldn't get the same value twice assert u1.node != u2.node @@ -70,8 +70,8 @@ def test_uuid_from_time(self): # random seq u1 = util.uuid_from_time(t, node=node) u2 = util.uuid_from_time(t, node=node) - self.assertAlmostEqual(util.unix_time_from_uuid1(u1), t, 4) - self.assertAlmostEqual(util.unix_time_from_uuid1(u2), t, 4) + assert util.unix_time_from_uuid1(u1) == pytest.approx(t, abs=1e-4) + assert util.unix_time_from_uuid1(u2) == pytest.approx(t, abs=1e-4) assert u.node == node # not impossible, but we shouldn't get the same value twice assert u1.clock_seq != u2.clock_seq @@ -87,7 +87,7 @@ def test_uuid_from_time(self): # construct from datetime dt = util.datetime_from_timestamp(t) u = util.uuid_from_time(dt, node, seq) - self.assertAlmostEqual(util.unix_time_from_uuid1(u), t, 4) + assert util.unix_time_from_uuid1(u) == pytest.approx(t, abs=1e-4) assert u.node == node assert u.clock_seq == seq diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index 9c9ade2a7b..7f32e14a90 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -45,6 +45,7 @@ datetime_from_timestamp ) from tests.unit.util import check_sequence_consistency +import pytest class TypeTests(unittest.TestCase): @@ -319,7 +320,7 @@ def _normalize_set(self, val): def _round_trip_compare_fn(self, first, second): if isinstance(first, float): - self.assertAlmostEqual(first, second, places=5) + assert first == pytest.approx(second, rel=1e-5) elif isinstance(first, list): assert len(first) == len(second) for (felem, selem) in zip(first, second): diff --git a/tests/util.py b/tests/util.py index 998ff3609e..14fe486782 100644 --- a/tests/util.py +++ b/tests/util.py @@ -94,3 +94,6 @@ def assertSetEqual(a, b): def assertCountEqual(a, b): unittest_test_case.assertCountEqual(a, b) + +def assertEqual(a, b): + assert a == b From 3841a331a3d90a96dcc83560e89cb13b76e76101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 14:04:43 +0200 Subject: [PATCH 061/298] verify_iterator_data: Use plain assert This method took a assertEqual callback, but it was always self.assertEqual. This commit removes this unnecessary indirection by replacing the callback with plain assert. --- .../standard/test_cython_protocol_handlers.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index 05ceb8adc1..9ec16cecc7 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -47,14 +47,14 @@ def test_cython_parser(self): """ Test Cython-based parser that returns a list of tuples """ - verify_iterator_data(self.assertEqual, get_data(ProtocolHandler)) + verify_iterator_data(get_data(ProtocolHandler)) @cythontest def test_cython_lazy_parser(self): """ Test Cython-based parser that returns an iterator of tuples """ - verify_iterator_data(self.assertEqual, get_data(LazyProtocolHandler)) + verify_iterator_data(get_data(LazyProtocolHandler)) @numpytest def test_cython_lazy_results_paged(self): @@ -74,7 +74,7 @@ def test_cython_lazy_results_paged(self): results = session.execute("SELECT * FROM test_table") assert results.has_more_pages - assert verify_iterator_data(self.assertEqual, results) == self.N_ITEMS # make sure we see all rows + assert verify_iterator_data(results) == self.N_ITEMS # make sure we see all rows cluster.shutdown() @@ -146,7 +146,7 @@ def _verify_numpy_page(self, page): arr = page[colname] self.match_dtype(datatype, arr.dtype) - return verify_iterator_data(self.assertEqual, arrays_to_list_of_tuples(page, colnames)) + return verify_iterator_data(arrays_to_list_of_tuples(page, colnames)) def match_dtype(self, datatype, dtype): """Match a string cqltype (e.g. 'int' or 'blob') with a numpy dtype""" @@ -192,7 +192,7 @@ def get_data(protocol_handler): return results -def verify_iterator_data(assertEqual, results): +def verify_iterator_data(results): """ Check the result of get_data() when this is a list or iterator of tuples @@ -200,10 +200,9 @@ def verify_iterator_data(assertEqual, results): count = 0 for count, result in enumerate(results, 1): params = get_all_primitive_params(result[0]) - assertEqual(len(params), len(result), - msg="Not the right number of columns?") + assert len(params) == len(result), "Not the right number of columns?" for expected, actual in zip(params, result): - assertEqual(actual, expected) + assert actual == expected return count From 5fcce2b04d1a94631422a8adce90be0cce65743a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 14:12:50 +0200 Subject: [PATCH 062/298] test_types: Use new assertEqual for callbacks --- tests/integration/standard/test_types.py | 58 ++++++++++++------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/integration/standard/test_types.py b/tests/integration/standard/test_types.py index a8cd5f0066..594ce37199 100644 --- a/tests/integration/standard/test_types.py +++ b/tests/integration/standard/test_types.py @@ -1037,11 +1037,11 @@ def random_subtype_vector(): test_fn(observed2[idx], expected2[idx]) def test_round_trip_integers(self): - self._round_trip_test("int", partial(random.randint, 0, 2 ** 31), self.assertEqual) - self._round_trip_test("bigint", partial(random.randint, 0, 2 ** 63), self.assertEqual) - self._round_trip_test("smallint", partial(random.randint, 0, 2 ** 15), self.assertEqual) - self._round_trip_test("tinyint", partial(random.randint, 0, (2 ** 7) - 1), self.assertEqual) - self._round_trip_test("varint", partial(random.randint, 0, 2 ** 63), self.assertEqual) + self._round_trip_test("int", partial(random.randint, 0, 2 ** 31), assertEqual) + self._round_trip_test("bigint", partial(random.randint, 0, 2 ** 63), assertEqual) + self._round_trip_test("smallint", partial(random.randint, 0, 2 ** 15), assertEqual) + self._round_trip_test("tinyint", partial(random.randint, 0, (2 ** 7) - 1), assertEqual) + self._round_trip_test("varint", partial(random.randint, 0, 2 ** 63), assertEqual) def test_round_trip_floating_point(self): _almost_equal_test_fn = partial(pytest.approx, abs=1e-5) @@ -1058,8 +1058,8 @@ def test_round_trip_text(self): def _random_string(): return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(24)) - self._round_trip_test("ascii", _random_string, self.assertEqual) - self._round_trip_test("text", _random_string, self.assertEqual) + self._round_trip_test("ascii", _random_string, assertEqual) + self._round_trip_test("text", _random_string, assertEqual) def test_round_trip_date_and_time(self): _almost_equal_test_fn = partial(pytest.approx, abs=timedelta(seconds=1)) @@ -1070,13 +1070,13 @@ def _random_date(): def _random_time(): return _random_datetime().time() - self._round_trip_test("date", _random_date, self.assertEqual) - self._round_trip_test("time", _random_time, self.assertEqual) + self._round_trip_test("date", _random_date, assertEqual) + self._round_trip_test("time", _random_time, assertEqual) self._round_trip_test("timestamp", _random_datetime, _almost_equal_test_fn) def test_round_trip_uuid(self): - self._round_trip_test("uuid", uuid.uuid1, self.assertEqual) - self._round_trip_test("timeuuid", uuid.uuid1, self.assertEqual) + self._round_trip_test("uuid", uuid.uuid1, assertEqual) + self._round_trip_test("timeuuid", uuid.uuid1, assertEqual) def test_round_trip_miscellany(self): def _random_bytes(): @@ -1088,10 +1088,10 @@ def _random_duration(): def _random_inet(): return socket.inet_ntoa(_random_bytes()) - self._round_trip_test("boolean", _random_boolean, self.assertEqual) - self._round_trip_test("duration", _random_duration, self.assertEqual) - self._round_trip_test("inet", _random_inet, self.assertEqual) - self._round_trip_test("blob", _random_bytes, self.assertEqual) + self._round_trip_test("boolean", _random_boolean, assertEqual) + self._round_trip_test("duration", _random_duration, assertEqual) + self._round_trip_test("inet", _random_inet, assertEqual) + self._round_trip_test("blob", _random_bytes, assertEqual) def test_round_trip_collections(self): def _random_seq(): @@ -1102,21 +1102,21 @@ def _random_map(): return {k:v for (k,v) in zip(_random_seq(), _random_seq())} # Goal here is to test collections of both fixed and variable size subtypes - self._round_trip_test("list", _random_seq, self.assertEqual) - self._round_trip_test("list", _random_seq, self.assertEqual) - self._round_trip_test("set", _random_set, self.assertEqual) - self._round_trip_test("set", _random_set, self.assertEqual) - self._round_trip_test("map", _random_map, self.assertEqual) - self._round_trip_test("map", _random_map, self.assertEqual) - self._round_trip_test("map", _random_map, self.assertEqual) - self._round_trip_test("map", _random_map, self.assertEqual) + self._round_trip_test("list", _random_seq, assertEqual) + self._round_trip_test("list", _random_seq, assertEqual) + self._round_trip_test("set", _random_set, assertEqual) + self._round_trip_test("set", _random_set, assertEqual) + self._round_trip_test("map", _random_map, assertEqual) + self._round_trip_test("map", _random_map, assertEqual) + self._round_trip_test("map", _random_map, assertEqual) + self._round_trip_test("map", _random_map, assertEqual) def test_round_trip_vector_of_vectors(self): def _random_vector(): return [random.randint(0,100000) for _ in range(2)] - self._round_trip_test("vector", _random_vector, self.assertEqual) - self._round_trip_test("vector", _random_vector, self.assertEqual) + self._round_trip_test("vector", _random_vector, assertEqual) + self._round_trip_test("vector", _random_vector, assertEqual) def test_round_trip_tuples(self): def _random_tuple(): @@ -1124,10 +1124,10 @@ def _random_tuple(): # Unfortunately we can't use positional parameters when inserting tuples because the driver will try to encode # them as lists before sending them to the server... and that confuses the parsing logic. - self._round_trip_test("tuple", _random_tuple, self.assertEqual, use_positional_parameters=False) - self._round_trip_test("tuple", _random_tuple, self.assertEqual, use_positional_parameters=False) - self._round_trip_test("tuple", _random_tuple, self.assertEqual, use_positional_parameters=False) - self._round_trip_test("tuple", _random_tuple, self.assertEqual, use_positional_parameters=False) + self._round_trip_test("tuple", _random_tuple, assertEqual, use_positional_parameters=False) + self._round_trip_test("tuple", _random_tuple, assertEqual, use_positional_parameters=False) + self._round_trip_test("tuple", _random_tuple, assertEqual, use_positional_parameters=False) + self._round_trip_test("tuple", _random_tuple, assertEqual, use_positional_parameters=False) def test_round_trip_udts(self): def _udt_equal_test_fn(udt1, udt2): From 6fa002d453feeed07fb483d4d4fa5e8923d8132d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 15:20:36 +0200 Subject: [PATCH 063/298] assert_startswith_diff: Move to utils I decided to not replace it with simple assert x.startswith(y) because the diff that the function produces is superior to default error message. --- tests/integration/standard/test_metadata.py | 15 +++------------ tests/util.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index c9d6da0499..879776050d 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -45,7 +45,7 @@ requires_collection_indexes, SCYLLA_VERSION, xfail_scylla, xfail_scylla_version_lt, requirescompactstorage) -from tests.util import wait_until, assertRegex, assertDictEqual, assertListEqual +from tests.util import wait_until, assertRegex, assertDictEqual, assertListEqual, assert_startswith_diff log = logging.getLogger(__name__) @@ -1125,15 +1125,6 @@ def assert_equal_diff(self, received, expected): lineterm='')) self.fail(diff_string) - def assert_startswith_diff(self, received, prefix): - if not received.startswith(prefix): - prefix_lines = prefix.split('\n') - diff_string = '\n'.join(difflib.unified_diff(prefix_lines, - received.split('\n')[:len(prefix_lines)], - 'EXPECTED', 'RECEIVED', - lineterm='')) - self.fail(diff_string) - @greaterthancass20 def test_export_keyspace_schema_udts(self): """ @@ -1198,7 +1189,7 @@ def test_export_keyspace_schema_udts(self): user text PRIMARY KEY, addresses map>""" - self.assert_startswith_diff(cluster.metadata.keyspaces['export_udts'].export_as_string(), expected_prefix) + assert_startswith_diff(cluster.metadata.keyspaces['export_udts'].export_as_string(), expected_prefix) table_meta = cluster.metadata.keyspaces['export_udts'].tables['users'] @@ -1206,7 +1197,7 @@ def test_export_keyspace_schema_udts(self): user text PRIMARY KEY, addresses map>""" - self.assert_startswith_diff(table_meta.export_as_string(), expected_prefix) + assert_startswith_diff(table_meta.export_as_string(), expected_prefix) cluster.shutdown() diff --git a/tests/util.py b/tests/util.py index 14fe486782..26a2bfdc50 100644 --- a/tests/util.py +++ b/tests/util.py @@ -16,6 +16,8 @@ from functools import wraps import re import unittest +import difflib +import pytest def wait_until(condition, delay, max_attempts): @@ -97,3 +99,12 @@ def assertCountEqual(a, b): def assertEqual(a, b): assert a == b + +def assert_startswith_diff(text, prefix): + if not text.startswith(prefix): + prefix_lines = prefix.split('\n') + diff_string = '\n'.join(difflib.unified_diff(prefix_lines, + text.split('\n')[:len(prefix_lines)], + 'EXPECTED', 'RECEIVED', + lineterm='')) + pytest.fail(diff_string) From d3cfd9f8edfaa010759d3817d198ad88c0cb6837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 15:22:06 +0200 Subject: [PATCH 064/298] Replace commented out unittest asserts with plain asserts --- tests/integration/standard/test_query.py | 2 +- tests/unit/advanced/test_geometry.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index fd1ce9cd65..2b064e1027 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -973,7 +973,7 @@ def test_was_applied_batch_stmt(self): "INSERT INTO test3rf.lwt_clustering (k, c, v) VALUES (0, 1, 10);", "INSERT INTO test3rf.lwt_clustering (k, c, v) VALUES (0, 2, 10);"], [None] * 3) result = self.session.execute(batch_statement) - #self.assertTrue(result.was_applied) + #assert result.was_applied # Should fail since (0, 0, 10) have already been written # The non conditional insert shouldn't be written as well diff --git a/tests/unit/advanced/test_geometry.py b/tests/unit/advanced/test_geometry.py index 3a207528da..a6c69b7157 100644 --- a/tests/unit/advanced/test_geometry.py +++ b/tests/unit/advanced/test_geometry.py @@ -230,7 +230,7 @@ def test_polygon_parse(self): long_poly_obj = Polygon.from_wkt(long_poly_string) assert len(long_poly_obj.exterior.coords) == 10000 #for expected, recieved in zip(self._construct_line_string_expected_cords(10000), long_poly_obj.exterior.coords): - # self.assertEqual(expected, recieved) + # assert expected == recieved assert long_poly_obj.exterior.coords == self._construct_line_string_expected_cords(10000) # Test bad polygon strings From 83671d8e4ee7f577ec3b69a0ef8afb4bb8d87d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 15:31:24 +0200 Subject: [PATCH 065/298] bytesio_testhelper: Use plain asserts for equality testing --- tests/unit/cython/bytesio_testhelper.pyx | 16 ++++++++-------- tests/unit/cython/test_bytesio.py | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/unit/cython/bytesio_testhelper.pyx b/tests/unit/cython/bytesio_testhelper.pyx index 7ba91bc4c0..eb67493e75 100644 --- a/tests/unit/cython/bytesio_testhelper.pyx +++ b/tests/unit/cython/bytesio_testhelper.pyx @@ -14,23 +14,23 @@ from cassandra.bytesio cimport BytesIOReader -def test_read1(assert_equal, assert_raises): +def test_read1(assert_raises): cdef BytesIOReader reader = BytesIOReader(b'abcdef') - assert_equal(reader.read(2)[:2], b'ab') - assert_equal(reader.read(2)[:2], b'cd') - assert_equal(reader.read(0)[:0], b'') - assert_equal(reader.read(2)[:2], b'ef') + assert reader.read(2)[:2] == b'ab' + assert reader.read(2)[:2] == b'cd' + assert reader.read(0)[:0] == b'' + assert reader.read(2)[:2] == b'ef' -def test_read2(assert_equal, assert_raises): +def test_read2(assert_raises): cdef BytesIOReader reader = BytesIOReader(b'abcdef') reader.read(5) reader.read(1) -def test_read3(assert_equal, assert_raises): +def test_read3(assert_raises): cdef BytesIOReader reader = BytesIOReader(b'abcdef') reader.read(6) -def test_read_eof(assert_equal, assert_raises): +def test_read_eof(assert_raises): cdef BytesIOReader reader = BytesIOReader(b'abcdef') reader.read(5) # cannot convert reader.read to an object, do it manually diff --git a/tests/unit/cython/test_bytesio.py b/tests/unit/cython/test_bytesio.py index cd4ea86f52..d3d4f3f8ef 100644 --- a/tests/unit/cython/test_bytesio.py +++ b/tests/unit/cython/test_bytesio.py @@ -23,10 +23,10 @@ class BytesIOTest(unittest.TestCase): @cythontest def test_reading(self): - bytesio_testhelper.test_read1(self.assertEqual, self.assertRaises) - bytesio_testhelper.test_read2(self.assertEqual, self.assertRaises) - bytesio_testhelper.test_read3(self.assertEqual, self.assertRaises) + bytesio_testhelper.test_read1(self.assertRaises) + bytesio_testhelper.test_read2(self.assertRaises) + bytesio_testhelper.test_read3(self.assertRaises) @cythontest def test_reading_error(self): - bytesio_testhelper.test_read_eof(self.assertEqual, self.assertRaises) + bytesio_testhelper.test_read_eof(self.assertRaises) From 39b922574c8e6013eddeca93199956a3fcedc66c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 15:33:42 +0200 Subject: [PATCH 066/298] bytesio_testhelper: Remove assert_raises argument --- tests/unit/cython/bytesio_testhelper.pyx | 17 ++++++----------- tests/unit/cython/test_bytesio.py | 8 ++++---- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/tests/unit/cython/bytesio_testhelper.pyx b/tests/unit/cython/bytesio_testhelper.pyx index eb67493e75..595cd29cc8 100644 --- a/tests/unit/cython/bytesio_testhelper.pyx +++ b/tests/unit/cython/bytesio_testhelper.pyx @@ -13,32 +13,27 @@ # limitations under the License. from cassandra.bytesio cimport BytesIOReader +import pytest -def test_read1(assert_raises): +def test_read1(): cdef BytesIOReader reader = BytesIOReader(b'abcdef') assert reader.read(2)[:2] == b'ab' assert reader.read(2)[:2] == b'cd' assert reader.read(0)[:0] == b'' assert reader.read(2)[:2] == b'ef' -def test_read2(assert_raises): +def test_read2(): cdef BytesIOReader reader = BytesIOReader(b'abcdef') reader.read(5) reader.read(1) -def test_read3(assert_raises): +def test_read3(): cdef BytesIOReader reader = BytesIOReader(b'abcdef') reader.read(6) -def test_read_eof(assert_raises): +def test_read_eof(): cdef BytesIOReader reader = BytesIOReader(b'abcdef') reader.read(5) - # cannot convert reader.read to an object, do it manually - # assert_raises(EOFError, reader.read, 2) - try: + with pytest.raises(EOFError): reader.read(2) - except EOFError: - pass - else: - raise Exception("Expected an EOFError") reader.read(1) # see that we can still read this diff --git a/tests/unit/cython/test_bytesio.py b/tests/unit/cython/test_bytesio.py index d3d4f3f8ef..0f27663391 100644 --- a/tests/unit/cython/test_bytesio.py +++ b/tests/unit/cython/test_bytesio.py @@ -23,10 +23,10 @@ class BytesIOTest(unittest.TestCase): @cythontest def test_reading(self): - bytesio_testhelper.test_read1(self.assertRaises) - bytesio_testhelper.test_read2(self.assertRaises) - bytesio_testhelper.test_read3(self.assertRaises) + bytesio_testhelper.test_read1() + bytesio_testhelper.test_read2() + bytesio_testhelper.test_read3() @cythontest def test_reading_error(self): - bytesio_testhelper.test_read_eof(self.assertRaises) + bytesio_testhelper.test_read_eof() From 4452fa4a2b197dec0ed4a87baf9a23358a454ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 15:35:58 +0200 Subject: [PATCH 067/298] cython/test_datetime_from_timestamp: Use plain assert --- tests/unit/cython/test_utils.py | 2 +- tests/unit/cython/utils_testhelper.pyx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/cython/test_utils.py b/tests/unit/cython/test_utils.py index 0e79c235d8..02e4f0590f 100644 --- a/tests/unit/cython/test_utils.py +++ b/tests/unit/cython/test_utils.py @@ -23,4 +23,4 @@ class UtilsTest(unittest.TestCase): @cythontest def test_datetime_from_timestamp(self): - utils_testhelper.test_datetime_from_timestamp(self.assertEqual) + utils_testhelper.test_datetime_from_timestamp() diff --git a/tests/unit/cython/utils_testhelper.pyx b/tests/unit/cython/utils_testhelper.pyx index fe67691aa8..e997caa89e 100644 --- a/tests/unit/cython/utils_testhelper.pyx +++ b/tests/unit/cython/utils_testhelper.pyx @@ -17,7 +17,7 @@ import datetime from cassandra.cython_utils cimport datetime_from_timestamp -def test_datetime_from_timestamp(assert_equal): - assert_equal(datetime_from_timestamp(1454781157.123456), datetime.datetime(2016, 2, 6, 17, 52, 37, 123456)) +def test_datetime_from_timestamp(): + assert datetime_from_timestamp(1454781157.123456) == datetime.datetime(2016, 2, 6, 17, 52, 37, 123456) # PYTHON-452 - assert_equal(datetime_from_timestamp(2177403010.123456), datetime.datetime(2038, 12, 31, 10, 10, 10, 123456)) + assert datetime_from_timestamp(2177403010.123456) == datetime.datetime(2038, 12, 31, 10, 10, 10, 123456) From c79be1f0e0e2fd6a997630433375bb8e5704f88d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 15:37:54 +0200 Subject: [PATCH 068/298] cython/test_types: Use plain asserts --- tests/unit/cython/test_types.py | 4 ++-- tests/unit/cython/types_testhelper.pyx | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/unit/cython/test_types.py b/tests/unit/cython/test_types.py index 545b82fc11..996be266c0 100644 --- a/tests/unit/cython/test_types.py +++ b/tests/unit/cython/test_types.py @@ -22,8 +22,8 @@ class TypesTest(unittest.TestCase): @cythontest def test_datetype(self): - types_testhelper.test_datetype(self.assertEqual) + types_testhelper.test_datetype() @cythontest def test_date_side_by_side(self): - types_testhelper.test_date_side_by_side(self.assertEqual) + types_testhelper.test_date_side_by_side() diff --git a/tests/unit/cython/types_testhelper.pyx b/tests/unit/cython/types_testhelper.pyx index 66d2516319..81f9dca114 100644 --- a/tests/unit/cython/types_testhelper.pyx +++ b/tests/unit/cython/types_testhelper.pyx @@ -28,7 +28,7 @@ from cassandra.buffer cimport Buffer from cassandra.deserializers cimport from_binary, Deserializer -def test_datetype(assert_equal): +def test_datetype(): cdef Deserializer des = find_deserializer(DateType) @@ -52,27 +52,27 @@ def test_datetype(assert_equal): # deserialize # epoc expected = 0 - assert_equal(deserialize(expected), datetime.datetime.fromtimestamp(expected, tz=datetime.timezone.utc).replace(tzinfo=None)) + assert deserialize(expected) == datetime.datetime.fromtimestamp(expected, tz=datetime.timezone.utc).replace(tzinfo=None) # beyond 32b expected = 2 ** 33 - assert_equal(deserialize(expected), datetime.datetime(2242, 3, 16, 12, 56, 32)) + assert deserialize(expected) == datetime.datetime(2242, 3, 16, 12, 56, 32) # less than epoc (PYTHON-119) expected = -770172256 - assert_equal(deserialize(expected), datetime.datetime(1945, 8, 5, 23, 15, 44)) + assert deserialize(expected) == datetime.datetime(1945, 8, 5, 23, 15, 44) # work around rounding difference among Python versions (PYTHON-230) # This wont pass with the cython extension until we fix the microseconds alignment with CPython #expected = 1424817268.274 - #assert_equal(deserialize(expected), datetime.datetime(2015, 2, 24, 22, 34, 28, 274000)) + #assert deserialize(expected) == datetime.datetime(2015, 2, 24, 22, 34, 28, 274000) # Large date overflow (PYTHON-452) expected = 2177403010.123 - assert_equal(deserialize(expected), datetime.datetime(2038, 12, 31, 10, 10, 10, 123000)) + assert deserialize(expected) == datetime.datetime(2038, 12, 31, 10, 10, 10, 123000) -def test_date_side_by_side(assert_equal): +def test_date_side_by_side(): # Test pure python and cython date deserialization side-by-side # This is meant to detect inconsistent rounding or conversion (PYTHON-480 for example) # The test covers the full range of time deserializable in Python. It bounds through @@ -91,7 +91,7 @@ def test_date_side_by_side(assert_equal): buf.size = bior.size cython_deserialized = from_binary(cython_deserializer, &buf, 0) python_deserialized = DateType.deserialize(blob, 0) - assert_equal(cython_deserialized, python_deserialized) + assert cython_deserialized == python_deserialized # min -> 0 x = int(calendar.timegm(datetime.datetime(1, 1, 1).utctimetuple()) * 1000) From c2cb8a59b7ad756a8978f4e24ea6adfa94757ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 15:44:00 +0200 Subject: [PATCH 069/298] test_response_future: Use util asserts --- tests/unit/test_response_future.py | 13 +++++++------ tests/util.py | 3 +++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/unit/test_response_future.py b/tests/unit/test_response_future.py index c1c8dc41ac..0aa8f72c95 100644 --- a/tests/unit/test_response_future.py +++ b/tests/unit/test_response_future.py @@ -31,6 +31,7 @@ from cassandra.policies import RetryPolicy, ExponentialBackoffRetryPolicy from cassandra.pool import NoConnectionsAvailable from cassandra.query import SimpleStatement +from tests.util import assertEqual, assertIsInstance class ResponseFutureTests(unittest.TestCase): @@ -442,7 +443,7 @@ def test_callback(self): callback.assert_called_once_with([expected_result], arg, **kwargs) # this should get called immediately now that the result is set - rf.add_callback(self.assertEqual, [expected_result]) + rf.add_callback(assertEqual, [expected_result]) def test_errback(self): session = self.make_session() @@ -457,7 +458,7 @@ def test_errback(self): rf._query_retries = 1 rf.send_request() - rf.add_errback(self.assertIsInstance, Exception) + rf.add_errback(assertIsInstance, Exception) result = Mock(spec=UnavailableErrorMessage, info={"required_replicas":2, "alive_replicas": 1, "consistency": 1}) result.to_exception.return_value = Exception() @@ -466,7 +467,7 @@ def test_errback(self): self.assertRaises(Exception, rf.result) # this should get called immediately now that the error is set - rf.add_errback(self.assertIsInstance, Exception) + rf.add_errback(assertIsInstance, Exception) def test_multiple_callbacks(self): session = self.make_session() @@ -537,8 +538,8 @@ def test_add_callbacks(self): rf.send_request() rf.add_callbacks( - callback=self.assertEqual, callback_args=([{'col': 'val'}],), - errback=self.assertIsInstance, errback_args=(Exception,)) + callback=assertEqual, callback_args=([{'col': 'val'}],), + errback=assertIsInstance, errback_args=(Exception,)) result = Mock(spec=UnavailableErrorMessage, info={"required_replicas":2, "alive_replicas": 1, "consistency": 1}) @@ -556,7 +557,7 @@ def test_add_callbacks(self): kwargs = {'one': 1, 'two': 2} rf.add_callbacks( callback=callback, callback_args=(arg,), callback_kwargs=kwargs, - errback=self.assertIsInstance, errback_args=(Exception,)) + errback=assertIsInstance, errback_args=(Exception,)) rf._set_result(None, None, None, self.make_mock_response(expected_result[0], expected_result[1])) assert rf.result()[0] == expected_result diff --git a/tests/util.py b/tests/util.py index 26a2bfdc50..2439e20fd5 100644 --- a/tests/util.py +++ b/tests/util.py @@ -108,3 +108,6 @@ def assert_startswith_diff(text, prefix): 'EXPECTED', 'RECEIVED', lineterm='')) pytest.fail(diff_string) + +def assertIsInstance(a, b): + assert isinstance(a, b) From c848cebab7105b5b1c468521f08dd336792c537a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 15:45:45 +0200 Subject: [PATCH 070/298] Replace assert_startswith with plain assert --- tests/integration/__init__.py | 7 ------- tests/integration/standard/test_metadata.py | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index 96aa8fdbee..e01029a7cd 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -944,13 +944,6 @@ def tearDown(self): self.cluster.shutdown() -def assert_startswith(s, prefix): - if not s.startswith(prefix): - raise AssertionError( - '{} does not start with {}'.format(repr(s), repr(prefix)) - ) - - class TestCluster(object): __test__ = False diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 879776050d..ba50dee65b 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -39,7 +39,7 @@ BasicExistingKeyspaceUnitTestCase, drop_keyspace_shutdown_cluster, CASSANDRA_VERSION, greaterthanorequalcass30, lessthancass30, local, get_supported_protocol_versions, greaterthancass20, - greaterthancass21, assert_startswith, greaterthanorequalcass40, + greaterthancass21, greaterthanorequalcass40, lessthancass40, TestCluster, requires_java_udf, requires_composite_type, requires_collection_indexes, SCYLLA_VERSION, xfail_scylla, xfail_scylla_version_lt, @@ -105,7 +105,7 @@ def test_host_release_version(self): @test_category metadata """ for host in self.cluster.metadata.all_hosts(): - assert_startswith(host.release_version, CASSANDRA_VERSION.base_version) + assert host.release_version.startswith(CASSANDRA_VERSION.base_version) From ae16aa114577e5d0e20deaaf6954c5c04b44890d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 15:45:57 +0200 Subject: [PATCH 071/298] Remove unused assertion functions --- tests/integration/cqlengine/base.py | 10 ---------- tests/integration/standard/test_metadata.py | 8 -------- tests/unit/test_metadata.py | 6 ------ 3 files changed, 24 deletions(-) diff --git a/tests/integration/cqlengine/base.py b/tests/integration/cqlengine/base.py index ee1d79ff8c..c65554b974 100644 --- a/tests/integration/cqlengine/base.py +++ b/tests/integration/cqlengine/base.py @@ -40,13 +40,3 @@ class BaseCassEngTestCase(unittest.TestCase): def setUp(self): self.session = get_session() - - def assertHasAttr(self, obj, attr): - assert hasattr(obj, attr), "{0} doesn't have attribute: {1}".format(obj, attr) - - def assertNotHasAttr(self, obj, attr): - assert not hasattr(obj, attr), "{0} shouldn't have the attribute: {1}".format(obj, attr) - - if sys.version_info > (3, 0): - def assertItemsEqual(self, first, second, msg=None): - return self.assertCountEqual(first, second, msg) diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index ba50dee65b..10def0d50f 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -1117,14 +1117,6 @@ def test_export_keyspace_schema(self): assert isinstance(keyspace_metadata.as_cql_query(), str) cluster.shutdown() - def assert_equal_diff(self, received, expected): - if received != expected: - diff_string = '\n'.join(difflib.unified_diff(expected.split('\n'), - received.split('\n'), - 'EXPECTED', 'RECEIVED', - lineterm='')) - self.fail(diff_string) - @greaterthancass20 def test_export_keyspace_schema_udts(self): """ diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 05e2e50d42..72874d6406 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -69,12 +69,6 @@ def test_replication_factor_equality(self): class StrategiesTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - "Hook method for setting up class fixture before running tests in the class." - if not hasattr(cls, 'assertItemsEqual'): - cls.assertItemsEqual = cls.assertCountEqual - def test_replication_strategy(self): """ Basic code coverage testing that ensures different ReplicationStrategies From ba53f591d6df9dfc493508a4ee8ff2be8d2dc3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 15:49:19 +0200 Subject: [PATCH 072/298] assert_query_count_equals: Use pytest.fail --- tests/integration/long/test_consistency.py | 46 ++--- .../long/test_loadbalancingpolicies.py | 166 +++++++++--------- tests/integration/long/utils.py | 5 +- 3 files changed, 109 insertions(+), 108 deletions(-) diff --git a/tests/integration/long/test_consistency.py b/tests/integration/long/test_consistency.py index 8f5fcb6313..5cc6b1891a 100644 --- a/tests/integration/long/test_consistency.py +++ b/tests/integration/long/test_consistency.py @@ -101,9 +101,9 @@ def _assert_reads_succeed(self, session, keyspace, consistency_levels, expected_ self._query(session, keyspace, 1, cl) for i in range(3): if i == expected_reader: - self.coordinator_stats.assert_query_count_equals(self, i, 1) + self.coordinator_stats.assert_query_count_equals(i, 1) else: - self.coordinator_stats.assert_query_count_equals(self, i, 0) + self.coordinator_stats.assert_query_count_equals(i, 0) except Exception as e: self._cl_failure(cl, e) @@ -136,9 +136,9 @@ def _test_tokenaware_one_node_down(self, keyspace, rf, accepted): create_schema(cluster, session, keyspace, replication_factor=rf) self._insert(session, keyspace, count=1) self._query(session, keyspace, count=1) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 1) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 1) + self.coordinator_stats.assert_query_count_equals(3, 0) try: force_stop(2) @@ -188,9 +188,9 @@ def test_rfthree_tokenaware_none_down(self): create_schema(cluster, session, keyspace, replication_factor=3) self._insert(session, keyspace, count=1) self._query(session, keyspace, count=1) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 1) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 1) + self.coordinator_stats.assert_query_count_equals(3, 0) self.coordinator_stats.reset_counts() @@ -211,9 +211,9 @@ def _test_downgrading_cl(self, keyspace, rf, accepted): create_schema(cluster, session, keyspace, replication_factor=rf) self._insert(session, keyspace, 1) self._query(session, keyspace, 1) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 1) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 1) + self.coordinator_stats.assert_query_count_equals(3, 0) try: force_stop(2) @@ -268,13 +268,13 @@ def rfthree_downgradingcl(self, cluster, keyspace, roundrobin): self._query(session, keyspace, count=12) if roundrobin: - self.coordinator_stats.assert_query_count_equals(self, 1, 4) - self.coordinator_stats.assert_query_count_equals(self, 2, 4) - self.coordinator_stats.assert_query_count_equals(self, 3, 4) + self.coordinator_stats.assert_query_count_equals(1, 4) + self.coordinator_stats.assert_query_count_equals(2, 4) + self.coordinator_stats.assert_query_count_equals(3, 4) else: - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 12) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 12) + self.coordinator_stats.assert_query_count_equals(3, 0) try: self.coordinator_stats.reset_counts() @@ -288,13 +288,13 @@ def rfthree_downgradingcl(self, cluster, keyspace, roundrobin): self.coordinator_stats.reset_counts() self._query(session, keyspace, 12, consistency_level=cl) if roundrobin: - self.coordinator_stats.assert_query_count_equals(self, 1, 6) - self.coordinator_stats.assert_query_count_equals(self, 2, 0) - self.coordinator_stats.assert_query_count_equals(self, 3, 6) + self.coordinator_stats.assert_query_count_equals(1, 6) + self.coordinator_stats.assert_query_count_equals(2, 0) + self.coordinator_stats.assert_query_count_equals(3, 6) else: - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 0) - self.coordinator_stats.assert_query_count_equals(self, 3, 12) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 0) + self.coordinator_stats.assert_query_count_equals(3, 12) finally: start(2) wait_for_up(cluster, 2) diff --git a/tests/integration/long/test_loadbalancingpolicies.py b/tests/integration/long/test_loadbalancingpolicies.py index 8a85c579cb..cf1f7629da 100644 --- a/tests/integration/long/test_loadbalancingpolicies.py +++ b/tests/integration/long/test_loadbalancingpolicies.py @@ -200,9 +200,9 @@ def test_roundrobin(self): self._insert(session, keyspace) self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 4) - self.coordinator_stats.assert_query_count_equals(self, 2, 4) - self.coordinator_stats.assert_query_count_equals(self, 3, 4) + self.coordinator_stats.assert_query_count_equals(1, 4) + self.coordinator_stats.assert_query_count_equals(2, 4) + self.coordinator_stats.assert_query_count_equals(3, 4) force_stop(3) self._wait_for_nodes_down([3], cluster) @@ -210,9 +210,9 @@ def test_roundrobin(self): self.coordinator_stats.reset_counts() self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 6) - self.coordinator_stats.assert_query_count_equals(self, 2, 6) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) + self.coordinator_stats.assert_query_count_equals(1, 6) + self.coordinator_stats.assert_query_count_equals(2, 6) + self.coordinator_stats.assert_query_count_equals(3, 0) decommission(1) start(3) @@ -222,9 +222,9 @@ def test_roundrobin(self): self.coordinator_stats.reset_counts() self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 6) - self.coordinator_stats.assert_query_count_equals(self, 3, 6) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 6) + self.coordinator_stats.assert_query_count_equals(3, 6) def test_roundrobin_two_dcs(self): use_multidc([2, 2]) @@ -237,10 +237,10 @@ def test_roundrobin_two_dcs(self): self._insert(session, keyspace) self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 3) - self.coordinator_stats.assert_query_count_equals(self, 2, 3) - self.coordinator_stats.assert_query_count_equals(self, 3, 3) - self.coordinator_stats.assert_query_count_equals(self, 4, 3) + self.coordinator_stats.assert_query_count_equals(1, 3) + self.coordinator_stats.assert_query_count_equals(2, 3) + self.coordinator_stats.assert_query_count_equals(3, 3) + self.coordinator_stats.assert_query_count_equals(4, 3) force_stop(1) bootstrap(5, 'dc3') @@ -253,11 +253,11 @@ def test_roundrobin_two_dcs(self): self.coordinator_stats.reset_counts() self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 3) - self.coordinator_stats.assert_query_count_equals(self, 3, 3) - self.coordinator_stats.assert_query_count_equals(self, 4, 3) - self.coordinator_stats.assert_query_count_equals(self, 5, 3) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 3) + self.coordinator_stats.assert_query_count_equals(3, 3) + self.coordinator_stats.assert_query_count_equals(4, 3) + self.coordinator_stats.assert_query_count_equals(5, 3) def test_roundrobin_two_dcs_2(self): use_multidc([2, 2]) @@ -270,10 +270,10 @@ def test_roundrobin_two_dcs_2(self): self._insert(session, keyspace) self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 3) - self.coordinator_stats.assert_query_count_equals(self, 2, 3) - self.coordinator_stats.assert_query_count_equals(self, 3, 3) - self.coordinator_stats.assert_query_count_equals(self, 4, 3) + self.coordinator_stats.assert_query_count_equals(1, 3) + self.coordinator_stats.assert_query_count_equals(2, 3) + self.coordinator_stats.assert_query_count_equals(3, 3) + self.coordinator_stats.assert_query_count_equals(4, 3) force_stop(1) bootstrap(5, 'dc1') @@ -286,11 +286,11 @@ def test_roundrobin_two_dcs_2(self): self.coordinator_stats.reset_counts() self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 3) - self.coordinator_stats.assert_query_count_equals(self, 3, 3) - self.coordinator_stats.assert_query_count_equals(self, 4, 3) - self.coordinator_stats.assert_query_count_equals(self, 5, 3) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 3) + self.coordinator_stats.assert_query_count_equals(3, 3) + self.coordinator_stats.assert_query_count_equals(4, 3) + self.coordinator_stats.assert_query_count_equals(5, 3) def test_dc_aware_roundrobin_two_dcs(self): use_multidc([3, 2]) @@ -303,11 +303,11 @@ def test_dc_aware_roundrobin_two_dcs(self): self._insert(session, keyspace) self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 4) - self.coordinator_stats.assert_query_count_equals(self, 2, 4) - self.coordinator_stats.assert_query_count_equals(self, 3, 4) - self.coordinator_stats.assert_query_count_equals(self, 4, 0) - self.coordinator_stats.assert_query_count_equals(self, 5, 0) + self.coordinator_stats.assert_query_count_equals(1, 4) + self.coordinator_stats.assert_query_count_equals(2, 4) + self.coordinator_stats.assert_query_count_equals(3, 4) + self.coordinator_stats.assert_query_count_equals(4, 0) + self.coordinator_stats.assert_query_count_equals(5, 0) def test_dc_aware_roundrobin_two_dcs_2(self): use_multidc([3, 2]) @@ -320,11 +320,11 @@ def test_dc_aware_roundrobin_two_dcs_2(self): self._insert(session, keyspace) self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 0) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) - self.coordinator_stats.assert_query_count_equals(self, 4, 6) - self.coordinator_stats.assert_query_count_equals(self, 5, 6) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 0) + self.coordinator_stats.assert_query_count_equals(3, 0) + self.coordinator_stats.assert_query_count_equals(4, 6) + self.coordinator_stats.assert_query_count_equals(5, 6) def test_dc_aware_roundrobin_one_remote_host(self): use_multidc([2, 2]) @@ -337,10 +337,10 @@ def test_dc_aware_roundrobin_one_remote_host(self): self._insert(session, keyspace) self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 0) - self.coordinator_stats.assert_query_count_equals(self, 3, 6) - self.coordinator_stats.assert_query_count_equals(self, 4, 6) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 0) + self.coordinator_stats.assert_query_count_equals(3, 6) + self.coordinator_stats.assert_query_count_equals(4, 6) self.coordinator_stats.reset_counts() bootstrap(5, 'dc1') @@ -348,11 +348,11 @@ def test_dc_aware_roundrobin_one_remote_host(self): self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 0) - self.coordinator_stats.assert_query_count_equals(self, 3, 6) - self.coordinator_stats.assert_query_count_equals(self, 4, 6) - self.coordinator_stats.assert_query_count_equals(self, 5, 0) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 0) + self.coordinator_stats.assert_query_count_equals(3, 6) + self.coordinator_stats.assert_query_count_equals(4, 6) + self.coordinator_stats.assert_query_count_equals(5, 0) self.coordinator_stats.reset_counts() decommission(3) @@ -361,8 +361,8 @@ def test_dc_aware_roundrobin_one_remote_host(self): self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) - self.coordinator_stats.assert_query_count_equals(self, 4, 0) + self.coordinator_stats.assert_query_count_equals(3, 0) + self.coordinator_stats.assert_query_count_equals(4, 0) responses = set() for node in [1, 2, 5]: responses.add(self.coordinator_stats.get_query_count(node)) @@ -374,9 +374,9 @@ def test_dc_aware_roundrobin_one_remote_host(self): self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) - self.coordinator_stats.assert_query_count_equals(self, 4, 0) - self.coordinator_stats.assert_query_count_equals(self, 5, 0) + self.coordinator_stats.assert_query_count_equals(3, 0) + self.coordinator_stats.assert_query_count_equals(4, 0) + self.coordinator_stats.assert_query_count_equals(5, 0) responses = set() for node in [1, 2]: responses.add(self.coordinator_stats.get_query_count(node)) @@ -388,11 +388,11 @@ def test_dc_aware_roundrobin_one_remote_host(self): self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 12) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) - self.coordinator_stats.assert_query_count_equals(self, 4, 0) - self.coordinator_stats.assert_query_count_equals(self, 5, 0) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 12) + self.coordinator_stats.assert_query_count_equals(3, 0) + self.coordinator_stats.assert_query_count_equals(4, 0) + self.coordinator_stats.assert_query_count_equals(5, 0) self.coordinator_stats.reset_counts() force_stop(2) @@ -421,16 +421,16 @@ def token_aware(self, keyspace, use_prepared=False): self._insert(session, keyspace) self._query(session, keyspace, use_prepared=use_prepared) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 12) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 12) + self.coordinator_stats.assert_query_count_equals(3, 0) self.coordinator_stats.reset_counts() self._query(session, keyspace, use_prepared=use_prepared) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 12) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 12) + self.coordinator_stats.assert_query_count_equals(3, 0) self.coordinator_stats.reset_counts() force_stop(2) @@ -450,9 +450,9 @@ def token_aware(self, keyspace, use_prepared=False): self._query(session, keyspace, use_prepared=use_prepared) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 12) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 12) + self.coordinator_stats.assert_query_count_equals(3, 0) self.coordinator_stats.reset_counts() stop(2) @@ -477,7 +477,7 @@ def token_aware(self, keyspace, use_prepared=False): self.coordinator_stats.get_query_count(3) ]) assert results == set([0, 12]) - self.coordinator_stats.assert_query_count_equals(self, 2, 0) + self.coordinator_stats.assert_query_count_equals(2, 0) def test_token_aware_composite_key(self): use_singledc() @@ -522,9 +522,9 @@ def test_token_aware_with_rf_2(self, use_prepared=False): self._insert(session, keyspace) self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 12) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 12) + self.coordinator_stats.assert_query_count_equals(3, 0) self.coordinator_stats.reset_counts() stop(2) @@ -532,9 +532,9 @@ def test_token_aware_with_rf_2(self, use_prepared=False): self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 0) - self.coordinator_stats.assert_query_count_equals(self, 3, 12) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 0) + self.coordinator_stats.assert_query_count_equals(3, 12) def test_token_aware_with_local_table(self): use_singledc() @@ -570,9 +570,9 @@ def test_token_aware_with_shuffle_rf2(self): self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 0) - self.coordinator_stats.assert_query_count_equals(self, 3, 12) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 0) + self.coordinator_stats.assert_query_count_equals(3, 12) def test_token_aware_with_shuffle_rf3(self): """ @@ -597,7 +597,7 @@ def test_token_aware_with_shuffle_rf3(self): self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) + self.coordinator_stats.assert_query_count_equals(1, 0) query_count_two = self.coordinator_stats.get_query_count(2) query_count_three = self.coordinator_stats.get_query_count(3) assert query_count_two + query_count_three == 12 @@ -608,9 +608,9 @@ def test_token_aware_with_shuffle_rf3(self): self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 0) - self.coordinator_stats.assert_query_count_equals(self, 3, 12) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 0) + self.coordinator_stats.assert_query_count_equals(3, 12) @greaterthanorequalcass40 def test_token_aware_with_transient_replication(self): @@ -704,9 +704,9 @@ def test_white_list(self): self._insert(session, keyspace) self._query(session, keyspace) - self.coordinator_stats.assert_query_count_equals(self, 1, 0) - self.coordinator_stats.assert_query_count_equals(self, 2, 12) - self.coordinator_stats.assert_query_count_equals(self, 3, 0) + self.coordinator_stats.assert_query_count_equals(1, 0) + self.coordinator_stats.assert_query_count_equals(2, 12) + self.coordinator_stats.assert_query_count_equals(3, 0) # white list policy should not allow reconnecting to ignored hosts force_stop(3) @@ -764,7 +764,7 @@ def test_black_list_with_host_filter_policy(self): assert first_node_count + third_node_count == 12 assert first_node_count == 8 or first_node_count == 4 - self.coordinator_stats.assert_query_count_equals(self, 2, 0) + self.coordinator_stats.assert_query_count_equals(2, 0) # policy should not allow reconnecting to ignored host force_stop(2) diff --git a/tests/integration/long/utils.py b/tests/integration/long/utils.py index a3ae705a34..93464df8ff 100644 --- a/tests/integration/long/utils.py +++ b/tests/integration/long/utils.py @@ -14,6 +14,7 @@ import logging import time +import pytest from collections import defaultdict from packaging.version import Version @@ -48,10 +49,10 @@ def get_query_count(self, node): ip = '127.0.0.%d' % node return self.coordinator_counts[ip] - def assert_query_count_equals(self, testcase, node, expected): + def assert_query_count_equals(self, node, expected): ip = '127.0.0.%d' % node if self.get_query_count(node) != expected: - testcase.fail('Expected %d queries to %s, but got %d. Query counts: %s' % ( + pytest.fail('Expected %d queries to %s, but got %d. Query counts: %s' % ( expected, ip, self.coordinator_counts[ip], dict(self.coordinator_counts))) From afc6e0bbd9cfda3ec32b86aed1fb2b9f3529200c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 22:23:46 +0200 Subject: [PATCH 073/298] test_types: Remove unittests asserts --- tests/unit/test_types.py | 42 ++++++++++++++++++++-------------------- tests/unit/util.py | 20 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index 7f32e14a90..ef5bd5196a 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -1004,9 +1004,9 @@ def test_host_order(self): hosts_equal = [Host(addr, SimpleConvictionPolicy) for addr in ("127.0.0.1", "127.0.0.1")] hosts_equal_conviction = [Host("127.0.0.1", SimpleConvictionPolicy), Host("127.0.0.1", ConvictionPolicy)] - check_sequence_consistency(self, hosts) - check_sequence_consistency(self, hosts_equal, equal=True) - check_sequence_consistency(self, hosts_equal_conviction, equal=True) + check_sequence_consistency(hosts) + check_sequence_consistency(hosts_equal, equal=True) + check_sequence_consistency(hosts_equal_conviction, equal=True) def test_date_order(self): """ @@ -1020,8 +1020,8 @@ def test_date_order(self): """ dates_from_string = [Date("2017-01-01"), Date("2017-01-05"), Date("2017-01-09"), Date("2017-01-13")] dates_from_string_equal = [Date("2017-01-01"), Date("2017-01-01")] - check_sequence_consistency(self, dates_from_string) - check_sequence_consistency(self, dates_from_string_equal, equal=True) + check_sequence_consistency(dates_from_string) + check_sequence_consistency(dates_from_string_equal, equal=True) date_format = "%Y-%m-%d" @@ -1031,15 +1031,15 @@ def test_date_order(self): for dtstr in ("2017-01-02", "2017-01-06", "2017-01-10", "2017-01-14") ] dates_from_value_equal = [Date(1), Date(1)] - check_sequence_consistency(self, dates_from_value) - check_sequence_consistency(self, dates_from_value_equal, equal=True) + check_sequence_consistency(dates_from_value) + check_sequence_consistency(dates_from_value_equal, equal=True) dates_from_datetime = [Date(datetime.datetime.strptime(dtstr, date_format)) for dtstr in ("2017-01-03", "2017-01-07", "2017-01-11", "2017-01-15")] dates_from_datetime_equal = [Date(datetime.datetime.strptime("2017-01-01", date_format)), Date(datetime.datetime.strptime("2017-01-01", date_format))] - check_sequence_consistency(self, dates_from_datetime) - check_sequence_consistency(self, dates_from_datetime_equal, equal=True) + check_sequence_consistency(dates_from_datetime) + check_sequence_consistency(dates_from_datetime_equal, equal=True) dates_from_date = [ Date(datetime.datetime.strptime(dtstr, date_format).date()) for dtstr in @@ -1048,10 +1048,10 @@ def test_date_order(self): dates_from_date_equal = [datetime.datetime.strptime(dtstr, date_format) for dtstr in ("2017-01-09", "2017-01-9")] - check_sequence_consistency(self, dates_from_date) - check_sequence_consistency(self, dates_from_date_equal, equal=True) + check_sequence_consistency(dates_from_date) + check_sequence_consistency(dates_from_date_equal, equal=True) - check_sequence_consistency(self, self._shuffle_lists(dates_from_string, dates_from_value, + check_sequence_consistency(self._shuffle_lists(dates_from_string, dates_from_value, dates_from_datetime, dates_from_date)) def test_timer_order(self): @@ -1066,23 +1066,23 @@ def test_timer_order(self): """ time_from_int = [Time(1000), Time(4000), Time(7000), Time(10000)] time_from_int_equal = [Time(1), Time(1)] - check_sequence_consistency(self, time_from_int) - check_sequence_consistency(self, time_from_int_equal, equal=True) + check_sequence_consistency(time_from_int) + check_sequence_consistency(time_from_int_equal, equal=True) time_from_datetime = [Time(datetime.time(hour=0, minute=0, second=0, microsecond=us)) for us in (2, 5, 8, 11)] time_from_datetime_equal = [Time(datetime.time(hour=0, minute=0, second=0, microsecond=us)) for us in (1, 1)] - check_sequence_consistency(self, time_from_datetime) - check_sequence_consistency(self, time_from_datetime_equal, equal=True) + check_sequence_consistency(time_from_datetime) + check_sequence_consistency(time_from_datetime_equal, equal=True) time_from_string = [Time("00:00:00.000003000"), Time("00:00:00.000006000"), Time("00:00:00.000009000"), Time("00:00:00.000012000")] time_from_string_equal = [Time("00:00:00.000004000"), Time("00:00:00.000004000")] - check_sequence_consistency(self, time_from_string) - check_sequence_consistency(self, time_from_string_equal, equal=True) + check_sequence_consistency(time_from_string) + check_sequence_consistency(time_from_string_equal, equal=True) - check_sequence_consistency(self, self._shuffle_lists(time_from_int, time_from_datetime, time_from_string)) + check_sequence_consistency(self._shuffle_lists(time_from_int, time_from_datetime, time_from_string)) def test_token_order(self): """ @@ -1096,5 +1096,5 @@ def test_token_order(self): """ tokens = [Token(1), Token(2), Token(3), Token(4)] tokens_equal = [Token(1), Token(1)] - check_sequence_consistency(self, tokens) - check_sequence_consistency(self, tokens_equal, equal=True) + check_sequence_consistency(tokens) + check_sequence_consistency(tokens_equal, equal=True) diff --git a/tests/unit/util.py b/tests/unit/util.py index e57fa6c3ee..603eb4d9b5 100644 --- a/tests/unit/util.py +++ b/tests/unit/util.py @@ -11,20 +11,20 @@ # limitations under the License. -def check_sequence_consistency(unit_test, ordered_sequence, equal=False): +def check_sequence_consistency(ordered_sequence, equal=False): for i, el in enumerate(ordered_sequence): for previous in ordered_sequence[:i]: - _check_order_consistency(unit_test, previous, el, equal) + _check_order_consistency(previous, el, equal) for posterior in ordered_sequence[i + 1:]: - _check_order_consistency(unit_test, el, posterior, equal) + _check_order_consistency(el, posterior, equal) -def _check_order_consistency(unit_test, smaller, bigger, equal=False): - unit_test.assertLessEqual(smaller, bigger) - unit_test.assertGreaterEqual(bigger, smaller) +def _check_order_consistency(smaller, bigger, equal=False): + assert smaller <= bigger + assert bigger >= smaller if equal: - unit_test.assertEqual(smaller, bigger) + assert smaller == bigger else: - unit_test.assertNotEqual(smaller, bigger) - unit_test.assertLess(smaller, bigger) - unit_test.assertGreater(bigger, smaller) + assert smaller != bigger + assert smaller < bigger + assert bigger > smaller From da874c25f346c57d018cd173445d954fe3924f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 17:27:18 +0200 Subject: [PATCH 074/298] tests: Get rid of self.fail calls They can be replaced, depending on situation, with: - assert - pytest.raises - pytest.fail --- .../cqlengine/columns/test_counter_column.py | 16 +++--------- tests/integration/long/test_consistency.py | 8 +++--- .../long/test_loadbalancingpolicies.py | 26 ++++++------------- tests/integration/standard/test_cluster.py | 3 ++- tests/integration/standard/test_connection.py | 5 ++-- tests/integration/standard/test_metadata.py | 3 +-- tests/integration/standard/test_query.py | 4 +-- .../integration/standard/test_query_paging.py | 3 ++- .../standard/test_row_factories.py | 7 ++--- .../standard/test_single_interface.py | 7 +++-- tests/integration/upgrade/__init__.py | 6 ++--- tests/unit/advanced/test_metadata.py | 6 ++--- tests/unit/test_parameter_binding.py | 23 +++++++--------- tests/unit/test_policies.py | 8 ++---- tests/unit/test_sortedset.py | 7 +++-- 15 files changed, 51 insertions(+), 81 deletions(-) diff --git a/tests/integration/cqlengine/columns/test_counter_column.py b/tests/integration/cqlengine/columns/test_counter_column.py index 338a0944dc..5f69475b34 100644 --- a/tests/integration/cqlengine/columns/test_counter_column.py +++ b/tests/integration/cqlengine/columns/test_counter_column.py @@ -13,6 +13,7 @@ # limitations under the License. from uuid import uuid4 +import pytest from cassandra.cqlengine import columns from cassandra.cqlengine.management import sync_table, drop_table @@ -32,37 +33,28 @@ class TestClassConstruction(BaseCassEngTestCase): def test_defining_a_non_counter_column_fails(self): """ Tests that defining a non counter column field in a model with a counter column fails """ - try: + with pytest.raises(ModelDefinitionException): class model(Model): partition = columns.UUID(primary_key=True, default=uuid4) counter = columns.Counter() text = columns.Text() - self.fail("did not raise expected ModelDefinitionException") - except ModelDefinitionException: - pass def test_defining_a_primary_key_counter_column_fails(self): """ Tests that defining primary keys on counter columns fails """ - try: + with pytest.raises(TypeError): class model(Model): partition = columns.UUID(primary_key=True, default=uuid4) cluster = columns.Counter(primary_ley=True) counter = columns.Counter() - self.fail("did not raise expected TypeError") - except TypeError: - pass # force it - try: + with pytest.raises(ModelDefinitionException): class model(Model): partition = columns.UUID(primary_key=True, default=uuid4) cluster = columns.Counter() cluster.primary_key = True counter = columns.Counter() - self.fail("did not raise expected ModelDefinitionException") - except ModelDefinitionException: - pass class TestCounterColumn(BaseCassEngTestCase): diff --git a/tests/integration/long/test_consistency.py b/tests/integration/long/test_consistency.py index 5cc6b1891a..48d0ca4ae2 100644 --- a/tests/integration/long/test_consistency.py +++ b/tests/integration/long/test_consistency.py @@ -17,6 +17,7 @@ import sys import time import traceback +import pytest from cassandra import ConsistencyLevel, OperationTimedOut, ReadTimeout, WriteTimeout, Unavailable from cassandra.cluster import ExecutionProfile, EXEC_PROFILE_DEFAULT @@ -51,12 +52,12 @@ def setUp(self): self.coordinator_stats = CoordinatorStats() def _cl_failure(self, consistency_level, e): - self.fail('Instead of success, saw %s for CL.%s:\n\n%s' % ( + pytest.fail('Instead of success, saw %s for CL.%s:\n\n%s' % ( e, ConsistencyLevel.value_to_name[consistency_level], traceback.format_exc())) def _cl_expected_failure(self, cl): - self.fail('Test passed at ConsistencyLevel.%s:\n\n%s' % ( + pytest.fail('Test passed at ConsistencyLevel.%s:\n\n%s' % ( ConsistencyLevel.value_to_name[cl], traceback.format_exc())) def _insert(self, session, keyspace, count, consistency_level=ConsistencyLevel.ONE): @@ -360,6 +361,3 @@ def test_pool_with_host_down(self): start(node_to_stop) wait_for_up(cluster, node_to_stop) cluster.shutdown() - - - diff --git a/tests/integration/long/test_loadbalancingpolicies.py b/tests/integration/long/test_loadbalancingpolicies.py index cf1f7629da..fd8edde14c 100644 --- a/tests/integration/long/test_loadbalancingpolicies.py +++ b/tests/integration/long/test_loadbalancingpolicies.py @@ -16,6 +16,7 @@ import struct import sys import traceback +import pytest from cassandra import cqltypes from cassandra import ConsistencyLevel, Unavailable, OperationTimedOut, ReadTimeout, ReadFailure, \ @@ -397,11 +398,8 @@ def test_dc_aware_roundrobin_one_remote_host(self): self.coordinator_stats.reset_counts() force_stop(2) - try: + with pytest.raises(NoHostAvailable): self._query(session, keyspace) - self.fail() - except NoHostAvailable: - pass def test_token_aware(self): keyspace = 'test_token_aware' @@ -436,13 +434,11 @@ def token_aware(self, keyspace, use_prepared=False): force_stop(2) self._wait_for_nodes_down([2], cluster) - try: + with pytest.raises(Unavailable) as e: self._query(session, keyspace, use_prepared=use_prepared) - self.fail() - except Unavailable as e: - assert e.consistency == 1 - assert e.required_replicas == 1 - assert e.alive_replicas == 0 + assert e.value.consistency == 1 + assert e.value.required_replicas == 1 + assert e.value.alive_replicas == 0 self.coordinator_stats.reset_counts() start(2) @@ -458,11 +454,8 @@ def token_aware(self, keyspace, use_prepared=False): stop(2) self._wait_for_nodes_down([2], cluster) - try: + with pytest.raises(Unavailable): self._query(session, keyspace, use_prepared=use_prepared) - self.fail() - except Unavailable: - pass self.coordinator_stats.reset_counts() start(2) @@ -717,11 +710,8 @@ def test_white_list(self): force_stop(2) self._wait_for_nodes_down([2]) - try: + with pytest.raises(NoHostAvailable): self._query(session, keyspace) - self.fail() - except NoHostAvailable: - pass def test_black_list_with_host_filter_policy(self): """ diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index e89d1290b4..6a98564b98 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -24,6 +24,7 @@ import warnings from packaging.version import Version import os +import pytest import cassandra from cassandra.cluster import NoHostAvailable, ExecutionProfile, EXEC_PROFILE_DEFAULT, ControlConnection, Cluster @@ -1490,7 +1491,7 @@ def test_invalid_protocol_version_beta_option(self): with self.assertRaises(NoHostAvailable): cluster.connect() except Exception as e: - self.fail("Unexpected error encountered {0}".format(e.message)) + pytest.fail("Unexpected error encountered {0}".format(e.message)) @protocolv6 def test_valid_protocol_version_beta_options_connect(self): diff --git a/tests/integration/standard/test_connection.py b/tests/integration/standard/test_connection.py index 9558aeaeb0..630e5e6ba0 100644 --- a/tests/integration/standard/test_connection.py +++ b/tests/integration/standard/test_connection.py @@ -22,6 +22,7 @@ from threading import Thread, Event import time from unittest import SkipTest +import pytest from cassandra import ConsistencyLevel, OperationTimedOut, DependencyException from cassandra.cluster import NoHostAvailable, ConnectionShutdown, ExecutionProfile, EXEC_PROFILE_DEFAULT @@ -179,7 +180,7 @@ def wait_for_connections(self, host, cluster): if connections: return connections time.sleep(.1) - self.fail("No new connections found") + pytest.fail("No new connections found") def wait_for_no_connections(self, host, cluster): retry = 0 @@ -189,7 +190,7 @@ def wait_for_no_connections(self, host, cluster): if not connections: return time.sleep(.5) - self.fail("Connections never cleared") + pytest.fail("Connections never cleared") class ConnectionTests(object): diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 10def0d50f..d2f28920ed 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -1349,8 +1349,7 @@ def send_msg(self, msg, request_id, cb, encoder=ProtocolHandler.encode_message, for stmt in stmts: if "SELECT now() FROM system.local WHERE key='local'" in stmt: continue - if "USING TIMEOUT 2000ms" not in stmt: - self.fail(f"query `{stmt}` does not contain `USING TIMEOUT 2000ms`") + assert "USING TIMEOUT 2000ms" in stmt, f"query `{stmt}` does not contain `USING TIMEOUT 2000ms`" class KeyspaceAlterMetadata(unittest.TestCase): diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 2b064e1027..77f19cb8e0 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -937,13 +937,13 @@ def test_no_connection_refused_on_timeout(self): # In this case result is an exception exception_type = type(result).__name__ if exception_type == "NoHostAvailable": - self.fail("PYTHON-91: Disconnected from Cassandra: %s" % result.message) + pytest.fail("PYTHON-91: Disconnected from Cassandra: %s" % result.message) if exception_type in ["WriteTimeout", "WriteFailure", "ReadTimeout", "ReadFailure", "ErrorMessageSub"]: if type(result).__name__ in ["WriteTimeout", "WriteFailure"]: received_timeout = True continue - self.fail("Unexpected exception %s: %s" % (exception_type, result.message)) + pytest.fail("Unexpected exception %s: %s" % (exception_type, result.message)) # Make sure test passed assert received_timeout diff --git a/tests/integration/standard/test_query_paging.py b/tests/integration/standard/test_query_paging.py index 84df047927..28567d991b 100644 --- a/tests/integration/standard/test_query_paging.py +++ b/tests/integration/standard/test_query_paging.py @@ -20,6 +20,7 @@ from itertools import cycle, count from threading import Event +import pytest from cassandra import ConsistencyLevel from cassandra.cluster import EXEC_PROFILE_DEFAULT, ExecutionProfile @@ -289,7 +290,7 @@ def handle_page(rows, future, counter, number_of_calls): def handle_error(err): event.set() - self.fail(err) + pytest.fail(err) future.add_callbacks(callback=handle_page, callback_args=(future, counter, number_of_calls), errback=handle_error) diff --git a/tests/integration/standard/test_row_factories.py b/tests/integration/standard/test_row_factories.py index b96e2f2901..187f35704a 100644 --- a/tests/integration/standard/test_row_factories.py +++ b/tests/integration/standard/test_row_factories.py @@ -16,6 +16,7 @@ BasicSharedKeyspaceUnitTestCaseWFunctionTable, BasicSharedKeyspaceUnitTestCase, execute_until_pass, TestCluster import unittest +import pytest from cassandra.cluster import ResultSet, ExecutionProfile, EXEC_PROFILE_DEFAULT from cassandra.query import tuple_factory, named_tuple_factory, dict_factory, ordered_dict_factory @@ -194,7 +195,7 @@ def test_no_exception_on_select(self): try: self.session.execute('SELECT * FROM test1rf.table_num_col') except ValueError as e: - self.fail("Unexpected ValueError exception: %s" % e.message) + pytest.fail("Unexpected ValueError exception: %s" % e.message) def test_can_select_using_alias(self): """ @@ -206,7 +207,7 @@ def test_can_select_using_alias(self): try: self.session.execute('SELECT key, "626972746864617465" AS my_col from test1rf.table_num_col') except ValueError as e: - self.fail("Unexpected ValueError exception: %s" % e.message) + pytest.fail("Unexpected ValueError exception: %s" % e.message) def test_can_select_with_dict_factory(self): """ @@ -218,4 +219,4 @@ def test_can_select_with_dict_factory(self): try: cluster.connect().execute('SELECT * FROM test1rf.table_num_col') except ValueError as e: - self.fail("Unexpected ValueError exception: %s" % e.message) + pytest.fail("Unexpected ValueError exception: %s" % e.message) diff --git a/tests/integration/standard/test_single_interface.py b/tests/integration/standard/test_single_interface.py index 802b42277e..3fd90b9708 100644 --- a/tests/integration/standard/test_single_interface.py +++ b/tests/integration/standard/test_single_interface.py @@ -13,6 +13,7 @@ # limitations under the License. import unittest +import pytest from cassandra import ConsistencyLevel from cassandra.query import SimpleStatement @@ -58,11 +59,9 @@ def test_single_interface(self): assert endpoint.address == host.broadcast_rpc_address assert endpoint.port == host.broadcast_rpc_port - if host.broadcast_rpc_port in broadcast_rpc_ports: - self.fail("Duplicate broadcast_rpc_port") + assert host.broadcast_rpc_port not in broadcast_rpc_ports, "Duplicate broadcast_rpc_port" broadcast_rpc_ports.append(host.broadcast_rpc_port) - if host.broadcast_port in broadcast_ports: - self.fail("Duplicate broadcast_port") + assert host.broadcast_port not in broadcast_ports, "Duplicate broadcast_port" broadcast_ports.append(host.broadcast_port) for _ in range(1, 100): diff --git a/tests/integration/upgrade/__init__.py b/tests/integration/upgrade/__init__.py index c5c06c4b01..a1c751bcbd 100644 --- a/tests/integration/upgrade/__init__.py +++ b/tests/integration/upgrade/__init__.py @@ -26,7 +26,7 @@ from ccmlib.node import TimeoutError import time import logging - +import pytest import unittest @@ -78,7 +78,7 @@ def setUpClass(cls): cls.logger_handler = MockLoggingHandler() cls.logger = logging.getLogger(cluster.__name__) cls.logger.addHandler(cls.logger_handler) - + @classmethod def tearDownClass(cls): cls.logger.removeHandler(cls.logger_handler) @@ -166,7 +166,7 @@ def upgrade_node(self, node): try: node.start(wait_for_binary_proto=True, wait_other_notice=True) except TimeoutError: - self.fail("Error starting C* node while upgrading") + pytest.fail("Error starting C* node while upgrading") return True diff --git a/tests/unit/advanced/test_metadata.py b/tests/unit/advanced/test_metadata.py index a3c47ab524..5ccfa5e477 100644 --- a/tests/unit/advanced/test_metadata.py +++ b/tests/unit/advanced/test_metadata.py @@ -135,16 +135,14 @@ def wait_for_responses(self, *msgs, **kwargs): p._query_all() for q in conn.queries: - if "USING TIMEOUT" in q.query: - self.fail(f"<{schemaClass.__name__}> query `{q.query}` contains `USING TIMEOUT`, while should not") + assert "USING TIMEOUT" not in q.query, f"<{schemaClass.__name__}> query `{q.query}` contains `USING TIMEOUT`, while should not" conn = FakeConnection() p = schemaClass(conn, 2.0, 1000, datetime.timedelta(seconds=2)) p._query_all() for q in conn.queries: - if "USING TIMEOUT 2000ms" not in q.query: - self.fail(f"{schemaClass.__name__} query `{q.query}` does not contain `USING TIMEOUT 2000ms`") + assert "USING TIMEOUT 2000ms" in q.query, f"{schemaClass.__name__} query `{q.query}` does not contain `USING TIMEOUT 2000ms`" def get_all_schema_parser_classes(cl): diff --git a/tests/unit/test_parameter_binding.py b/tests/unit/test_parameter_binding.py index 03e23a172a..c8f9a71e65 100644 --- a/tests/unit/test_parameter_binding.py +++ b/tests/unit/test_parameter_binding.py @@ -13,6 +13,7 @@ # limitations under the License. import unittest +import pytest from cassandra.encoder import Encoder from cassandra.protocol import ColumnMetadata @@ -91,25 +92,19 @@ def setUpClass(cls): def test_invalid_argument_type(self): values = (0, 0, 0, 'string not int') - try: + with pytest.raises(TypeError) as e: self.bound.bind(values) - except TypeError as e: - assert 'v0' in str(e) - assert 'Int32Type' in str(e) - assert 'str' in str(e) - else: - self.fail('Passed invalid type but exception was not thrown') + assert 'v0' in str(e) + assert 'Int32Type' in str(e) + assert 'str' in str(e) values = (['1', '2'], 0, 0, 0) - try: + with pytest.raises(TypeError) as e: self.bound.bind(values) - except TypeError as e: - assert 'rk0' in str(e) - assert 'Int32Type' in str(e) - assert 'list' in str(e) - else: - self.fail('Passed invalid type but exception was not thrown') + assert 'rk0' in str(e) + assert 'Int32Type' in str(e) + assert 'list' in str(e) def test_inherit_fetch_size(self): keyspace = 'keyspace1' diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index de06a9b59c..8f419b31f1 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -161,8 +161,7 @@ def host_down(): else: sys.setswitchinterval(original_interval) - if errors: - self.fail("Saw errors: %s" % (errors,)) + assert not errors, "Saw errors: %s" % (errors,) def test_no_live_nodes(self): """ @@ -913,11 +912,8 @@ def test_schedule_negative_max_attempts(self): delay = 2 max_attempts = -100 - try: + with pytest.raises(ValueError): ConstantReconnectionPolicy(delay=delay, max_attempts=max_attempts) - self.fail('max_attempts should throw ValueError when negative') - except ValueError: - pass def test_schedule_infinite_attempts(self): delay = 2 diff --git a/tests/unit/test_sortedset.py b/tests/unit/test_sortedset.py index 915d5f52f7..a6e0123ef9 100644 --- a/tests/unit/test_sortedset.py +++ b/tests/unit/test_sortedset.py @@ -13,6 +13,7 @@ # limitations under the License. import unittest +import pytest from cassandra.util import sortedset from cassandra.cqltypes import EMPTY @@ -186,11 +187,9 @@ def test_pop(self): ss = sortedset([2, 1]) assert ss.pop() == 2 assert ss.pop() == 1 - try: + with pytest.raises((KeyError, IndexError)): ss.pop() - self.fail("Error not thrown") - except (KeyError, IndexError) as e: - pass + def test_remove(self): ss = sortedset([2, 1]) From 874b918b0cf3ff32c7a32874b4c0c22456b70b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Wed, 16 Jul 2025 18:21:25 +0200 Subject: [PATCH 075/298] check_lookup: use plain assert --- .../cqlengine/operators/__init__.py | 4 ++-- .../operators/test_where_operators.py | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/integration/cqlengine/operators/__init__.py b/tests/integration/cqlengine/operators/__init__.py index 05a41c46fd..45690e9448 100644 --- a/tests/integration/cqlengine/operators/__init__.py +++ b/tests/integration/cqlengine/operators/__init__.py @@ -15,6 +15,6 @@ from cassandra.cqlengine.operators import BaseWhereOperator -def check_lookup(test_case, symbol, expected): +def check_lookup(symbol, expected): op = BaseWhereOperator.get_operator(symbol) - test_case.assertEqual(op, expected) + assert op == expected diff --git a/tests/integration/cqlengine/operators/test_where_operators.py b/tests/integration/cqlengine/operators/test_where_operators.py index 4b789377ed..4297e63d43 100644 --- a/tests/integration/cqlengine/operators/test_where_operators.py +++ b/tests/integration/cqlengine/operators/test_where_operators.py @@ -33,15 +33,15 @@ class TestWhereOperators(unittest.TestCase): def test_symbol_lookup(self): """ tests where symbols are looked up properly """ - check_lookup(self, 'EQ', EqualsOperator) - check_lookup(self, 'NE', NotEqualsOperator) - check_lookup(self, 'IN', InOperator) - check_lookup(self, 'GT', GreaterThanOperator) - check_lookup(self, 'GTE', GreaterThanOrEqualOperator) - check_lookup(self, 'LT', LessThanOperator) - check_lookup(self, 'LTE', LessThanOrEqualOperator) - check_lookup(self, 'CONTAINS', ContainsOperator) - check_lookup(self, 'LIKE', LikeOperator) + check_lookup('EQ', EqualsOperator) + check_lookup('NE', NotEqualsOperator) + check_lookup('IN', InOperator) + check_lookup('GT', GreaterThanOperator) + check_lookup('GTE', GreaterThanOrEqualOperator) + check_lookup('LT', LessThanOperator) + check_lookup('LTE', LessThanOrEqualOperator) + check_lookup('CONTAINS', ContainsOperator) + check_lookup('LIKE', LikeOperator) def test_operator_rendering(self): """ tests symbols are rendered properly """ @@ -68,7 +68,7 @@ def test_is_not_null_to_cql(self): @test_category cqlengine """ - check_lookup(self, 'IS NOT NULL', IsNotNullOperator) + check_lookup('IS NOT NULL', IsNotNullOperator) # The * is not expanded because there are no referred fields assert str(TestQueryUpdateModel.filter(IsNotNull("text")).limit(2)) == 'SELECT * FROM cqlengine_test.test_query_update_model WHERE "text" IS NOT NULL LIMIT 2' From 4a0b793ff0733c18eb6585a04a86dd169779dfe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Wed, 16 Jul 2025 18:26:10 +0200 Subject: [PATCH 076/298] integration/standard/test_metadata: Plain asserts in FunctionTest --- tests/integration/standard/test_metadata.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index d2f28920ed..581df5b9d0 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -1536,18 +1536,18 @@ def __init__(self, test_case, meta_class, element_meta, **function_kwargs): def __enter__(self): tc = self.test_case expected_meta = self.meta_class(**self.function_kwargs) - tc.assertNotIn(expected_meta.signature, self.element_meta) + assert expected_meta.signature not in self.element_meta tc.session.execute(expected_meta.as_cql_query()) - tc.assertIn(expected_meta.signature, self.element_meta) + assert expected_meta.signature in self.element_meta generated_meta = self.element_meta[expected_meta.signature] - self.test_case.assertEqual(generated_meta.as_cql_query(), expected_meta.as_cql_query()) + assert generated_meta.as_cql_query() == expected_meta.as_cql_query() return self def __exit__(self, exc_type, exc_val, exc_tb): tc = self.test_case tc.session.execute("DROP %s %s.%s" % (self.meta_class.__name__, tc.keyspace_name, self.signature)) - tc.assertNotIn(self.signature, self.element_meta) + assert self.signature not in self.element_meta @property def signature(self): From ff4d392c8877ab7c21c534a0661a877a9ae49dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Wed, 16 Jul 2025 18:27:31 +0200 Subject: [PATCH 077/298] execute_count: Use plain assert --- tests/integration/cqlengine/__init__.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/integration/cqlengine/__init__.py b/tests/integration/cqlengine/__init__.py index 204dcb1253..7fae437370 100644 --- a/tests/integration/cqlengine/__init__.py +++ b/tests/integration/cqlengine/__init__.py @@ -77,12 +77,7 @@ def wrapped_function(*args, **kwargs): # DeMonkey Patch our code cassandra.cqlengine.connection.execute = original_function # Check to see if we have a pre-existing test case to work from. - if args: - test_case = args[0] - else: - test_case = unittest.TestCase("__init__") - # Check to see if the count is what you expect - test_case.assertEqual(count.get_counter(), expected, msg="Expected number of cassandra.cqlengine.connection.execute calls ({0}) doesn't match actual number invoked ({1})".format(expected, count.get_counter())) + assert count.get_counter() == expected, "Expected number of cassandra.cqlengine.connection.execute calls ({0}) doesn't match actual number invoked ({1})".format(expected, count.get_counter()) return to_return # Name of the wrapped function must match the original or unittest will error out. wrapped_function.__name__ = fn.__name__ @@ -94,5 +89,3 @@ def wrapped_function(*args, **kwargs): return wrapped_function return innerCounter - - From 07f01bb28e70e18cbae0c9d10568282db06e9c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Fri, 11 Jul 2025 17:37:01 +0200 Subject: [PATCH 078/298] Replace unitttest assertRaises with pytest.raises Replaces all uses of self.assertRaises and self.assertRaisesRegex with pytest.raises. I started this commit with command `unittest2pytest -f self_assert ./tests -w -n`. After it finished, I manually reviewed all differences, and performed fixes where necessary / useful. There were two types of fixes: - When using output value of context manager (with ... as X), unittest exposes the thrown exception as X.exception, while pytest as X.value. - In few places there were calls like `self.assertRaises(T, func, **{'arg': val, ...}`. Passing args as dict with ** is not pretty, so I desugared this into directly passing named args. --- .../columns/test_container_columns.py | 54 ++++++++---- .../cqlengine/columns/test_validation.py | 85 ++++++++++--------- .../cqlengine/management/test_management.py | 15 ++-- .../model/test_class_construction.py | 17 ++-- .../integration/cqlengine/model/test_model.py | 11 ++- .../cqlengine/model/test_polymorphism.py | 13 +-- .../integration/cqlengine/model/test_udts.py | 11 +-- .../cqlengine/model/test_updates.py | 9 +- .../operators/test_where_operators.py | 5 +- .../cqlengine/query/test_batch_query.py | 7 +- .../integration/cqlengine/query/test_named.py | 5 +- .../cqlengine/query/test_queryoperators.py | 10 ++- .../cqlengine/query/test_queryset.py | 77 ++++++++--------- .../cqlengine/query/test_updates.py | 9 +- .../cqlengine/statements/test_where_clause.py | 3 +- .../integration/cqlengine/test_batch_query.py | 10 +-- .../integration/cqlengine/test_connections.py | 49 +++++------ .../cqlengine/test_context_query.py | 11 +-- tests/integration/cqlengine/test_ifexists.py | 36 ++++---- .../integration/cqlengine/test_ifnotexists.py | 14 +-- .../cqlengine/test_lwt_conditional.py | 31 +++---- tests/integration/cqlengine/test_timestamp.py | 13 +-- tests/integration/long/test_failure_types.py | 15 ++-- tests/integration/long/test_ipv6.py | 10 ++- tests/integration/long/test_policies.py | 5 +- tests/integration/long/test_ssl.py | 9 +- .../simulacron/test_backpressure.py | 6 +- tests/integration/simulacron/test_cluster.py | 7 +- .../integration/simulacron/test_connection.py | 13 ++- tests/integration/simulacron/test_policies.py | 9 +- .../standard/test_authentication.py | 24 +++--- tests/integration/standard/test_cluster.py | 50 ++++++----- tests/integration/standard/test_concurrent.py | 14 +-- .../standard/test_custom_cluster.py | 3 +- .../standard/test_custom_payload.py | 3 +- .../standard/test_custom_protocol_handler.py | 5 +- tests/integration/standard/test_metadata.py | 6 +- tests/integration/standard/test_metrics.py | 15 ++-- .../standard/test_prepared_statements.py | 39 ++++++--- tests/integration/standard/test_query.py | 36 +++++--- .../standard/test_rate_limit_exceeded.py | 11 +-- tests/integration/standard/test_types.py | 26 ++++-- tests/integration/standard/test_udts.py | 7 +- tests/integration/upgrade/test_upgrade.py | 3 +- tests/unit/advanced/test_geometry.py | 17 ++-- tests/unit/advanced/test_graph.py | 35 +++++--- tests/unit/column_encryption/test_policies.py | 27 +++--- tests/unit/cqlengine/test_connection.py | 5 +- tests/unit/test_cluster.py | 55 +++++++----- tests/unit/test_concurrent.py | 4 +- tests/unit/test_connection.py | 4 +- tests/unit/test_host_connection_pool.py | 16 ++-- tests/unit/test_metadata.py | 19 +++-- tests/unit/test_orderedmap.py | 17 ++-- tests/unit/test_parameter_binding.py | 36 +++++--- tests/unit/test_policies.py | 50 +++++++---- tests/unit/test_protocol.py | 8 +- tests/unit/test_response_future.py | 37 +++++--- tests/unit/test_resultset.py | 17 ++-- tests/unit/test_segment.py | 7 +- tests/unit/test_sortedset.py | 12 +-- tests/unit/test_time_util.py | 5 +- tests/unit/test_timestamps.py | 3 +- tests/unit/test_types.py | 29 ++++--- tests/unit/test_util_types.py | 21 +++-- 65 files changed, 719 insertions(+), 516 deletions(-) diff --git a/tests/integration/cqlengine/columns/test_container_columns.py b/tests/integration/cqlengine/columns/test_container_columns.py index 6b6cb929e7..6fb2754877 100644 --- a/tests/integration/cqlengine/columns/test_container_columns.py +++ b/tests/integration/cqlengine/columns/test_container_columns.py @@ -73,7 +73,8 @@ def tearDownClass(cls): drop_table(TestSetModel) def test_add_none_fails(self): - self.assertRaises(ValidationError, TestSetModel.create, **{'int_set': set([None])}) + with pytest.raises(ValidationError): + TestSetModel.create(int_set=set([None])) def test_empty_set_initial(self): """ @@ -127,7 +128,8 @@ def test_type_validation(self): """ Tests that attempting to use the wrong types will raise an exception """ - self.assertRaises(ValidationError, TestSetModel.create, **{'int_set': set(('string', True)), 'text_set': set((1, 3.0))}) + with pytest.raises(ValidationError): + TestSetModel.create(int_set=set(('string', True)), text_set=set((1, 3.0))) def test_element_count_validation(self): """ @@ -144,7 +146,8 @@ def test_element_count_validation(self): except OperationTimedOut: #This will happen if the host is remote assert not CASSANDRA_IP.startswith("127.0.0.") - self.assertRaises(ValidationError, TestSetModel.create, **{'text_set': set(str(uuid4()) for i in range(65536))}) + with pytest.raises(ValidationError): + TestSetModel.create(text_set=set(str(uuid4()) for i in range(65536))) def test_partial_updates(self): """ Tests that partial udpates work as expected """ @@ -244,7 +247,8 @@ def test_type_validation(self): """ Tests that attempting to use the wrong types will raise an exception """ - self.assertRaises(ValidationError, TestListModel.create, **{'int_list': ['string', True], 'text_list': [1, 3.0]}) + with pytest.raises(ValidationError): + TestListModel.create(int_list=['string', True], text_list=[1, 3.0]) def test_element_count_validation(self): """ @@ -258,7 +262,8 @@ def test_element_count_validation(self): ex_type, ex, tb = sys.exc_info() log.warning("{0}: {1} Backtrace: {2}".format(ex_type.__name__, ex, traceback.extract_tb(tb))) del tb - self.assertRaises(ValidationError, TestListModel.create, **{'text_list': [str(uuid4()) for _ in range(65536)]}) + with pytest.raises(ValidationError): + TestListModel.create(text_list=[str(uuid4()) for _ in range(65536)]) def test_partial_updates(self): """ Tests that partial udpates work as expected """ @@ -332,7 +337,8 @@ def test_update_from_non_empty_to_empty(self): def test_insert_none(self): pkey = uuid4() - self.assertRaises(ValidationError, TestListModel.create, **{'partition': pkey, 'int_list': [None]}) + with pytest.raises(ValidationError): + TestListModel.create(partition=pkey, int_list=[None]) def test_blind_list_updates_from_none(self): """ Tests that updates from None work as expected """ @@ -374,7 +380,8 @@ def test_empty_default(self): tmp.int_map['blah'] = 1 def test_add_none_as_map_key(self): - self.assertRaises(ValidationError, TestMapModel.create, **{'int_map': {None: uuid4()}}) + with pytest.raises(ValidationError): + TestMapModel.create(int_map={None: uuid4()}) def test_empty_retrieve(self): tmp = TestMapModel.create() @@ -416,7 +423,8 @@ def test_type_validation(self): """ Tests that attempting to use the wrong types will raise an exception """ - self.assertRaises(ValidationError, TestMapModel.create, **{'int_map': {'key': 2, uuid4(): 'val'}, 'text_map': {2: 5}}) + with pytest.raises(ValidationError): + TestMapModel.create(int_map={'key': 2, uuid4(): 'val'}, text_map={2: 5}) def test_element_count_validation(self): """ @@ -430,7 +438,8 @@ def test_element_count_validation(self): ex_type, ex, tb = sys.exc_info() log.warning("{0}: {1} Backtrace: {2}".format(ex_type.__name__, ex, traceback.extract_tb(tb))) del tb - self.assertRaises(ValidationError, TestMapModel.create, **{'text_map': dict((str(uuid4()), i) for i in range(65536))}) + with pytest.raises(ValidationError): + TestMapModel.create(text_map=dict((str(uuid4()), i) for i in range(65536))) def test_partial_updates(self): """ Tests that partial udpates work as expected """ @@ -636,9 +645,12 @@ def test_type_validation(self): @test_category object_mapper """ - self.assertRaises(ValidationError, TestTupleModel.create, **{'int_tuple': ('string', True), 'text_tuple': ('test', 'test'), 'mixed_tuple': ('one', 2, 'three')}) - self.assertRaises(ValidationError, TestTupleModel.create, **{'int_tuple': ('string', 'string'), 'text_tuple': (1, 3.0), 'mixed_tuple': ('one', 2, 'three')}) - self.assertRaises(ValidationError, TestTupleModel.create, **{'int_tuple': ('string', 'string'), 'text_tuple': ('test', 'test'), 'mixed_tuple': (1, "two", 3)}) + with pytest.raises(ValidationError): + TestTupleModel.create(int_tuple=('string', True), text_tuple=('test', 'test'), mixed_tuple=('one', 2, 'three')) + with pytest.raises(ValidationError): + TestTupleModel.create(int_tuple=('string', 'string'), text_tuple=(1, 3.0), mixed_tuple=('one', 2, 'three')) + with pytest.raises(ValidationError): + TestTupleModel.create(int_tuple=('string', 'string'), text_tuple=('test', 'test'), mixed_tuple=(1, "two", 3)) def test_instantiation_with_column_class(self): """ @@ -854,12 +866,18 @@ def test_type_validation(self): set_tuple_bad_tuple_value = set((("text", "text"), ("text", "text"), ("text", "text"))) set_tuple_not_set = ['This', 'is', 'not', 'a', 'set'] - self.assertRaises(ValidationError, TestNestedModel.create, **{'list_list': list_list_bad_list_context}) - self.assertRaises(ValidationError, TestNestedModel.create, **{'list_list': list_list_no_list}) - self.assertRaises(ValidationError, TestNestedModel.create, **{'map_list': map_list_bad_value}) - self.assertRaises(ValidationError, TestNestedModel.create, **{'map_list': map_list_bad_key}) - self.assertRaises(ValidationError, TestNestedModel.create, **{'set_tuple': set_tuple_bad_tuple_value}) - self.assertRaises(ValidationError, TestNestedModel.create, **{'set_tuple': set_tuple_not_set}) + with pytest.raises(ValidationError): + TestNestedModel.create(list_list=list_list_bad_list_context) + with pytest.raises(ValidationError): + TestNestedModel.create(list_list=list_list_no_list) + with pytest.raises(ValidationError): + TestNestedModel.create(map_list=map_list_bad_value) + with pytest.raises(ValidationError): + TestNestedModel.create(map_list=map_list_bad_key) + with pytest.raises(ValidationError): + TestNestedModel.create(set_tuple=set_tuple_bad_tuple_value) + with pytest.raises(ValidationError): + TestNestedModel.create(set_tuple=set_tuple_not_set) def test_instantiation_with_column_class(self): """ diff --git a/tests/integration/cqlengine/columns/test_validation.py b/tests/integration/cqlengine/columns/test_validation.py index f86923ca66..ebffc0666c 100644 --- a/tests/integration/cqlengine/columns/test_validation.py +++ b/tests/integration/cqlengine/columns/test_validation.py @@ -33,6 +33,7 @@ from tests.integration import PROTOCOL_VERSION, CASSANDRA_VERSION, greaterthanorequalcass30, greaterthanorequalcass3_11 from tests.integration.cqlengine.base import BaseCassEngTestCase +import pytest class TestDatetime(BaseCassEngTestCase): @@ -90,7 +91,7 @@ def test_datetime_none(self): def test_datetime_invalid(self): dt_value= 'INVALID' - with self.assertRaises(TypeError): + with pytest.raises(TypeError): self.DatetimeTest.objects.create(test_id=4, created_at=dt_value) def test_datetime_timestamp(self): @@ -185,7 +186,7 @@ def test_varint_io(self): int2 = self.VarIntTest.objects(test_id=0).first() assert int1.bignum == int2.bignum - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): self.VarIntTest.objects.create(test_id=0, bignum="not_a_number") @@ -541,22 +542,22 @@ def test_min_length(self): Ascii(min_length=5).validate('kevin') Ascii(min_length=5).validate('kevintastic') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii(min_length=1).validate('') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii(min_length=1).validate(None) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii(min_length=6).validate('') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii(min_length=6).validate(None) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii(min_length=6).validate('kevin') - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Ascii(min_length=-1) def test_max_length(self): @@ -573,13 +574,13 @@ def test_max_length(self): Ascii(max_length=5).validate('b') Ascii(max_length=5).validate('blake') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii(max_length=0).validate('b') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii(max_length=5).validate('blaketastic') - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Ascii(max_length=-1) def test_length_range(self): @@ -588,10 +589,10 @@ def test_length_range(self): Ascii(min_length=10, max_length=10) Ascii(min_length=10, max_length=11) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Ascii(min_length=10, max_length=9) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Ascii(min_length=1, max_length=0) def test_type_checking(self): @@ -599,19 +600,19 @@ def test_type_checking(self): Ascii().validate(u'unicode') Ascii().validate(bytearray('bytearray', encoding='ascii')) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii().validate(5) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii().validate(True) Ascii().validate("!#$%&\'()*+,-./") - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii().validate('Beyonc' + chr(233)) if sys.version_info < (3, 1): - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii().validate(u'Beyonc' + unichr(233)) def test_unaltering_validation(self): @@ -629,26 +630,26 @@ def test_required_validation(self): """ Tests that validation raise on none and blank values if value required. """ Ascii(required=True).validate('k') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii(required=True).validate('') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii(required=True).validate(None) # With min_length set. Ascii(required=True, min_length=0).validate('k') Ascii(required=True, min_length=1).validate('k') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii(required=True, min_length=2).validate('k') # With max_length set. Ascii(required=True, max_length=1).validate('k') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Ascii(required=True, max_length=2).validate('kevin') - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Ascii(required=True, max_length=0) @@ -668,22 +669,22 @@ def test_min_length(self): Text(min_length=5).validate('blake') Text(min_length=5).validate('blaketastic') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Text(min_length=1).validate('') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Text(min_length=1).validate(None) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Text(min_length=6).validate('') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Text(min_length=6).validate(None) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Text(min_length=6).validate('blake') - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Text(min_length=-1) def test_max_length(self): @@ -700,13 +701,13 @@ def test_max_length(self): Text(max_length=5).validate('b') Text(max_length=5).validate('blake') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Text(max_length=0).validate('b') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Text(max_length=5).validate('blaketastic') - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Text(max_length=-1) def test_length_range(self): @@ -715,10 +716,10 @@ def test_length_range(self): Text(min_length=10, max_length=10) Text(min_length=10, max_length=11) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Text(min_length=10, max_length=9) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Text(min_length=1, max_length=0) def test_type_checking(self): @@ -726,10 +727,10 @@ def test_type_checking(self): Text().validate(u'unicode') Text().validate(bytearray('bytearray', encoding='ascii')) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Text().validate(5) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Text().validate(True) Text().validate("!#$%&\'()*+,-./") @@ -752,26 +753,26 @@ def test_required_validation(self): """ Tests that validation raise on none and blank values if value required. """ Text(required=True).validate('b') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Text(required=True).validate('') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Text(required=True).validate(None) # With min_length set. Text(required=True, min_length=0).validate('b') Text(required=True, min_length=1).validate('b') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Text(required=True, min_length=2).validate('b') # With max_length set. Text(required=True, max_length=1).validate('b') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): Text(required=True, max_length=2).validate('blake') - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Text(required=True, max_length=0) @@ -781,7 +782,7 @@ class TestModel(Model): id = UUID(primary_key=True, default=uuid4) def test_extra_field(self): - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): self.TestModel.create(bacon=5000) @@ -834,5 +835,5 @@ def test_inet_saves(self): def test_non_address_fails(self): # TODO: presently this only tests that the server blows it up. Is there supposed to be local validation? - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): self.InetTestModel.create(address="what is going on here?") diff --git a/tests/integration/cqlengine/management/test_management.py b/tests/integration/cqlengine/management/test_management.py index b24d61acaa..1332680cef 100644 --- a/tests/integration/cqlengine/management/test_management.py +++ b/tests/integration/cqlengine/management/test_management.py @@ -29,6 +29,7 @@ from tests.integration.cqlengine.query.test_queryset import TestModel from cassandra.cqlengine.usertype import UserType from tests.integration.cqlengine import DEFAULT_KEYSPACE +import pytest INCLUDE_REPAIR = (not CASSANDRA_VERSION >= Version('4-a')) and SCYLLA_VERSION is None # This should cover DSE 6.0+ @@ -261,7 +262,8 @@ def test_bogus_option_update(self): option = 'no way will this ever be an option' try: ModelWithTableProperties.__options__[option] = 'what was I thinking?' - self.assertRaisesRegex(KeyError, "Invalid table option.*%s.*" % option, sync_table, ModelWithTableProperties) + with pytest.raises(KeyError, match="Invalid table option.*%s.*" % option): + sync_table(ModelWithTableProperties) finally: ModelWithTableProperties.__options__.pop(option, None) @@ -297,9 +299,12 @@ def test_primary_key_validation(self): @test_category object_mapper """ sync_table(PrimaryKeysOnlyModel) - self.assertRaises(CQLEngineException, sync_table, PrimaryKeysModelChanged) - self.assertRaises(CQLEngineException, sync_table, PrimaryKeysAddedClusteringKey) - self.assertRaises(CQLEngineException, sync_table, PrimaryKeysRemovedPk) + with pytest.raises(CQLEngineException): + sync_table(PrimaryKeysModelChanged) + with pytest.raises(CQLEngineException): + sync_table(PrimaryKeysAddedClusteringKey) + with pytest.raises(CQLEngineException): + sync_table(PrimaryKeysRemovedPk) class IndexModel(Model): @@ -453,7 +458,7 @@ class FakeModel(object): pass def test_failure(self): - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): sync_table(self.FakeModel) diff --git a/tests/integration/cqlengine/model/test_class_construction.py b/tests/integration/cqlengine/model/test_class_construction.py index 1dc15e5300..df0a57d543 100644 --- a/tests/integration/cqlengine/model/test_class_construction.py +++ b/tests/integration/cqlengine/model/test_class_construction.py @@ -20,6 +20,7 @@ from cassandra.cqlengine.query import ModelQuerySet, DMLQuery from tests.integration.cqlengine.base import BaseCassEngTestCase +import pytest class TestModelClassFunction(BaseCassEngTestCase): @@ -91,7 +92,7 @@ def test_attempting_to_make_duplicate_column_names_fails(self): Tests that trying to create conflicting db column names will fail """ - with self.assertRaisesRegex(ModelException, r".*more than once$"): + with pytest.raises(ModelException, match=r".*more than once$"): class BadNames(Model): words = columns.Text(primary_key=True) content = columns.Text(db_field='words') @@ -111,7 +112,7 @@ class Stuff(Model): assert [x for x in Stuff._columns.keys()] == ['id', 'words', 'content', 'numbers'] def test_exception_raised_when_creating_class_without_pk(self): - with self.assertRaises(ModelDefinitionException): + with pytest.raises(ModelDefinitionException): class TestModel(Model): count = columns.Integer() @@ -191,7 +192,7 @@ class DelModel(Model): model = DelModel(key=4, data=5) del model.data - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): del model.key def test_does_not_exist_exceptions_are_not_shared_between_model(self): @@ -339,18 +340,18 @@ def test_abstract_attribute_is_not_inherited(self): def test_attempting_to_save_abstract_model_fails(self): """ Attempting to save a model from an abstract model should fail """ - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): AbstractModelWithFullCols.create(pkey=1, data=2) def test_attempting_to_create_abstract_table_fails(self): """ Attempting to create a table from an abstract model should fail """ from cassandra.cqlengine.management import sync_table - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): sync_table(AbstractModelWithFullCols) def test_attempting_query_on_abstract_model_fails(self): """ Tests attempting to execute query with an abstract model fails """ - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): iter(AbstractModelWithFullCols.objects(pkey=5)).next() def test_abstract_columns_are_inherited(self): @@ -395,7 +396,7 @@ class CQModel(Model): part = columns.UUID(primary_key=True) data = columns.Text() - with self.assertRaises(self.TestException): + with pytest.raises(self.TestException): CQModel.create(part=uuid4(), data='s') def test_overriding_dmlqueryset(self): @@ -410,7 +411,7 @@ class CDQModel(Model): part = columns.UUID(primary_key=True) data = columns.Text() - with self.assertRaises(self.TestException): + with pytest.raises(self.TestException): CDQModel().save() diff --git a/tests/integration/cqlengine/model/test_model.py b/tests/integration/cqlengine/model/test_model.py index 420a5c88c6..cafe6ae9c9 100644 --- a/tests/integration/cqlengine/model/test_model.py +++ b/tests/integration/cqlengine/model/test_model.py @@ -22,6 +22,7 @@ from uuid import uuid1 from tests.integration import pypy from tests.integration.cqlengine.base import TestQueryUpdateModel +import pytest class TestModel(unittest.TestCase): """ Tests the non-io functionality of models """ @@ -121,7 +122,8 @@ class TestModel(Model): # neither set should raise CQLEngineException before failing or formatting an invalid name del TestModel.__keyspace__ with patch('cassandra.cqlengine.models.DEFAULT_KEYSPACE', None): - self.assertRaises(CQLEngineException, TestModel.column_family_name) + with pytest.raises(CQLEngineException): + TestModel.column_family_name() # .. but we can still get the bare CF name assert TestModel.column_family_name(include_keyspace=False) == "test_model" @@ -148,7 +150,8 @@ class TestModel(Model): del TestModel.__keyspace__ with patch('cassandra.cqlengine.models.DEFAULT_KEYSPACE', None): - self.assertRaises(CQLEngineException, TestModel.column_family_name) + with pytest.raises(CQLEngineException): + TestModel.column_family_name() assert TestModel.column_family_name(include_keyspace=False) == '"TestModel"' @@ -157,7 +160,7 @@ class BuiltInAttributeConflictTest(unittest.TestCase): def test_model_with_attribute_name_conflict(self): """should raise exception when model defines column that conflicts with built-in attribute""" - with self.assertRaises(ModelDefinitionException): + with pytest.raises(ModelDefinitionException): class IllegalTimestampColumnModel(Model): my_primary_key = columns.Integer(primary_key=True) @@ -165,7 +168,7 @@ class IllegalTimestampColumnModel(Model): def test_model_with_method_name_conflict(self): """should raise exception when model defines column that conflicts with built-in method""" - with self.assertRaises(ModelDefinitionException): + with pytest.raises(ModelDefinitionException): class IllegalFilterColumnModel(Model): my_primary_key = columns.Integer(primary_key=True) diff --git a/tests/integration/cqlengine/model/test_polymorphism.py b/tests/integration/cqlengine/model/test_polymorphism.py index e59ff86d09..a37b499df6 100644 --- a/tests/integration/cqlengine/model/test_polymorphism.py +++ b/tests/integration/cqlengine/model/test_polymorphism.py @@ -20,20 +20,21 @@ from cassandra.cqlengine.connection import get_session from tests.integration.cqlengine.base import BaseCassEngTestCase from cassandra.cqlengine import management +import pytest class TestInheritanceClassConstruction(BaseCassEngTestCase): def test_multiple_discriminator_value_failure(self): """ Tests that defining a model with more than one discriminator column fails """ - with self.assertRaises(models.ModelDefinitionException): + with pytest.raises(models.ModelDefinitionException): class M(models.Model): partition = columns.Integer(primary_key=True) type1 = columns.Integer(discriminator_column=True) type2 = columns.Integer(discriminator_column=True) def test_no_discriminator_column_failure(self): - with self.assertRaises(models.ModelDefinitionException): + with pytest.raises(models.ModelDefinitionException): class M(models.Model): __discriminator_value__ = 1 @@ -86,7 +87,7 @@ class M1(Base): assert Base.column_family_name() == M1.column_family_name() def test_collection_columns_cant_be_discriminator_column(self): - with self.assertRaises(models.ModelDefinitionException): + with pytest.raises(models.ModelDefinitionException): class Base(models.Model): partition = columns.Integer(primary_key=True) @@ -124,7 +125,7 @@ def tearDownClass(cls): management.drop_table(Inherit2) def test_saving_base_model_fails(self): - with self.assertRaises(models.PolymorphicModelException): + with pytest.raises(models.PolymorphicModelException): InheritBase.create() def test_saving_subclass_saves_disc_value(self): @@ -210,9 +211,9 @@ def test_subclassed_model_results_work_properly(self): assert len(list(UnindexedInherit2.objects(partition=p1.partition, cluster__in=[p2.cluster, p3.cluster]))) == 2 def test_conflicting_type_results(self): - with self.assertRaises(models.PolymorphicModelException): + with pytest.raises(models.PolymorphicModelException): list(UnindexedInherit1.objects(partition=self.p1.partition)) - with self.assertRaises(models.PolymorphicModelException): + with pytest.raises(models.PolymorphicModelException): list(UnindexedInherit2.objects(partition=self.p1.partition)) diff --git a/tests/integration/cqlengine/model/test_udts.py b/tests/integration/cqlengine/model/test_udts.py index 22452ba720..80f1b9693f 100644 --- a/tests/integration/cqlengine/model/test_udts.py +++ b/tests/integration/cqlengine/model/test_udts.py @@ -28,6 +28,7 @@ from tests.integration import PROTOCOL_VERSION from tests.integration.cqlengine.base import BaseCassEngTestCase from tests.integration.cqlengine import DEFAULT_KEYSPACE +import pytest class User(UserType): @@ -99,7 +100,7 @@ class User(UserType): sync_type(DEFAULT_KEYSPACE, User) user = User(age=42, name="John", gender="male") - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): user.gender def test_can_insert_udts(self): @@ -478,12 +479,12 @@ def test_db_field_overload(self): @test_category data_types:udt """ - with self.assertRaises(UserTypeDefinitionException): + with pytest.raises(UserTypeDefinitionException): class something_silly(UserType): first_col = columns.Integer() second_col = columns.Text(db_field='first_col') - with self.assertRaises(UserTypeDefinitionException): + with pytest.raises(UserTypeDefinitionException): class something_silly_2(UserType): first_col = columns.Integer(db_field="second_col") second_col = columns.Text() @@ -556,7 +557,7 @@ class UserModelValidate(Model): user = UserValidate(age=1, name="Robert") item = UserModelValidate(id=1, info=user) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): item.save() def test_udt_validate_with_default(self): @@ -584,5 +585,5 @@ class UserModelValidateDefault(Model): user = UserValidateDefault(age=1) item = UserModelValidateDefault(id=1, info=user) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): item.save() diff --git a/tests/integration/cqlengine/model/test_updates.py b/tests/integration/cqlengine/model/test_updates.py index 836e0eef99..c64df8fdcc 100644 --- a/tests/integration/cqlengine/model/test_updates.py +++ b/tests/integration/cqlengine/model/test_updates.py @@ -23,6 +23,7 @@ from cassandra.cqlengine import columns from cassandra.cqlengine.management import sync_table, drop_table from cassandra.cqlengine.usertype import UserType +import pytest class TestUpdateModel(Model): __test__ = False @@ -67,11 +68,11 @@ def test_update_model(self): m0.update(partition=m0.partition, cluster=m0.cluster) #Assert a ValidationError is risen if the PR changes - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): m0.update(partition=m0.partition, cluster=20) # Assert a ValidationError is risen if the columns doesn't exist - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): m0.update(invalid_column=20) def test_update_values(self): @@ -139,13 +140,13 @@ def test_noop_model_assignation_update(self): def test_invalid_update_kwarg(self): """ tests that passing in a kwarg to the update method that isn't a column will fail """ m0 = TestUpdateModel.create(count=5, text='monkey') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): m0.update(numbers=20) def test_primary_key_update_failure(self): """ tests that attempting to update the value of a primary key will fail """ m0 = TestUpdateModel.create(count=5, text='monkey') - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): m0.update(partition=uuid4()) diff --git a/tests/integration/cqlengine/operators/test_where_operators.py b/tests/integration/cqlengine/operators/test_where_operators.py index 4297e63d43..c7e30b8905 100644 --- a/tests/integration/cqlengine/operators/test_where_operators.py +++ b/tests/integration/cqlengine/operators/test_where_operators.py @@ -26,6 +26,7 @@ from tests.integration.cqlengine.base import TestQueryUpdateModel, BaseCassEngTestCase from tests.integration.cqlengine.operators import check_lookup from tests.integration import greaterthanorequalcass30 +import pytest class TestWhereOperators(unittest.TestCase): @@ -96,8 +97,8 @@ def test_is_not_null_execution(self): self.addCleanup(drop_table, TestQueryUpdateModel) # Raises InvalidRequest instead of dse.protocol.SyntaxException - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): list(TestQueryUpdateModel.filter(IsNotNull("text"))) - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): list(TestQueryUpdateModel.filter(IsNotNull("text"), partition=uuid4())) diff --git a/tests/integration/cqlengine/query/test_batch_query.py b/tests/integration/cqlengine/query/test_batch_query.py index 86bd57fb6b..512a580154 100644 --- a/tests/integration/cqlengine/query/test_batch_query.py +++ b/tests/integration/cqlengine/query/test_batch_query.py @@ -24,6 +24,7 @@ from cassandra.cluster import Session from cassandra.query import BatchType as cassandra_BatchType from cassandra.cqlengine.query import BatchType as cqlengine_BatchType +import pytest class TestMultiKeyModel(Model): @@ -70,7 +71,7 @@ def test_insert_success_case(self): b = BatchQuery() inst = TestMultiKeyModel.batch(b).create(partition=self.pkey, cluster=2, count=3, text='4') - with self.assertRaises(TestMultiKeyModel.DoesNotExist): + with pytest.raises(TestMultiKeyModel.DoesNotExist): TestMultiKeyModel.get(partition=self.pkey, cluster=2) b.execute() @@ -108,7 +109,7 @@ def test_delete_success_case(self): b.execute() - with self.assertRaises(TestMultiKeyModel.DoesNotExist): + with pytest.raises(TestMultiKeyModel.DoesNotExist): TestMultiKeyModel.get(partition=self.pkey, cluster=2) @execute_count(11) @@ -119,7 +120,7 @@ def test_context_manager(self): TestMultiKeyModel.batch(b).create(partition=self.pkey, cluster=i, count=3, text='4') for i in range(5): - with self.assertRaises(TestMultiKeyModel.DoesNotExist): + with pytest.raises(TestMultiKeyModel.DoesNotExist): TestMultiKeyModel.get(partition=self.pkey, cluster=i) for i in range(5): diff --git a/tests/integration/cqlengine/query/test_named.py b/tests/integration/cqlengine/query/test_named.py index a2f890600b..24a6802b47 100644 --- a/tests/integration/cqlengine/query/test_named.py +++ b/tests/integration/cqlengine/query/test_named.py @@ -28,6 +28,7 @@ from tests.integration import BasicSharedKeyspaceUnitTestCase, greaterthanorequalcass30, requires_collection_indexes +import pytest class TestQuerySetOperation(BaseCassEngTestCase): @@ -265,7 +266,7 @@ def test_get_doesnotexist_exception(self): """ Tests that get calls that don't return a result raises a DoesNotExist error """ - with self.assertRaises(self.table.DoesNotExist): + with pytest.raises(self.table.DoesNotExist): self.table.objects.get(test_id=100) @execute_count(1) @@ -273,7 +274,7 @@ def test_get_multipleobjects_exception(self): """ Tests that get calls that return multiple results raise a MultipleObjectsReturned error """ - with self.assertRaises(self.table.MultipleObjectsReturned): + with pytest.raises(self.table.MultipleObjectsReturned): self.table.objects.get(test_id=1) diff --git a/tests/integration/cqlengine/query/test_queryoperators.py b/tests/integration/cqlengine/query/test_queryoperators.py index 05c6f7c3cf..b9e9356b06 100644 --- a/tests/integration/cqlengine/query/test_queryoperators.py +++ b/tests/integration/cqlengine/query/test_queryoperators.py @@ -25,6 +25,7 @@ from tests.integration.cqlengine import DEFAULT_KEYSPACE from tests.integration.cqlengine.base import BaseCassEngTestCase from tests.integration.cqlengine import execute_count +import pytest class TestQuerySetOperation(BaseCassEngTestCase): @@ -124,15 +125,18 @@ class TestModel(Model): str(q._select_query()) # The 'pk__token' virtual column may only be compared to a Token - self.assertRaises(query.QueryException, TestModel.objects.filter, pk__token__gt=10) + with pytest.raises(query.QueryException): + TestModel.objects.filter(pk__token__gt=10) # A Token may only be compared to the `pk__token' virtual column func = functions.Token('a', 'b') - self.assertRaises(query.QueryException, TestModel.objects.filter, p1__gt=func) + with pytest.raises(query.QueryException): + TestModel.objects.filter(p1__gt=func) # The # of arguments to Token must match the # of partition keys func = functions.Token('a') - self.assertRaises(query.QueryException, TestModel.objects.filter, pk__token__gt=func) + with pytest.raises(query.QueryException): + TestModel.objects.filter(pk__token__gt=func) @execute_count(7) def test_named_table_pk_token_function(self): diff --git a/tests/integration/cqlengine/query/test_queryset.py b/tests/integration/cqlengine/query/test_queryset.py index dc28da7cca..34b4ab5964 100644 --- a/tests/integration/cqlengine/query/test_queryset.py +++ b/tests/integration/cqlengine/query/test_queryset.py @@ -41,6 +41,7 @@ from tests.integration import PROTOCOL_VERSION, CASSANDRA_VERSION, greaterthancass20, greaterthancass21, \ greaterthanorequalcass30, TestCluster, requires_collection_indexes from tests.integration.cqlengine import execute_count, DEFAULT_KEYSPACE +import pytest class TzOffset(tzinfo): @@ -157,21 +158,21 @@ def test_using_invalid_column_names_in_filter_kwargs_raises_error(self): """ Tests that using invalid or nonexistant column names for filter args raises an error """ - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): TestModel.objects(nonsense=5) def test_using_nonexistant_column_names_in_query_args_raises_error(self): """ Tests that using invalid or nonexistant columns for query args raises an error """ - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): TestModel.objects(TestModel.nonsense == 5) def test_using_non_query_operators_in_query_args_raises_error(self): """ Tests that providing query args that are not query operator instances raises an error """ - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): TestModel.objects(5) def test_queryset_is_immutable(self): @@ -240,11 +241,11 @@ def test_defining_only_fields(self): q = TestModel.objects.only(['attempt_id', 'description']) assert q._select_fields() == ['attempt_id', 'description'] - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): TestModel.objects.only(['nonexistent_field']) # Cannot define more than once only fields - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): TestModel.objects.only(['description']).only(['attempt_id']) # only with defer fields @@ -255,18 +256,18 @@ def test_defining_only_fields(self): # Eliminate all results confirm exception is thrown q = TestModel.objects.only(['description']) q = q.defer(['description']) - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): q._select_fields() q = TestModel.objects.filter(test_id=0).only(['test_id', 'attempt_id', 'description']) assert q._select_fields() == ['attempt_id', 'description'] # no fields to select - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): q = TestModel.objects.only(['test_id']).defer(['test_id']) q._select_fields() - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): q = TestModel.objects.filter(test_id=0).only(['test_id']) q._select_fields() @@ -286,7 +287,7 @@ def test_defining_defer_fields(self): q = TestModel.objects.defer(['attempt_id', 'description']) assert q._select_fields() == ['test_id', 'expected_result', 'test_result'] - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): TestModel.objects.defer(['nonexistent_field']) # defer more than one @@ -302,7 +303,7 @@ def test_defining_defer_fields(self): # Eliminate all results confirm exception is thrown q = TestModel.objects.defer(['description', 'attempt_id']) q = q.only(['description']) - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): q._select_fields() # implicit defer @@ -523,7 +524,7 @@ def test_get_doesnotexist_exception(self): """ Tests that get calls that don't return a result raises a DoesNotExist error """ - with self.assertRaises(TestModel.DoesNotExist): + with pytest.raises(TestModel.DoesNotExist): TestModel.objects.get(test_id=100) @execute_count(1) @@ -531,7 +532,7 @@ def test_get_multipleobjects_exception(self): """ Tests that get calls that return multiple results raise a MultipleObjectsReturned error """ - with self.assertRaises(TestModel.MultipleObjectsReturned): + with pytest.raises(TestModel.MultipleObjectsReturned): TestModel.objects.get(test_id=1) def test_allow_filtering_flag(self): @@ -580,7 +581,7 @@ def test_distinct_with_filter(self): @execute_count(1) def test_distinct_with_non_partition(self): - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): q = TestModel.objects.distinct(['description']).filter(test_id__in=[1, 2]) len(q) @@ -615,19 +616,19 @@ def test_order_by_success_case(self): def test_ordering_by_non_second_primary_keys_fail(self): # kwarg filtering - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): TestModel.objects(test_id=0).order_by('test_id') # kwarg filtering - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): TestModel.objects(TestModel.test_id == 0).order_by('test_id') def test_ordering_by_non_primary_keys_fails(self): - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): TestModel.objects(test_id=0).order_by('description') def test_ordering_on_indexed_columns_fails(self): - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): IndexedTestModel.objects(test_id=0).order_by('attempt_id') @execute_count(8) @@ -654,7 +655,7 @@ class TestQuerySetSlicing(BaseQuerySetUsage): @execute_count(1) def test_out_of_range_index_raises_error(self): q = TestModel.objects(test_id=0).order_by('attempt_id') - with self.assertRaises(IndexError): + with pytest.raises(IndexError): q[10] @execute_count(1) @@ -710,7 +711,7 @@ def test_primary_key_or_index_must_be_specified(self): """ Tests that queries that don't have an equals relation to a primary key or indexed field fail """ - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): q = TestModel.objects(test_result=25) list([i for i in q]) @@ -718,7 +719,7 @@ def test_primary_key_or_index_must_have_equal_relation_filter(self): """ Tests that queries that don't have non equal (>,<, etc) relation to a primary key or indexed field fail """ - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): q = TestModel.objects(test_id__gt=0) list([i for i in q]) @@ -754,27 +755,27 @@ def test_custom_indexed_field_can_be_queried(self): Tests that queries on an custom indexed field will work without any primary key relations specified """ - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): list(CustomIndexedTestModel.objects.filter(data='test')) # not custom indexed # It should return InvalidRequest if target an indexed columns - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): list(CustomIndexedTestModel.objects.filter(indexed='test', data='test')) # It should return InvalidRequest if target an indexed columns - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): list(CustomIndexedTestModel.objects.filter(description='test', data='test')) # equals operator, server error since there is no real index, but it passes - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): list(CustomIndexedTestModel.objects.filter(description='test')) - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): list(CustomIndexedTestModel.objects.filter(test_id=1, description='test')) # gte operator, server error since there is no real index, but it passes # this can't work with a secondary index - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): list(CustomIndexedTestModel.objects.filter(description__gte='test')) with TestCluster().connect() as session: @@ -805,12 +806,12 @@ def test_delete(self): def test_delete_without_partition_key(self): """ Tests that attempting to delete a model without defining a partition key fails """ - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): TestModel.objects(attempt_id=0).delete() def test_delete_without_any_where_args(self): """ Tests that attempting to delete a whole table without any arguments will fail """ - with self.assertRaises(query.QueryException): + with pytest.raises(query.QueryException): TestModel.objects(attempt_id=0).delete() @greaterthanorequalcass30 @@ -1029,13 +1030,13 @@ def test_kwarg_success_case(self): q = IndexedCollectionsTestModel.filter(test_map__contains=13) assert q.count() == 0 - with self.assertRaises(QueryException): + with pytest.raises(QueryException): q = IndexedCollectionsTestModel.filter(test_list_no_index__contains=1) assert q.count() == 0 - with self.assertRaises(QueryException): + with pytest.raises(QueryException): q = IndexedCollectionsTestModel.filter(test_set_no_index__contains=1) assert q.count() == 0 - with self.assertRaises(QueryException): + with pytest.raises(QueryException): q = IndexedCollectionsTestModel.filter(test_map_no_index__contains=1) assert q.count() == 0 @@ -1060,13 +1061,13 @@ def test_query_expression_success_case(self): q = IndexedCollectionsTestModel.filter(IndexedCollectionsTestModel.test_map.contains_(13)) assert q.count() == 0 - with self.assertRaises(QueryException): + with pytest.raises(QueryException): q = IndexedCollectionsTestModel.filter(IndexedCollectionsTestModel.test_map_no_index.contains_(1)) assert q.count() == 0 - with self.assertRaises(QueryException): + with pytest.raises(QueryException): q = IndexedCollectionsTestModel.filter(IndexedCollectionsTestModel.test_map_no_index.contains_(1)) assert q.count() == 0 - with self.assertRaises(QueryException): + with pytest.raises(QueryException): q = IndexedCollectionsTestModel.filter(IndexedCollectionsTestModel.test_map_no_index.contains_(1)) assert q.count() == 0 @@ -1157,13 +1158,13 @@ def test_none_timeout(self): def test_timeout_then_batch(self): b = query.BatchQuery() m = self.model.timeout(None) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): m.batch(b) def test_batch_then_timeout(self): b = query.BatchQuery() m = self.model.batch(b) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): m.timeout(0.5) @@ -1397,9 +1398,9 @@ def test_defaultFetchSize(self): assert len(TestModelSmall.objects.fetch_size(5101)) == 5100 assert len(TestModelSmall.objects.fetch_size(1)) == 5100 - with self.assertRaises(QueryException): + with pytest.raises(QueryException): TestModelSmall.objects.fetch_size(0) - with self.assertRaises(QueryException): + with pytest.raises(QueryException): TestModelSmall.objects.fetch_size(-1) diff --git a/tests/integration/cqlengine/query/test_updates.py b/tests/integration/cqlengine/query/test_updates.py index 5ffc2da6f5..cedde0cd7b 100644 --- a/tests/integration/cqlengine/query/test_updates.py +++ b/tests/integration/cqlengine/query/test_updates.py @@ -23,6 +23,7 @@ from tests.integration.cqlengine.base import BaseCassEngTestCase, TestQueryUpdateModel from tests.integration.cqlengine import execute_count from tests.integration import greaterthancass20 +import pytest class QueryUpdateTests(BaseCassEngTestCase): @@ -72,17 +73,17 @@ def test_update_values_validation(self): assert row.text == str(i) # perform update - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): TestQueryUpdateModel.objects(partition=partition, cluster=3).update(count='asdf') def test_invalid_update_kwarg(self): """ tests that passing in a kwarg to the update method that isn't a column will fail """ - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): TestQueryUpdateModel.objects(partition=uuid4(), cluster=3).update(bacon=5000) def test_primary_key_update_failure(self): """ tests that attempting to update the value of a primary key will fail """ - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): TestQueryUpdateModel.objects(partition=uuid4(), cluster=3).update(cluster=5000) @execute_count(8) @@ -280,7 +281,7 @@ def test_map_remove_rejects_non_sets(self): cluster=cluster, text_map={"foo": '1', "bar": '2'} ) - with self.assertRaises(ValidationError): + with pytest.raises(ValidationError): TestQueryUpdateModel.objects(partition=partition, cluster=cluster).update( text_map__remove=["bar"] ) diff --git a/tests/integration/cqlengine/statements/test_where_clause.py b/tests/integration/cqlengine/statements/test_where_clause.py index 2f2f3d9222..8ac2536a19 100644 --- a/tests/integration/cqlengine/statements/test_where_clause.py +++ b/tests/integration/cqlengine/statements/test_where_clause.py @@ -15,13 +15,14 @@ from cassandra.cqlengine.operators import EqualsOperator from cassandra.cqlengine.statements import StatementException, WhereClause +import pytest class TestWhereClause(unittest.TestCase): def test_operator_check(self): """ tests that creating a where statement with a non BaseWhereOperator object fails """ - with self.assertRaises(StatementException): + with pytest.raises(StatementException): WhereClause('a', 'b', 'c') def test_where_clause_rendering(self): diff --git a/tests/integration/cqlengine/test_batch_query.py b/tests/integration/cqlengine/test_batch_query.py index 6cf33c9ef9..cd6bf0fe93 100644 --- a/tests/integration/cqlengine/test_batch_query.py +++ b/tests/integration/cqlengine/test_batch_query.py @@ -57,7 +57,7 @@ def test_insert_success_case(self): b = BatchQuery() TestMultiKeyModel.batch(b).create(partition=self.pkey, cluster=2, count=3, text='4') - with self.assertRaises(TestMultiKeyModel.DoesNotExist): + with pytest.raises(TestMultiKeyModel.DoesNotExist): TestMultiKeyModel.get(partition=self.pkey, cluster=2) b.execute() @@ -93,7 +93,7 @@ def test_delete_success_case(self): b.execute() - with self.assertRaises(TestMultiKeyModel.DoesNotExist): + with pytest.raises(TestMultiKeyModel.DoesNotExist): TestMultiKeyModel.get(partition=self.pkey, cluster=2) def test_context_manager(self): @@ -103,7 +103,7 @@ def test_context_manager(self): TestMultiKeyModel.batch(b).create(partition=self.pkey, cluster=i, count=3, text='4') for i in range(5): - with self.assertRaises(TestMultiKeyModel.DoesNotExist): + with pytest.raises(TestMultiKeyModel.DoesNotExist): TestMultiKeyModel.get(partition=self.pkey, cluster=i) for i in range(5): @@ -185,7 +185,7 @@ def my_callback(*args, **kwargs): class SomeError(Exception): pass - with self.assertRaises(SomeError): + with pytest.raises(SomeError): with BatchQuery() as batch: batch.add_callback(my_callback) # this error bubbling up through context manager @@ -197,7 +197,7 @@ class SomeError(Exception): # but if execute ran, even with an error bubbling through # the callbacks also would have fired - with self.assertRaises(SomeError): + with pytest.raises(SomeError): with BatchQuery(execute_on_exception=True) as batch: batch.add_callback(my_callback) raise SomeError diff --git a/tests/integration/cqlengine/test_connections.py b/tests/integration/cqlengine/test_connections.py index 006e0f68fa..612255bdc5 100644 --- a/tests/integration/cqlengine/test_connections.py +++ b/tests/integration/cqlengine/test_connections.py @@ -23,6 +23,7 @@ from tests.integration.cqlengine.base import BaseCassEngTestCase from tests.integration.cqlengine.query import test_queryset from tests.integration import local, CASSANDRA_IP, TestCluster +import pytest class TestModel(Model): @@ -98,7 +99,7 @@ def test_context_connection_priority(self): # ContextQuery connection should have priority over default one with ContextQuery(TestModel, connection='fake_cluster') as tm: - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): tm.objects.create(partition=1, cluster=1) # Explicit connection should have priority over ContextQuery one @@ -110,7 +111,7 @@ def test_context_connection_priority(self): # No model connection and an invalid default connection with ContextQuery(TestModel) as tm: - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): tm.objects.create(partition=1, cluster=1) def test_context_connection_with_keyspace(self): @@ -126,7 +127,7 @@ def test_context_connection_with_keyspace(self): # ks2 doesn't exist with ContextQuery(TestModel, connection='cluster', keyspace='ks2') as tm: - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): tm.objects.create(partition=1, cluster=1) @@ -166,7 +167,7 @@ def test_create_drop_keyspace(self): """ # No connection (default is fake) - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): create_keyspace_simple(self.keyspaces[0], 1) # Explicit connections @@ -190,7 +191,7 @@ def test_create_drop_table(self): create_keyspace_simple(ks, 1, connections=self.conns) # No connection (default is fake) - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): sync_table(TestModel) # Explicit connections @@ -205,7 +206,7 @@ def test_create_drop_table(self): TestModel.__connection__ = None # No connection (default is fake) - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): drop_table(TestModel) # Model connection @@ -259,15 +260,15 @@ def test_connection_param_validation(self): """ cluster = TestCluster() session = cluster.connect() - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): conn.register_connection("bad_coonection1", session=session, consistency="not_null") - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): conn.register_connection("bad_coonection2", session=session, lazy_connect="not_null") - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): conn.register_connection("bad_coonection3", session=session, retry_connect="not_null") - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): conn.register_connection("bad_coonection4", session=session, cluster_options="not_null") - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): conn.register_connection("bad_coonection5", hosts="not_null", session=session) cluster.shutdown() @@ -318,7 +319,7 @@ def test_basic_batch_query(self): """ # No connection with a QuerySet (default is a fake one) - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): with BatchQuery() as b: TestModel.objects.batch(b).create(partition=1, cluster=1) @@ -332,7 +333,7 @@ def test_basic_batch_query(self): obj.__connection__ = None # No connection with a model (default is a fake one) - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): with BatchQuery() as b: obj.count = 2 obj.batch(b).save() @@ -357,7 +358,7 @@ def test_batch_query_different_connection(self): TestModel.__connection__ = 'cluster' AnotherTestModel.__connection__ = 'cluster2' - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): with BatchQuery() as b: TestModel.objects.batch(b).create(partition=1, cluster=1) AnotherTestModel.objects.batch(b).create(partition=1, cluster=1) @@ -380,7 +381,7 @@ def test_batch_query_different_connection(self): obj1.count = 4 obj2.count = 4 - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): with BatchQuery() as b: obj1.batch(b).save() obj2.batch(b).save() @@ -396,11 +397,11 @@ def test_batch_query_connection_override(self): @test_category object_mapper """ - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): with BatchQuery(connection='cluster') as b: TestModel.batch(b).using(connection='test').save() - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): with BatchQuery(connection='cluster') as b: TestModel.using(connection='test').batch(b).save() @@ -408,11 +409,11 @@ def test_batch_query_connection_override(self): obj1 = tm.objects.get(partition=1, cluster=1) obj1.__connection__ = None - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): with BatchQuery(connection='cluster') as b: obj1.using(connection='test').batch(b).save() - with self.assertRaises(CQLEngineException): + with pytest.raises(CQLEngineException): with BatchQuery(connection='cluster') as b: obj1.batch(b).using(connection='test').save() @@ -470,14 +471,14 @@ def test_keyspace(self): tm.objects.using(keyspace='ks2').create(partition=1, cluster=1) tm.objects.using(keyspace='ks2').create(partition=2, cluster=2) - with self.assertRaises(TestModel.DoesNotExist): + with pytest.raises(TestModel.DoesNotExist): tm.objects.get(partition=1, cluster=1) # default keyspace ks1 obj1 = tm.objects.using(keyspace='ks2').get(partition=1, cluster=1) obj1.count = 2 obj1.save() - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): TestModel.objects.using(keyspace='ks2').get(partition=1, cluster=1) obj2 = TestModel.objects.using(connection='cluster', keyspace='ks2').get(partition=1, cluster=1) @@ -489,7 +490,7 @@ def test_keyspace(self): assert obj3.count == 5 TestModel.objects(partition=2, cluster=2).using(connection='cluster', keyspace='ks2').delete() - with self.assertRaises(TestModel.DoesNotExist): + with pytest.raises(TestModel.DoesNotExist): TestModel.objects.using(connection='cluster', keyspace='ks2').get(partition=2, cluster=2) def test_connection(self): @@ -505,7 +506,7 @@ def test_connection(self): self._reset_data() # Model class - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): TestModel.objects.create(partition=1, cluster=1) TestModel.objects.using(connection='cluster').create(partition=1, cluster=1) @@ -518,7 +519,7 @@ def test_connection(self): assert obj1.count == 5 obj1.using(connection='cluster').delete() - with self.assertRaises(TestModel.DoesNotExist): + with pytest.raises(TestModel.DoesNotExist): TestModel.objects.using(connection='cluster').get(partition=1, cluster=1) diff --git a/tests/integration/cqlengine/test_context_query.py b/tests/integration/cqlengine/test_context_query.py index 7d3c2512ce..a922806dcf 100644 --- a/tests/integration/cqlengine/test_context_query.py +++ b/tests/integration/cqlengine/test_context_query.py @@ -17,6 +17,7 @@ from cassandra.cqlengine.models import Model from cassandra.cqlengine.query import ContextQuery from tests.integration.cqlengine.base import BaseCassEngTestCase +import pytest class TestModel(Model): @@ -154,22 +155,22 @@ def test_context_invalid_parameters(self): @test_category query """ - with self.assertRaises(ValueError): + with pytest.raises(ValueError): with ContextQuery(keyspace='ks2'): pass - with self.assertRaises(ValueError): + with pytest.raises(ValueError): with ContextQuery(42) as tm: pass - with self.assertRaises(ValueError): + with pytest.raises(ValueError): with ContextQuery(TestModel, 42): pass - with self.assertRaises(ValueError): + with pytest.raises(ValueError): with ContextQuery(TestModel, unknown_param=42): pass - with self.assertRaises(ValueError): + with pytest.raises(ValueError): with ContextQuery(TestModel, keyspace='ks2', unknown_param=42): pass \ No newline at end of file diff --git a/tests/integration/cqlengine/test_ifexists.py b/tests/integration/cqlengine/test_ifexists.py index efe3b64a39..6c2ff437ab 100644 --- a/tests/integration/cqlengine/test_ifexists.py +++ b/tests/integration/cqlengine/test_ifexists.py @@ -22,6 +22,7 @@ from tests.integration.cqlengine.base import BaseCassEngTestCase from tests.integration import PROTOCOL_VERSION +import pytest class TestIfExistsModel(Model): @@ -104,16 +105,16 @@ def test_update_if_exists(self): assert m.text == 'changed_again' m = TestIfExistsModel(id=uuid4(), count=44) # do not exists - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException) as assertion: m.if_exists().update() - assert assertion.exception.existing.get('[applied]') == False + assert assertion.value.existing.get('[applied]') == False # queryset update - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException) as assertion: TestIfExistsModel.objects(id=uuid4()).if_exists().update(count=8) - assert assertion.exception.existing.get('[applied]') == False + assert assertion.value.existing.get('[applied]') == False @unittest.skipUnless(PROTOCOL_VERSION >= 2, "only runs against the cql3 protocol v2.0") def test_batch_update_if_exists_success(self): @@ -135,12 +136,12 @@ def test_batch_update_if_exists_success(self): m.text = '111111111' m.batch(b).if_exists().update() - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException) as assertion: with BatchQuery() as b: m = TestIfExistsModel(id=uuid4(), count=42) # Doesn't exist m.batch(b).if_exists().update() - assert assertion.exception.existing.get('[applied]') == False + assert assertion.value.existing.get('[applied]') == False q = TestIfExistsModel.objects(id=id) assert len(q) == 1 @@ -162,14 +163,14 @@ def test_batch_mixed_update_if_exists_success(self): """ m = TestIfExistsModel2.create(id=1, count=8, text='123456789') - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException) as assertion: with BatchQuery() as b: m.text = '111111112' m.batch(b).if_exists().update() # Does exist n = TestIfExistsModel2(id=1, count=10, text="Failure") # Doesn't exist n.batch(b).if_exists().update() - assert assertion.exception.existing.get('[applied]') == False + assert assertion.value.existing.get('[applied]') == False @unittest.skipUnless(PROTOCOL_VERSION >= 2, "only runs against the cql3 protocol v2.0") def test_delete_if_exists(self): @@ -191,16 +192,16 @@ def test_delete_if_exists(self): assert len(q) == 0 m = TestIfExistsModel(id=uuid4(), count=44) # do not exists - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException) as assertion: m.if_exists().delete() - assert assertion.exception.existing.get('[applied]') == False + assert assertion.value.existing.get('[applied]') == False # queryset delete - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException) as assertion: TestIfExistsModel.objects(id=uuid4()).if_exists().delete() - assert assertion.exception.existing.get('[applied]') == False + assert assertion.value.existing.get('[applied]') == False @unittest.skipUnless(PROTOCOL_VERSION >= 2, "only runs against the cql3 protocol v2.0") def test_batch_delete_if_exists_success(self): @@ -224,12 +225,12 @@ def test_batch_delete_if_exists_success(self): q = TestIfExistsModel.objects(id=id) assert len(q) == 0 - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException) as assertion: with BatchQuery() as b: m = TestIfExistsModel(id=uuid4(), count=42) # Doesn't exist m.batch(b).if_exists().delete() - assert assertion.exception.existing.get('[applied]') == False + assert assertion.value.existing.get('[applied]') == False @unittest.skipUnless(PROTOCOL_VERSION >= 2, "only runs against the cql3 protocol v2.0") def test_batch_delete_mixed(self): @@ -245,13 +246,13 @@ def test_batch_delete_mixed(self): m = TestIfExistsModel2.create(id=3, count=8, text='123456789') - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException) as assertion: with BatchQuery() as b: m.batch(b).if_exists().delete() # Does exist n = TestIfExistsModel2(id=3, count=42, text='1111111') # Doesn't exist n.batch(b).if_exists().delete() - assert assertion.exception.existing.get('[applied]') == False + assert assertion.value.existing.get('[applied]') == False q = TestIfExistsModel2.objects(id=3, count=8) assert len(q) == 1 @@ -298,6 +299,5 @@ def test_instance_raise_exception(self): @test_category object_mapper """ id = uuid4() - with self.assertRaises(IfExistsWithCounterColumn): + with pytest.raises(IfExistsWithCounterColumn): TestIfExistsWithCounterModel.if_exists() - diff --git a/tests/integration/cqlengine/test_ifnotexists.py b/tests/integration/cqlengine/test_ifnotexists.py index 9c41f37802..6a1dd9d4bc 100644 --- a/tests/integration/cqlengine/test_ifnotexists.py +++ b/tests/integration/cqlengine/test_ifnotexists.py @@ -22,6 +22,7 @@ from tests.integration.cqlengine.base import BaseCassEngTestCase from tests.integration import PROTOCOL_VERSION +import pytest class TestIfNotExistsModel(Model): __test__ = False @@ -80,13 +81,13 @@ def test_insert_if_not_exists(self): TestIfNotExistsModel.create(id=id, count=8, text='123456789') - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException): TestIfNotExistsModel.if_not_exists().create(id=id, count=9, text='111111111111') - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException) as assertion: TestIfNotExistsModel.objects(count=9, text='111111111111').if_not_exists().create(id=id) - assert assertion.exception.existing == { + assert assertion.value.existing == { 'count': 8, 'id': id, 'text': '123456789', @@ -111,10 +112,10 @@ def test_batch_insert_if_not_exists(self): b = BatchQuery() TestIfNotExistsModel.batch(b).if_not_exists().create(id=id, count=9, text='111111111111') - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException) as assertion: b.execute() - assert assertion.exception.existing == { + assert assertion.value.existing == { 'count': 8, 'id': id, 'text': '123456789', @@ -198,6 +199,5 @@ def test_instance_raise_exception(self): if_not_exists on table with counter column """ id = uuid4() - with self.assertRaises(IfNotExistsWithCounterColumn): + with pytest.raises(IfNotExistsWithCounterColumn): TestIfNotExistsWithCounterModel.if_not_exists() - diff --git a/tests/integration/cqlengine/test_lwt_conditional.py b/tests/integration/cqlengine/test_lwt_conditional.py index 8aaf0ff44d..f8d9d01035 100644 --- a/tests/integration/cqlengine/test_lwt_conditional.py +++ b/tests/integration/cqlengine/test_lwt_conditional.py @@ -23,6 +23,7 @@ from tests.integration.cqlengine.base import BaseCassEngTestCase from tests.integration import greaterthancass20 +import pytest class TestConditionalModel(Model): @@ -79,10 +80,10 @@ def test_update_failure(self): t.text = 'new blah' t = t.iff(text='something wrong') - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException) as assertion: t.save() - assert assertion.exception.existing == { + assert assertion.value.existing == { 'text': 'blah blah', '[applied]': False, } @@ -103,10 +104,10 @@ def test_blind_update_fail(self): t.text = 'something else' uid = t.id qs = TestConditionalModel.objects(id=uid).iff(text='Not dis!') - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException) as assertion: qs.update(text='this will never work') - assert assertion.exception.existing == { + assert assertion.value.existing == { 'text': 'blah blah', '[applied]': False, } @@ -129,10 +130,10 @@ def test_batch_update_conditional(self): b = BatchQuery() updated.batch(b).iff(count=6).update(text='and another thing') - with self.assertRaises(LWTException) as assertion: + with pytest.raises(LWTException) as assertion: b.execute() - assert assertion.exception.existing == { + assert assertion.value.existing == { 'id': id, 'count': 5, '[applied]': False, @@ -155,7 +156,7 @@ def test_batch_update_conditional_several_rows(self): TestUpdateModel.batch(b).if_not_exists().create(partition=1, cluster=3, value=5, text='something else') # The response will be more than two rows because two of the inserts will fail - with self.assertRaises(LWTException): + with pytest.raises(LWTException): b.execute() first_row.delete() @@ -167,7 +168,7 @@ def test_delete_conditional(self): # DML path t = TestConditionalModel.if_not_exists().create(text='something', count=5) assert TestConditionalModel.objects(id=t.id).count() == 1 - with self.assertRaises(LWTException): + with pytest.raises(LWTException): t.iff(count=9999).delete() assert TestConditionalModel.objects(id=t.id).count() == 1 t.iff(count=5).delete() @@ -176,7 +177,7 @@ def test_delete_conditional(self): # QuerySet path t = TestConditionalModel.if_not_exists().create(text='something', count=5) assert TestConditionalModel.objects(id=t.id).count() == 1 - with self.assertRaises(LWTException): + with pytest.raises(LWTException): TestConditionalModel.objects(id=t.id).iff(count=9999).delete() assert TestConditionalModel.objects(id=t.id).count() == 1 TestConditionalModel.objects(id=t.id).iff(count=5).delete() @@ -196,7 +197,7 @@ def test_delete_lwt_ne(self): # DML path t = TestConditionalModel.if_not_exists().create(text='something', count=5) assert TestConditionalModel.objects(id=t.id).count() == 1 - with self.assertRaises(LWTException): + with pytest.raises(LWTException): t.iff(count__ne=5).delete() t.iff(count__ne=2).delete() assert TestConditionalModel.objects(id=t.id).count() == 0 @@ -204,7 +205,7 @@ def test_delete_lwt_ne(self): # QuerySet path t = TestConditionalModel.if_not_exists().create(text='something', count=5) assert TestConditionalModel.objects(id=t.id).count() == 1 - with self.assertRaises(LWTException): + with pytest.raises(LWTException): TestConditionalModel.objects(id=t.id).iff(count__ne=5).delete() TestConditionalModel.objects(id=t.id).iff(count__ne=2).delete() assert TestConditionalModel.objects(id=t.id).count() == 0 @@ -223,7 +224,7 @@ def test_update_lwt_ne(self): # DML path t = TestConditionalModel.if_not_exists().create(text='something', count=5) assert TestConditionalModel.objects(id=t.id).count() == 1 - with self.assertRaises(LWTException): + with pytest.raises(LWTException): t.iff(count__ne=5).update(text='nothing') t.iff(count__ne=2).update(text='nothing') assert TestConditionalModel.objects(id=t.id).first().text == 'nothing' @@ -232,7 +233,7 @@ def test_update_lwt_ne(self): # QuerySet path t = TestConditionalModel.if_not_exists().create(text='something', count=5) assert TestConditionalModel.objects(id=t.id).count() == 1 - with self.assertRaises(LWTException): + with pytest.raises(LWTException): TestConditionalModel.objects(id=t.id).iff(count__ne=5).update(text='nothing') TestConditionalModel.objects(id=t.id).iff(count__ne=2).update(text='nothing') assert TestConditionalModel.objects(id=t.id).first().text == 'nothing' @@ -246,7 +247,7 @@ def test_update_to_none(self): # DML path t = TestConditionalModel.if_not_exists().create(text='something', count=5) assert TestConditionalModel.objects(id=t.id).count() == 1 - with self.assertRaises(LWTException): + with pytest.raises(LWTException): t.iff(count=9999).update(text=None) assert TestConditionalModel.objects(id=t.id).first().text is not None t.iff(count=5).update(text=None) @@ -255,7 +256,7 @@ def test_update_to_none(self): # QuerySet path t = TestConditionalModel.if_not_exists().create(text='something', count=5) assert TestConditionalModel.objects(id=t.id).count() == 1 - with self.assertRaises(LWTException): + with pytest.raises(LWTException): TestConditionalModel.objects(id=t.id).iff(count=9999).update(text=None) assert TestConditionalModel.objects(id=t.id).first().text is not None TestConditionalModel.objects(id=t.id).iff(count=5).update(text=None) diff --git a/tests/integration/cqlengine/test_timestamp.py b/tests/integration/cqlengine/test_timestamp.py index 3c57e20b7d..1b0e3d6b9d 100644 --- a/tests/integration/cqlengine/test_timestamp.py +++ b/tests/integration/cqlengine/test_timestamp.py @@ -22,6 +22,7 @@ from cassandra.cqlengine.models import Model from cassandra.cqlengine.query import BatchQuery from tests.integration.cqlengine.base import BaseCassEngTestCase +import pytest class TestTimestampModel(Model): @@ -135,12 +136,12 @@ def test_non_batch(self): tmp.timestamp(timedelta(seconds=5)).delete() - with self.assertRaises(TestTimestampModel.DoesNotExist): + with pytest.raises(TestTimestampModel.DoesNotExist): TestTimestampModel.get(id=uid) tmp = TestTimestampModel.create(id=uid, count=1) - with self.assertRaises(TestTimestampModel.DoesNotExist): + with pytest.raises(TestTimestampModel.DoesNotExist): TestTimestampModel.get(id=uid) # calling .timestamp sets the TS on the model @@ -166,12 +167,12 @@ def test_blind_delete(self): TestTimestampModel.objects(id=uid).timestamp(timedelta(seconds=5)).delete() - with self.assertRaises(TestTimestampModel.DoesNotExist): + with pytest.raises(TestTimestampModel.DoesNotExist): TestTimestampModel.get(id=uid) tmp = TestTimestampModel.create(id=uid, count=1) - with self.assertRaises(TestTimestampModel.DoesNotExist): + with pytest.raises(TestTimestampModel.DoesNotExist): TestTimestampModel.get(id=uid) def test_blind_delete_with_datetime(self): @@ -187,12 +188,12 @@ def test_blind_delete_with_datetime(self): TestTimestampModel.objects(id=uid).timestamp(plus_five_seconds).delete() - with self.assertRaises(TestTimestampModel.DoesNotExist): + with pytest.raises(TestTimestampModel.DoesNotExist): TestTimestampModel.get(id=uid) tmp = TestTimestampModel.create(id=uid, count=1) - with self.assertRaises(TestTimestampModel.DoesNotExist): + with pytest.raises(TestTimestampModel.DoesNotExist): TestTimestampModel.get(id=uid) def test_delete_in_the_past(self): diff --git a/tests/integration/long/test_failure_types.py b/tests/integration/long/test_failure_types.py index 7edb2e2790..33d2c99130 100644 --- a/tests/integration/long/test_failure_types.py +++ b/tests/integration/long/test_failure_types.py @@ -33,6 +33,7 @@ local, CASSANDRA_VERSION, TestCluster) import unittest +import pytest log = logging.getLogger(__name__) @@ -156,13 +157,13 @@ def _perform_cql_statement(self, text, consistency_level, expected_exception, se if expected_exception is None: self.execute_helper(session, statement) else: - with self.assertRaises(expected_exception) as cm: + with pytest.raises(expected_exception) as cm: self.execute_helper(session, statement) if ProtocolVersion.uses_error_code_map(PROTOCOL_VERSION): - if isinstance(cm.exception, ReadFailure): - assert list(cm.exception.error_code_map.values())[0] == 1 - if isinstance(cm.exception, WriteFailure): - assert list(cm.exception.error_code_map.values())[0] == 0 + if isinstance(cm.value, ReadFailure): + assert list(cm.value.error_code_map.values())[0] == 1 + if isinstance(cm.value, WriteFailure): + assert list(cm.value.error_code_map.values())[0] == 0 def test_write_failures_from_coordinator(self): """ @@ -379,7 +380,7 @@ def test_async_timeouts(self): # Test with default timeout (should be 10) start_time = time.time() future = self.session.execute_async(ss) - with self.assertRaises(OperationTimedOut): + with pytest.raises(OperationTimedOut): future.result() end_time = time.time() total_time = end_time-start_time @@ -396,7 +397,7 @@ def test_async_timeouts(self): future.add_callback(mock_callback) future.add_errback(mock_errorback) - with self.assertRaises(OperationTimedOut): + with pytest.raises(OperationTimedOut): future.result() end_time = time.time() total_time = end_time-start_time diff --git a/tests/integration/long/test_ipv6.py b/tests/integration/long/test_ipv6.py index c57987cc26..1d2c7b2874 100644 --- a/tests/integration/long/test_ipv6.py +++ b/tests/integration/long/test_ipv6.py @@ -38,6 +38,7 @@ import unittest +import pytest # If more modules do IPV6 testing, this can be moved down to integration.__init__. @@ -89,16 +90,17 @@ def test_connect(self): def test_error(self): cluster = TestCluster(connection_class=self.connection_class, contact_points=['::1'], port=9043, connect_timeout=10) - self.assertRaisesRegex(NoHostAvailable, '\(\'Unable to connect.*%s.*::1\', 9043.*Connection refused.*' - % errno.ECONNREFUSED, cluster.connect) + with pytest.raises(NoHostAvailable, match='\(\'Unable to connect.*%s.*::1\', 9043.*Connection refused.*' + % errno.ECONNREFUSED): + cluster.connect() def test_error_multiple(self): if len(socket.getaddrinfo('localhost', 9043, socket.AF_UNSPEC, socket.SOCK_STREAM)) < 2: raise unittest.SkipTest('localhost only resolves one address') cluster = TestCluster(connection_class=self.connection_class, contact_points=['localhost'], port=9043, connect_timeout=10) - self.assertRaisesRegex(NoHostAvailable, '\(\'Unable to connect.*Tried connecting to \[\(.*\(.*\].*Last error', - cluster.connect) + with pytest.raises(NoHostAvailable, match='\(\'Unable to connect.*Tried connecting to \[\(.*\(.*\].*Last error'): + cluster.connect() class LibevConnectionTests(IPV6ConnectionTest, unittest.TestCase): diff --git a/tests/integration/long/test_policies.py b/tests/integration/long/test_policies.py index 9936209e0d..ab8d125ab1 100644 --- a/tests/integration/long/test_policies.py +++ b/tests/integration/long/test_policies.py @@ -18,6 +18,7 @@ from cassandra.cluster import ExecutionProfile, EXEC_PROFILE_DEFAULT from tests.integration import use_cluster, get_cluster, get_node, TestCluster +import pytest def setup_module(): @@ -58,10 +59,10 @@ def test_should_rethrow_on_unvailable_with_default_policy_if_cas(self): # supported as conditional update commit consistency. ...."" # after fix: cassandra.Unavailable (expected since replicas are down) - with self.assertRaises(Unavailable) as cm: + with pytest.raises(Unavailable) as cm: session.execute("update test_retry_policy_cas.t set data = 'staging' where id = 42 if data ='testing'") - exception = cm.exception + exception = cm.value assert exception.consistency == ConsistencyLevel.SERIAL assert exception.required_replicas == 2 assert exception.alive_replicas == 1 diff --git a/tests/integration/long/test_ssl.py b/tests/integration/long/test_ssl.py index 070e2fe268..56dc6a5c2d 100644 --- a/tests/integration/long/test_ssl.py +++ b/tests/integration/long/test_ssl.py @@ -25,6 +25,7 @@ from tests.integration import ( get_cluster, remove_cluster, use_single_node, start_cluster_wait_for_up, EVENT_LOOP_MANAGER, TestCluster ) +import pytest if not hasattr(ssl, 'match_hostname'): try: @@ -290,7 +291,7 @@ def test_cannot_connect_without_client_auth(self): cluster = TestCluster(ssl_options={'ca_certs': CLIENT_CA_CERTS, 'ssl_version': ssl_version}) - with self.assertRaises(NoHostAvailable) as _: + with pytest.raises(NoHostAvailable): cluster.connect() cluster.shutdown() @@ -322,7 +323,7 @@ def test_cannot_connect_with_bad_client_auth(self): 'keyfile': DRIVER_KEYFILE} ) - with self.assertRaises(NoHostAvailable) as _: + with pytest.raises(NoHostAvailable): cluster.connect() cluster.shutdown() @@ -333,7 +334,7 @@ def test_cannot_connect_with_invalid_hostname(self): 'certfile': DRIVER_CERTFILE} ssl_options.update(verify_certs) - with self.assertRaises(Exception): + with pytest.raises(Exception): validate_ssl_options(ssl_options=ssl_options, hostname='localhost') @@ -487,7 +488,7 @@ def test_cannot_connect_ssl_context_with_invalid_hostname(self): ) ssl_context.verify_mode = ssl.CERT_REQUIRED ssl_options["check_hostname"] = True - with self.assertRaises(Exception): + with pytest.raises(Exception): validate_ssl_options(ssl_context=ssl_context, ssl_options=ssl_options, hostname="localhost") @unittest.skipIf(USES_PYOPENSSL, "This test is for the built-in ssl.Context") diff --git a/tests/integration/simulacron/test_backpressure.py b/tests/integration/simulacron/test_backpressure.py index 458151c25e..0b84f73e29 100644 --- a/tests/integration/simulacron/test_backpressure.py +++ b/tests/integration/simulacron/test_backpressure.py @@ -19,6 +19,7 @@ from tests.integration import requiressimulacron, libevtest from tests.integration.simulacron import SimulacronBase, PROTOCOL_VERSION from tests.integration.simulacron.utils import ResumeReads, PauseReads, prime_request, start_and_prime_singledc +import pytest @requiressimulacron @@ -146,9 +147,9 @@ def test_cluster_busy(self): # Now that our send buffer is completely full, verify we immediately get busy exceptions rather than timing out for i in range(1000): - with self.assertRaises(NoHostAvailable) as e: + with pytest.raises(NoHostAvailable) as e: session.execute(query, [str(i)]) - assert "ConnectionBusy" in str(e.exception) + assert "ConnectionBusy" in str(e.value) def test_node_busy(self): """ Verify that once TCP buffer is full, queries continue to get re-routed to other nodes """ @@ -176,4 +177,3 @@ def test_node_busy(self): # verify queries get re-routed to other nodes and queries complete successfully for i in range(1000): session.execute(query, [str(i)]) - diff --git a/tests/integration/simulacron/test_cluster.py b/tests/integration/simulacron/test_cluster.py index 0df1a4a35a..898734c416 100644 --- a/tests/integration/simulacron/test_cluster.py +++ b/tests/integration/simulacron/test_cluster.py @@ -24,6 +24,7 @@ from cassandra import (WriteTimeout, WriteType, ConsistencyLevel, UnresolvableContactPoints) from cassandra.cluster import Cluster, ControlConnection +import pytest PROTOCOL_VERSION = min(4, PROTOCOL_VERSION) @@ -48,9 +49,9 @@ def test_writetimeout(self): } prime_query(query_to_prime_simple, then=then, rows=None, column_types=None) - with self.assertRaises(WriteTimeout) as assert_raised_context: + with pytest.raises(WriteTimeout) as assert_raised_context: self.session.execute(query_to_prime_simple) - wt = assert_raised_context.exception + wt = assert_raised_context.value assert wt.write_type == WriteType.name_to_value[write_type] assert wt.consistency == ConsistencyLevel.name_to_value[consistency] assert wt.received_responses == received_responses @@ -77,7 +78,7 @@ def test_connection_with_one_unresolvable_contact_point(self): compression=False) def test_connection_with_only_unresolvable_contact_points(self): - with self.assertRaises(UnresolvableContactPoints): + with pytest.raises(UnresolvableContactPoints): self.cluster = Cluster(['dns.invalid'], protocol_version=PROTOCOL_VERSION, compression=False) diff --git a/tests/integration/simulacron/test_connection.py b/tests/integration/simulacron/test_connection.py index d09980c230..756bf3ac68 100644 --- a/tests/integration/simulacron/test_connection.py +++ b/tests/integration/simulacron/test_connection.py @@ -36,6 +36,7 @@ start_and_prime_singledc, clear_queries, RejectConnections, RejectType, AcceptConnections, PauseReads, ResumeReads) +import pytest class TrackDownListener(HostStateListener): @@ -181,7 +182,8 @@ def test_callbacks_and_pool_when_oto(self): future = session.execute_async(query_to_prime, timeout=1) callback, errback = Mock(name='callback'), Mock(name='errback') future.add_callbacks(callback, errback) - self.assertRaises(OperationTimedOut, future.result) + with pytest.raises(OperationTimedOut): + future.result() assert_quiescent_pool_state(self, cluster) @@ -261,7 +263,8 @@ def connection_factory(self, *args, **kwargs): prime_request(PrimeOptions(then={"result": "no_result", "delay_in_ms": never})) prime_request(RejectConnections("unbind")) - self.assertRaisesRegex(OperationTimedOut, "Connection defunct by heartbeat", future.result) + with pytest.raises(OperationTimedOut, match="Connection defunct by heartbeat"): + future.result() def test_close_when_query(self): """ @@ -289,7 +292,8 @@ def test_close_when_query(self): } prime_query(query_to_prime, rows=None, column_types=None, then=then) - self.assertRaises(NoHostAvailable, session.execute, query_to_prime) + with pytest.raises(NoHostAvailable): + session.execute(query_to_prime) def test_retry_after_defunct(self): """ @@ -463,7 +467,8 @@ def test_driver_recovers_nework_isolation(self): for host in cluster.metadata.all_hosts(): assert host in listener.hosts_marked_down - self.assertRaises(NoHostAvailable, session.execute, "SELECT * from system.local WHERE key='local'") + with pytest.raises(NoHostAvailable): + session.execute("SELECT * from system.local WHERE key='local'") clear_queries() prime_request(AcceptConnections()) diff --git a/tests/integration/simulacron/test_policies.py b/tests/integration/simulacron/test_policies.py index 45a76449c9..3f94a41222 100644 --- a/tests/integration/simulacron/test_policies.py +++ b/tests/integration/simulacron/test_policies.py @@ -27,6 +27,7 @@ from itertools import count from packaging.version import Version +import pytest class BadRoundRobinPolicy(RoundRobinPolicy): @@ -124,7 +125,7 @@ def test_speculative_execution(self): assert 1 == len(result.response_future.attempted_hosts) # Test timeout with spec_ex - with self.assertRaises(OperationTimedOut): + with pytest.raises(OperationTimedOut): self.session.execute(statement, execution_profile='spec_ep_rr', timeout=.5) prepared_query_to_prime = "SELECT * FROM test3rf.test where k = ?" @@ -306,7 +307,7 @@ def test_retry_policy_ignores_and_rethrows(self): then["write_type"] = "CDC" prime_query(query_to_prime_cdc, rows=None, column_types=None, then=then) - with self.assertRaises(WriteTimeout): + with pytest.raises(WriteTimeout): self.session.execute(query_to_prime_simple) #CDC should be ignored @@ -438,7 +439,7 @@ def test_retry_policy_on_request_error(self): prime_query(query_to_prime, then=prime_error, rows=None, column_types=None) rf = self.session.execute_async(query_to_prime) - with self.assertRaises(exc): + with pytest.raises(exc): rf.result() assert len(rf.attempted_hosts) == 1 # no retry @@ -455,7 +456,7 @@ def test_retry_policy_on_request_error(self): prime_query(query_to_prime, then=e, rows=None, column_types=None) rf = self.session.execute_async(query_to_prime) - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): rf.result() assert len(rf.attempted_hosts) == 3 # all 3 nodes failed diff --git a/tests/integration/standard/test_authentication.py b/tests/integration/standard/test_authentication.py index 75be6b0c9a..d40aa33852 100644 --- a/tests/integration/standard/test_authentication.py +++ b/tests/integration/standard/test_authentication.py @@ -24,6 +24,7 @@ from tests.integration.util import assert_quiescent_pool_state import unittest +import pytest log = logging.getLogger(__name__) @@ -121,9 +122,8 @@ def test_auth_connect(self): def test_connect_wrong_pwd(self): cluster = self.cluster_as('cassandra', 'wrong_pass') try: - self.assertRaisesRegex(NoHostAvailable, - '.*AuthenticationFailed.', - cluster.connect) + with pytest.raises(NoHostAvailable, match='.*AuthenticationFailed.'): + cluster.connect() assert_quiescent_pool_state(self, cluster) finally: cluster.shutdown() @@ -131,9 +131,8 @@ def test_connect_wrong_pwd(self): def test_connect_wrong_username(self): cluster = self.cluster_as('wrong_user', 'cassandra') try: - self.assertRaisesRegex(NoHostAvailable, - '.*AuthenticationFailed.*', - cluster.connect) + with pytest.raises(NoHostAvailable, match='.*AuthenticationFailed.*'): + cluster.connect() assert_quiescent_pool_state(self, cluster) finally: cluster.shutdown() @@ -141,9 +140,8 @@ def test_connect_wrong_username(self): def test_connect_empty_pwd(self): cluster = self.cluster_as('Cassandra', '') try: - self.assertRaisesRegex(NoHostAvailable, - '.*AuthenticationFailed.*', - cluster.connect) + with pytest.raises(NoHostAvailable, match='.*AuthenticationFailed.*'): + cluster.connect() assert_quiescent_pool_state(self, cluster) finally: cluster.shutdown() @@ -151,9 +149,8 @@ def test_connect_empty_pwd(self): def test_connect_no_auth_provider(self): cluster = TestCluster() try: - self.assertRaisesRegex(NoHostAvailable, - '.*AuthenticationFailed.*', - cluster.connect) + with pytest.raises(NoHostAvailable, match='.*AuthenticationFailed.*'): + cluster.connect() assert_quiescent_pool_state(self, cluster) finally: cluster.shutdown() @@ -188,4 +185,5 @@ def test_host_passthrough(self): def test_host_rejected(self): sasl_kwargs = {'host': 'something'} - self.assertRaises(ValueError, SaslAuthProvider, **sasl_kwargs) + with pytest.raises(ValueError): + SaslAuthProvider(**sasl_kwargs) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 6a98564b98..746dc8db38 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -152,7 +152,7 @@ def test_raise_error_on_control_connection_timeout(self): get_node(1).pause() cluster = TestCluster(contact_points=['127.0.0.1'], connect_timeout=1) - with self.assertRaisesRegex(NoHostAvailable, r"OperationTimedOut\('errors=Timed out creating connection \(1 seconds\)"): + with pytest.raises(NoHostAvailable, match=r"OperationTimedOut\('errors=Timed out creating connection \(1 seconds\)"): cluster.connect() cluster.shutdown() @@ -220,13 +220,13 @@ def cleanup(): # Test with empty list self.cluster_to_shutdown = TestCluster(contact_points=[]) - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): self.cluster_to_shutdown.connect() self.cluster_to_shutdown.shutdown() # Test with only invalid self.cluster_to_shutdown = TestCluster(contact_points=('1.2.3.4',)) - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): self.cluster_to_shutdown.connect() self.cluster_to_shutdown.shutdown() @@ -310,7 +310,7 @@ def test_invalid_protocol_negotation(self): log.debug('got upper_bound of {}'.format(upper_bound)) if upper_bound is not None: cluster = TestCluster(protocol_version=upper_bound) - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): cluster.connect() cluster.shutdown() @@ -318,7 +318,7 @@ def test_invalid_protocol_negotation(self): log.debug('got lower_bound of {}'.format(lower_bound)) if lower_bound is not None: cluster = TestCluster(protocol_version=lower_bound) - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): cluster.connect() cluster.shutdown() @@ -368,31 +368,37 @@ def test_connect_to_already_shutdown_cluster(self): """ cluster = TestCluster() cluster.shutdown() - self.assertRaises(Exception, cluster.connect) + with pytest.raises(Exception): + cluster.connect() def test_auth_provider_is_callable(self): """ Ensure that auth_providers are always callable """ - self.assertRaises(TypeError, Cluster, auth_provider=1, protocol_version=1) + with pytest.raises(TypeError): + Cluster(auth_provider=1, protocol_version=1) c = TestCluster(protocol_version=1) - self.assertRaises(TypeError, setattr, c, 'auth_provider', 1) + with pytest.raises(TypeError): + setattr(c, 'auth_provider', 1) def test_v2_auth_provider(self): """ Check for v2 auth_provider compliance """ bad_auth_provider = lambda x: {'username': 'foo', 'password': 'bar'} - self.assertRaises(TypeError, Cluster, auth_provider=bad_auth_provider, protocol_version=2) + with pytest.raises(TypeError): + Cluster(auth_provider=bad_auth_provider, protocol_version=2) c = TestCluster(protocol_version=2) - self.assertRaises(TypeError, setattr, c, 'auth_provider', bad_auth_provider) + with pytest.raises(TypeError): + setattr(c, 'auth_provider', bad_auth_provider) def test_conviction_policy_factory_is_callable(self): """ Ensure that conviction_policy_factory are always callable """ - self.assertRaises(ValueError, Cluster, conviction_policy_factory=1) + with pytest.raises(ValueError): + Cluster(conviction_policy_factory=1) def test_connect_to_bad_hosts(self): """ @@ -402,7 +408,8 @@ def test_connect_to_bad_hosts(self): cluster = TestCluster(contact_points=['127.1.2.9', '127.1.2.10'], protocol_version=PROTOCOL_VERSION) - self.assertRaises(NoHostAvailable, cluster.connect) + with pytest.raises(NoHostAvailable): + cluster.connect() def test_refresh_schema(self): cluster = TestCluster() @@ -505,7 +512,8 @@ def patched_wait_for_responses(*args, **kwargs): # cluster agreement wait used for refresh original_meta = c.metadata.keyspaces start_time = time.time() - self.assertRaisesRegex(Exception, r"Schema metadata was not refreshed.*", c.refresh_schema_metadata) + with pytest.raises(Exception, match=r"Schema metadata was not refreshed.*"): + c.refresh_schema_metadata() end_time = time.time() assert end_time - start_time >= agreement_timeout assert original_meta is c.metadata.keyspaces @@ -542,8 +550,8 @@ def patched_wait_for_responses(*args, **kwargs): # refresh wait overrides cluster value original_meta = c.metadata.keyspaces start_time = time.time() - self.assertRaisesRegex(Exception, r"Schema metadata was not refreshed.*", c.refresh_schema_metadata, - max_schema_agreement_wait=agreement_timeout) + with pytest.raises(Exception, match=r"Schema metadata was not refreshed.*"): + c.refresh_schema_metadata(max_schema_agreement_wait=agreement_timeout) end_time = time.time() assert end_time - start_time >= agreement_timeout assert original_meta is c.metadata.keyspaces @@ -876,7 +884,7 @@ def test_profile_load_balancing(self): assert queried_hosts == expected_hosts tuple_row = rs.one() assert isinstance(tuple_row, tuple) - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): tuple_row.release_version # make sure original profile is not impacted @@ -985,7 +993,7 @@ def test_missing_exec_prof(self): exec_profiles = {'rr1': rr1, 'rr2': rr2} with TestCluster(execution_profiles=exec_profiles) as cluster: session = cluster.connect() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): session.execute(query, execution_profile='rr3') @local @@ -1059,8 +1067,8 @@ def test_add_profile_timeout(self): start = time.time() try: - self.assertRaises(cassandra.OperationTimedOut, cluster.add_execution_profile, - 'profile_{0}'.format(i), + with pytest.raises(cassandra.OperationTimedOut): + cluster.add_execution_profile('profile_{0}'.format(i), node2, pool_wait_timeout=sys.float_info.min) break except AssertionError: @@ -1117,7 +1125,7 @@ def test_execute_query_timeout(self): for _ in range(max_retry_count): start = time.time() try: - with self.assertRaises(cassandra.OperationTimedOut): + with pytest.raises(cassandra.OperationTimedOut): session.execute(query, execution_profile=tmp_profile) break except: @@ -1488,7 +1496,7 @@ def test_invalid_protocol_version_beta_option(self): cluster = TestCluster(protocol_version=cassandra.ProtocolVersion.V6, allow_beta_protocol_version=False) try: - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): cluster.connect() except Exception as e: pytest.fail("Unexpected error encountered {0}".format(e.message)) diff --git a/tests/integration/standard/test_concurrent.py b/tests/integration/standard/test_concurrent.py index 4271004d55..e4bd379dee 100644 --- a/tests/integration/standard/test_concurrent.py +++ b/tests/integration/standard/test_concurrent.py @@ -25,6 +25,7 @@ from tests.integration import use_singledc, PROTOCOL_VERSION, TestCluster import unittest +import pytest log = logging.getLogger(__name__) @@ -175,7 +176,8 @@ def test_execute_concurrent_with_args_generator(self): for i in range(num_statements): result = next(results) assert (True, [(i,)]) == result - self.assertRaises(StopIteration, next, results) + with pytest.raises(StopIteration): + next(results) def test_execute_concurrent_paged_result(self): if PROTOCOL_VERSION < 2: @@ -259,9 +261,8 @@ def test_first_failure(self): # we'll get an error back from the server parameters[57] = ('efefef', 'awefawefawef') - self.assertRaises( - InvalidRequest, - execute_concurrent, self.session, list(zip(statements, parameters)), raise_on_first_error=True) + with pytest.raises(InvalidRequest): + execute_concurrent(self.session, list(zip(statements, parameters)), raise_on_first_error=True) def test_first_failure_client_side(self): statement = SimpleStatement( @@ -273,9 +274,8 @@ def test_first_failure_client_side(self): # the driver will raise an error when binding the params parameters[57] = 1 - self.assertRaises( - TypeError, - execute_concurrent, self.session, list(zip(statements, parameters)), raise_on_first_error=True) + with pytest.raises(TypeError): + execute_concurrent(self.session, list(zip(statements, parameters)), raise_on_first_error=True) def test_no_raise_on_first_failure(self): statement = SimpleStatement( diff --git a/tests/integration/standard/test_custom_cluster.py b/tests/integration/standard/test_custom_cluster.py index 3da6d176ad..4eb62e43bc 100644 --- a/tests/integration/standard/test_custom_cluster.py +++ b/tests/integration/standard/test_custom_cluster.py @@ -17,6 +17,7 @@ from tests.util import wait_until, wait_until_not_raised import unittest +import pytest def setup_module(): @@ -44,7 +45,7 @@ def test_connection_honor_cluster_port(self): All hosts should be marked as up and we should be able to execute queries on it. """ cluster = TestCluster() - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): cluster.connect() # should fail on port 9042 cluster = TestCluster(port=9046) diff --git a/tests/integration/standard/test_custom_payload.py b/tests/integration/standard/test_custom_payload.py index b77d4e336d..fc58081070 100644 --- a/tests/integration/standard/test_custom_payload.py +++ b/tests/integration/standard/test_custom_payload.py @@ -19,6 +19,7 @@ from tests.integration import (use_singledc, PROTOCOL_VERSION, local, TestCluster, requires_custom_payload) +import pytest def setup_module(): @@ -148,7 +149,7 @@ def validate_various_custom_payloads(self, statement): # Add one custom payload to this is too many key value pairs and should fail custom_payload[str(65535)] = b'x' - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.execute_async_validate_custom_payload(statement=statement, custom_payload=custom_payload) def execute_async_validate_custom_payload(self, statement, custom_payload): diff --git a/tests/integration/standard/test_custom_protocol_handler.py b/tests/integration/standard/test_custom_protocol_handler.py index d3fe96a669..a9025bba97 100644 --- a/tests/integration/standard/test_custom_protocol_handler.py +++ b/tests/integration/standard/test_custom_protocol_handler.py @@ -28,6 +28,7 @@ import uuid from unittest import mock +import pytest def setup_module(): @@ -145,9 +146,9 @@ def test_protocol_divergence_v5_fail_by_continuous_paging(self): continuous_paging_options=continuous_paging_options) # This should raise NoHostAvailable because continuous paging is not supported under ProtocolVersion.DSE_V1 - with self.assertRaises(NoHostAvailable) as context: + with pytest.raises(NoHostAvailable) as context: future.result() - assert "Continuous paging may only be used with protocol version ProtocolVersion.DSE_V1 or higher" in str(context.exception) + assert "Continuous paging may only be used with protocol version ProtocolVersion.DSE_V1 or higher" in str(context.value) cluster.shutdown() diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 581df5b9d0..c27d18dccb 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -1255,13 +1255,15 @@ def test_already_exists_exceptions(self): ddl = ''' CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'}''' - self.assertRaises(AlreadyExists, session.execute, ddl % ksname) + with pytest.raises(AlreadyExists): + session.execute(ddl % ksname) ddl = ''' CREATE TABLE %s.%s ( k int PRIMARY KEY, v int )''' - self.assertRaises(AlreadyExists, session.execute, ddl % (ksname, cfname)) + with pytest.raises(AlreadyExists): + session.execute(ddl % (ksname, cfname)) cluster.shutdown() @local diff --git a/tests/integration/standard/test_metrics.py b/tests/integration/standard/test_metrics.py index 5c26fb4ddd..8ccd278ee4 100644 --- a/tests/integration/standard/test_metrics.py +++ b/tests/integration/standard/test_metrics.py @@ -29,6 +29,7 @@ from tests.integration import BasicSharedKeyspaceUnitTestCaseRF3WM, BasicExistingKeyspaceUnitTestCase, local import pprint as pp +import pytest def setup_module(): @@ -70,7 +71,7 @@ def test_connection_error(self): # Ensure the nodes are actually down query = SimpleStatement("SELECT * FROM test", consistency_level=ConsistencyLevel.ALL) # both exceptions can happen depending on when the connection has been detected as defunct - with self.assertRaises((NoHostAvailable, ConnectionShutdown)): + with pytest.raises((NoHostAvailable, ConnectionShutdown)): self.session.execute(query) finally: get_cluster().start(wait_for_binary_proto=True, wait_other_notice=True) @@ -100,7 +101,7 @@ def test_write_timeout(self): try: # Test write query = SimpleStatement("INSERT INTO test (k, v) VALUES (2, 2)", consistency_level=ConsistencyLevel.ALL) - with self.assertRaises(WriteTimeout): + with pytest.raises(WriteTimeout): self.session.execute(query, timeout=None) assert 1 == self.cluster.metrics.stats.write_timeouts @@ -129,7 +130,7 @@ def test_read_timeout(self): try: # Test read query = SimpleStatement("SELECT * FROM test", consistency_level=ConsistencyLevel.ALL) - with self.assertRaises(ReadTimeout): + with pytest.raises(ReadTimeout): self.session.execute(query, timeout=None) assert 1 == self.cluster.metrics.stats.read_timeouts @@ -159,13 +160,13 @@ def test_unavailable(self): try: # Test write query = SimpleStatement("INSERT INTO test (k, v) VALUES (2, 2)", consistency_level=ConsistencyLevel.ALL) - with self.assertRaises(Unavailable): + with pytest.raises(Unavailable): self.session.execute(query) assert self.cluster.metrics.stats.unavailables == 1 # Test write query = SimpleStatement("SELECT * FROM test", consistency_level=ConsistencyLevel.ALL) - with self.assertRaises(Unavailable): + with pytest.raises(Unavailable): self.session.execute(query, timeout=None) assert self.cluster.metrics.stats.unavailables == 2 finally: @@ -217,7 +218,7 @@ def test_metrics_per_cluster(self): try: # Test write query = SimpleStatement("INSERT INTO {0}.{0} (k, v) VALUES (2, 2)".format(self.ks_name), consistency_level=ConsistencyLevel.ALL) - with self.assertRaises(WriteTimeout): + with pytest.raises(WriteTimeout): self.session.execute(query, timeout=None) finally: get_node(1).resume() @@ -269,7 +270,7 @@ def test_duplicate_metrics_per_cluster(self): # Ensure duplicate metric names are not allowed cluster2.metrics.set_stats_name("appcluster") cluster2.metrics.set_stats_name("appcluster") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): cluster3.metrics.set_stats_name("appcluster") cluster3.metrics.set_stats_name("devops") diff --git a/tests/integration/standard/test_prepared_statements.py b/tests/integration/standard/test_prepared_statements.py index cac8d40fcd..68a704cd77 100644 --- a/tests/integration/standard/test_prepared_statements.py +++ b/tests/integration/standard/test_prepared_statements.py @@ -27,6 +27,7 @@ BasicSharedKeyspaceUnitTestCase) import logging +import pytest LOG = logging.getLogger(__name__) @@ -133,12 +134,14 @@ def _run_missing_primary_key(self, session): statement_to_prepare = """INSERT INTO test3rf.test (v) VALUES (?)""" # logic needed work with changes in CASSANDRA-6237 if self.cass_version[0] >= (3, 0, 0): - self.assertRaises(InvalidRequest, session.prepare, statement_to_prepare) + with pytest.raises(InvalidRequest): + session.prepare(statement_to_prepare) else: prepared = session.prepare(statement_to_prepare) assert isinstance(prepared, PreparedStatement) bound = prepared.bind((1,)) - self.assertRaises(InvalidRequest, session.execute, bound) + with pytest.raises(InvalidRequest): + session.execute(bound) def test_missing_primary_key_dicts(self): """ @@ -152,12 +155,14 @@ def _run_missing_primary_key_dicts(self, session): statement_to_prepare = """ INSERT INTO test3rf.test (v) VALUES (?)""" # logic needed work with changes in CASSANDRA-6237 if self.cass_version[0] >= (3, 0, 0): - self.assertRaises(InvalidRequest, session.prepare, statement_to_prepare) + with pytest.raises(InvalidRequest): + session.prepare(statement_to_prepare) else: prepared = session.prepare(statement_to_prepare) assert isinstance(prepared, PreparedStatement) bound = prepared.bind({'v': 1}) - self.assertRaises(InvalidRequest, session.execute, bound) + with pytest.raises(InvalidRequest): + session.execute(bound) def test_too_many_bind_values(self): """ @@ -169,11 +174,13 @@ def _run_too_many_bind_values(self, session): statement_to_prepare = """ INSERT INTO test3rf.test (v) VALUES (?)""" # logic needed work with changes in CASSANDRA-6237 if self.cass_version[0] >= (2, 2, 8): - self.assertRaises(InvalidRequest, session.prepare, statement_to_prepare) + with pytest.raises(InvalidRequest): + session.prepare(statement_to_prepare) else: prepared = session.prepare(statement_to_prepare) assert isinstance(prepared, PreparedStatement) - self.assertRaises(ValueError, prepared.bind, (1, 2)) + with pytest.raises(ValueError): + prepared.bind((1, 2)) def test_imprecise_bind_values_dicts(self): """ @@ -194,7 +201,8 @@ def test_imprecise_bind_values_dicts(self): # right number, but one does not belong if PROTOCOL_VERSION < 4: # pre v4, the driver bails with key error when 'v' is found missing - self.assertRaises(KeyError, prepared.bind, {'k': 1, 'v2': 3}) + with pytest.raises(KeyError): + prepared.bind({'k': 1, 'v2': 3}) else: # post v4, the driver uses UNSET_VALUE for 'v' and 'v2' is ignored prepared.bind({'k': 1, 'v2': 3}) @@ -202,10 +210,12 @@ def test_imprecise_bind_values_dicts(self): # also catch too few variables with dicts assert isinstance(prepared, PreparedStatement) if PROTOCOL_VERSION < 4: - self.assertRaises(KeyError, prepared.bind, {}) + with pytest.raises(KeyError): + prepared.bind({}) else: # post v4, the driver attempts to use UNSET_VALUE for unspecified keys - self.assertRaises(ValueError, prepared.bind, {}) + with pytest.raises(ValueError): + prepared.bind({}) def test_none_values(self): """ @@ -274,7 +284,8 @@ def test_unset_values(self): results = self.session.execute(select, (0,)) assert results.one() == expected - self.assertRaises(ValueError, self.session.execute, select, (UNSET_VALUE, 0, 0)) + with pytest.raises(ValueError): + self.session.execute(select, (UNSET_VALUE, 0, 0)) def test_no_meta(self): @@ -392,7 +403,7 @@ def test_raise_error_on_prepared_statement_execution_dropped_table(self): prepared = self.session.prepare("SELECT * FROM test3rf.error_test WHERE k=?") self.session.execute("DROP TABLE test3rf.error_test") - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): self.session.execute(prepared, [0]) @unittest.skipIf((CASSANDRA_VERSION >= Version('3.11.12') and CASSANDRA_VERSION < Version('4.0')) or \ @@ -410,9 +421,9 @@ def test_fail_if_different_query_id_on_reprepare(self): self.session.execute("DROP TABLE {}.foo".format(keyspace)) self.session.execute("CREATE TABLE {}.foo(k int PRIMARY KEY)".format(keyspace)) self.session.execute("USE {}".format(keyspace)) - with self.assertRaises(DriverException) as e: + with pytest.raises(DriverException) as e: self.session.execute(prepared, [0]) - assert "ID mismatch" in str(e.exception) + assert "ID mismatch" in str(e.value) @greaterthanorequalcass40 @@ -546,7 +557,7 @@ def test_not_reprepare_invalid_statements(self): prepared_statement = self.session.prepare( "SELECT a, b, d FROM {} WHERE a = ?".format(self.table_name)) self.session.execute("ALTER TABLE {} DROP d".format(self.table_name)) - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): self.session.execute(prepared_statement.bind((1, ))) def test_id_is_not_updated_conditional_v4(self): diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 77f19cb8e0..a16a34233a 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -99,9 +99,9 @@ def test_row_error_message(self): self.session.execute("CREATE TABLE {0}.{1} (k int PRIMARY KEY, v timestamp)".format(self.keyspace_name,self.function_table_name)) ss = SimpleStatement("INSERT INTO {0}.{1} (k, v) VALUES (1, 1000000000000000)".format(self.keyspace_name, self.function_table_name)) self.session.execute(ss) - with self.assertRaises(DriverException) as context: + with pytest.raises(DriverException) as context: self.session.execute("SELECT * FROM {0}.{1}".format(self.keyspace_name, self.function_table_name)) - assert "Failed decoding result column" in str(context.exception) + assert "Failed decoding result column" in str(context.value) def test_trace_id_to_resultset(self): @@ -187,7 +187,7 @@ def test_trace_cl(self): statement = SimpleStatement(query) response_future = self.session.execute_async(statement, trace=True) response_future.result() - with self.assertRaises(Unavailable): + with pytest.raises(Unavailable): response_future.get_query_trace(query_cl=ConsistencyLevel.THREE) # Try again with a smattering of other CL's assert response_future.get_query_trace(max_wait=2.0, query_cl=ConsistencyLevel.TWO).trace_id is not None @@ -196,7 +196,7 @@ def test_trace_cl(self): assert response_future.get_query_trace(max_wait=2.0, query_cl=ConsistencyLevel.ONE).trace_id is not None response_future = self.session.execute_async(statement, trace=True) response_future.result() - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): assert response_future.get_query_trace(max_wait=2.0, query_cl=ConsistencyLevel.ANY).trace_id is not None assert response_future.get_query_trace(max_wait=2.0, query_cl=ConsistencyLevel.QUORUM).trace_id is not None @@ -233,7 +233,8 @@ def test_incomplete_query_trace(self): assert self._wait_for_trace_to_delete(trace.trace_id) # should raise because duration is not set - self.assertRaises(TraceUnavailable, trace.populate, max_wait=0.2, wait_for_complete=True) + with pytest.raises(TraceUnavailable): + trace.populate(max_wait=0.2, wait_for_complete=True) assert not trace.events # should get the events with wait False @@ -743,9 +744,12 @@ def test_no_parameters(self): batch.add("INSERT INTO test3rf.test (k, v) VALUES (8, 8)", ()) batch.add("INSERT INTO test3rf.test (k, v) VALUES (9, 9)", ()) - self.assertRaises(ValueError, batch.add, prepared.bind([]), (1)) - self.assertRaises(ValueError, batch.add, prepared.bind([]), (1, 2)) - self.assertRaises(ValueError, batch.add, prepared.bind([]), (1, 2, 3)) + with pytest.raises(ValueError): + batch.add(prepared.bind([]), (1)) + with pytest.raises(ValueError): + batch.add(prepared.bind([]), (1, 2)) + with pytest.raises(ValueError): + batch.add(prepared.bind([]), (1, 2, 3)) self.session.execute(batch) self.confirm_results() @@ -779,11 +783,13 @@ def test_too_many_statements(self): b = BatchStatement(batch_type=BatchType.UNLOGGED, consistency_level=ConsistencyLevel.ONE) # max + 1 raises b.add_all([ss] * max_statements, [None] * max_statements) - self.assertRaises(ValueError, b.add, ss) + with pytest.raises(ValueError): + b.add(ss) # also would have bombed trying to encode b._statements_and_parameters.append((False, ss.query_string, ())) - self.assertRaises(NoHostAvailable, self.session.execute, b) + with pytest.raises(NoHostAvailable): + self.session.execute(b) class SerialConsistencyTests(unittest.TestCase): @@ -869,8 +875,10 @@ def test_conditional_update_with_batch_statements(self): def test_bad_consistency_level(self): statement = SimpleStatement("foo") - self.assertRaises(ValueError, setattr, statement, 'serial_consistency_level', ConsistencyLevel.ONE) - self.assertRaises(ValueError, SimpleStatement, 'foo', serial_consistency_level=ConsistencyLevel.ONE) + with pytest.raises(ValueError): + setattr(statement, 'serial_consistency_level', ConsistencyLevel.ONE) + with pytest.raises(ValueError): + SimpleStatement('foo', serial_consistency_level=ConsistencyLevel.ONE) class LightweightTransactionTests(unittest.TestCase): @@ -1031,7 +1039,7 @@ def test_empty_batch_statement(self): """ batch_statement = BatchStatement() results = self.session.execute(batch_statement) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): results.was_applied @pytest.mark.xfail(reason='Skipping until PYTHON-943 is resolved') @@ -1469,7 +1477,7 @@ def test_lower_protocol(self): # set on queries with protocol version 5 or higher. Consider setting Cluster.protocol_version to 5.',), # : ConnectionException('Host has been marked down or removed',), # : ConnectionException('Host has been marked down or removed',)}) - with self.assertRaises(NoHostAvailable): + with pytest.raises(NoHostAvailable): session.execute(simple_stmt) def _check_set_keyspace_in_statement(self, session): diff --git a/tests/integration/standard/test_rate_limit_exceeded.py b/tests/integration/standard/test_rate_limit_exceeded.py index 2fa2953cb9..211f0c9930 100644 --- a/tests/integration/standard/test_rate_limit_exceeded.py +++ b/tests/integration/standard/test_rate_limit_exceeded.py @@ -5,6 +5,7 @@ from cassandra.policies import ConstantReconnectionPolicy, RoundRobinPolicy, TokenAwarePolicy from tests.integration import PROTOCOL_VERSION, use_cluster +import pytest LOGGER = logging.getLogger(__name__) @@ -31,17 +32,17 @@ def test_rate_limit_exceeded(self): ) self.session.execute( """ - CREATE KEYSPACE IF NOT EXISTS ratetests + CREATE KEYSPACE IF NOT EXISTS ratetests WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1} """) self.session.execute("USE ratetests") self.session.execute( """ - CREATE TABLE tbl (pk int PRIMARY KEY, v int) + CREATE TABLE tbl (pk int PRIMARY KEY, v int) WITH per_partition_rate_limit = {'max_writes_per_second': 1} """) - + prepared = self.session.prepare( """ INSERT INTO tbl (pk, v) VALUES (?, ?) @@ -53,7 +54,7 @@ def execute_write(): for _ in range(1000): self.session.execute(prepared.bind((123, 456))) - with self.assertRaises(RateLimitReached) as context: + with pytest.raises(RateLimitReached) as context: execute_write() - assert context.exception.op_type == OperationType.Write + assert context.value.op_type == OperationType.Write diff --git a/tests/integration/standard/test_types.py b/tests/integration/standard/test_types.py index 594ce37199..4ee9b70cde 100644 --- a/tests/integration/standard/test_types.py +++ b/tests/integration/standard/test_types.py @@ -43,6 +43,7 @@ greaterthanorequalcass3_10, TestCluster, requires_composite_type, greaterthanorequalcass50 from tests.integration.datatype_utils import update_datatypes, PRIMITIVE_DATATYPES, COLLECTION_TYPES, PRIMITIVE_DATATYPES_KEYS, \ get_sample, get_all_samples, get_collection_sample +import pytest def setup_module(): @@ -334,11 +335,11 @@ def test_can_insert_empty_strings_and_nulls(self): # non-string types shouldn't accept empty strings for col in non_string_columns: query = "INSERT INTO all_empty (zz, {0}) VALUES (4, %s)".format(col) - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): s.execute(query, ['']) insert = s.prepare("INSERT INTO all_empty (zz, {0}) VALUES (4, ?)".format(col)) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): s.execute(insert, ['']) # verify that Nones can be inserted and overwrites existing data @@ -470,7 +471,8 @@ def test_can_insert_tuples(self): s.execute(prepared, parameters=(5, subpartial)) # extra items in the tuple should result in an error - self.assertRaises(ValueError, s.execute, prepared, parameters=(0, (1, 2, 3, 4, 5, 6))) + with pytest.raises(ValueError): + s.execute(prepared, parameters=(0, (1, 2, 3, 4, 5, 6))) prepared = s.prepare("SELECT b FROM tuple_type WHERE a=?") assert complete == s.execute(prepared, (3,)).one().b @@ -508,7 +510,8 @@ def test_can_insert_tuples_with_varying_lengths(self): for i in lengths: # ensure tuples of larger sizes throw an error created_tuple = tuple(range(0, i + 1)) - self.assertRaises(InvalidRequest, s.execute, "INSERT INTO tuple_lengths (k, v_%s) VALUES (0, %s)", (i, created_tuple)) + with pytest.raises(InvalidRequest): + s.execute("INSERT INTO tuple_lengths (k, v_%s) VALUES (0, %s)", (i, created_tuple)) # ensure tuples of proper sizes are written and read correctly created_tuple = tuple(range(0, i)) @@ -730,9 +733,11 @@ def test_insert_collection_with_null_fails(self): s.execute(f'CREATE TABLE collection_nulls (k int PRIMARY KEY, {", ".join(columns)})') def raises_simple_and_prepared(exc_type, query_str, args): - self.assertRaises(exc_type, lambda: s.execute(query_str, args)) + with pytest.raises(exc_type): + s.execute(query_str, args) p = s.prepare(query_str.replace('%s', '?')) - self.assertRaises(exc_type, lambda: s.execute(p, args)) + with pytest.raises(exc_type): + s.execute(p, args) i = 0 for simple_type in PRIMITIVE_DATATYPES_KEYS: @@ -894,11 +899,14 @@ def test_smoke_duration_values(self): v = results.one()[1] assert Duration(month_day_value, month_day_value, nanosecond_value) == v, "Error encoding value {0},{0},{1}".format(month_day_value, nanosecond_value) - self.assertRaises(ValueError, self.session.execute, prepared, + with pytest.raises(ValueError): + self.session.execute(prepared, (1, Duration(0, 0, int("8FFFFFFFFFFFFFF0", 16)))) - self.assertRaises(ValueError, self.session.execute, prepared, + with pytest.raises(ValueError): + self.session.execute(prepared, (1, Duration(0, int("8FFFFFFFFFFFFFF0", 16), 0))) - self.assertRaises(ValueError, self.session.execute, prepared, + with pytest.raises(ValueError): + self.session.execute(prepared, (1, Duration(int("8FFFFFFFFFFFFFF0", 16), 0, 0))) class TypeTestsProtocol(BasicSharedKeyspaceUnitTestCase): diff --git a/tests/integration/standard/test_udts.py b/tests/integration/standard/test_udts.py index e20d02813d..dd696ea0e9 100644 --- a/tests/integration/standard/test_udts.py +++ b/tests/integration/standard/test_udts.py @@ -25,6 +25,7 @@ BasicSegregatedKeyspaceUnitTestCase, greaterthancass20, lessthancass30, greaterthanorequalcass36, TestCluster from tests.integration.datatype_utils import update_datatypes, PRIMITIVE_DATATYPES, PRIMITIVE_DATATYPES_KEYS, \ COLLECTION_TYPES, get_sample, get_collection_sample +import pytest nested_collection_udt = namedtuple('nested_collection_udt', ['m', 't', 'l', 's']) nested_collection_udt_nested = namedtuple('nested_collection_udt_nested', ['m', 't', 'l', 's', 'u']) @@ -482,13 +483,13 @@ def test_raise_error_on_nonexisting_udts(self): s = c.connect(self.keyspace_name, wait_for_all_pools=True) User = namedtuple('user', ('age', 'name')) - with self.assertRaises(UserTypeDoesNotExist): + with pytest.raises(UserTypeDoesNotExist): c.register_user_type("some_bad_keyspace", "user", User) - with self.assertRaises(UserTypeDoesNotExist): + with pytest.raises(UserTypeDoesNotExist): c.register_user_type("system", "user", User) - with self.assertRaises(InvalidRequest): + with pytest.raises(InvalidRequest): s.execute("CREATE TABLE mytable (a int PRIMARY KEY, b frozen)") c.shutdown() diff --git a/tests/integration/upgrade/test_upgrade.py b/tests/integration/upgrade/test_upgrade.py index 6a539db60d..fec9a38604 100644 --- a/tests/integration/upgrade/test_upgrade.py +++ b/tests/integration/upgrade/test_upgrade.py @@ -21,6 +21,7 @@ from tests.integration.upgrade import UpgradeBase, UpgradeBaseAuth, UpgradePath, upgrade_paths import unittest +import pytest # Previous Cassandra upgrade @@ -143,7 +144,7 @@ def test_schema_metadata_gets_refreshed(self): # Wait for the control connection to reconnect time.sleep(20) - with self.assertRaises(DriverException): + with pytest.raises(DriverException): self.cluster_driver.refresh_schema_metadata(max_schema_agreement_wait=10) self.upgrade_node(nodes[0]) diff --git a/tests/unit/advanced/test_geometry.py b/tests/unit/advanced/test_geometry.py index a6c69b7157..1927b51da7 100644 --- a/tests/unit/advanced/test_geometry.py +++ b/tests/unit/advanced/test_geometry.py @@ -20,6 +20,7 @@ from cassandra.protocol import ProtocolVersion from cassandra.cqltypes import PointType, LineStringType, PolygonType, WKBGeometryType from cassandra.util import Point, LineString, Polygon, _LinearRing, Distance, _HAS_GEOMET +import pytest wkb_be = 0 wkb_le = 1 @@ -132,10 +133,10 @@ def test_line_parse(self): # Test bad line strings bls = "LINESTRIN (1.0 2.0, 3.0 4.0, 5.0 6.0)" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): blo = LineString.from_wkt(bls) bls = "LINESTRING (1.0 2.0 3.0 4.0 5.0" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): blo = LineString.from_wkt(bls) # Test with NAN @@ -163,10 +164,10 @@ def test_distance_parse(self): # Test bad distance strings bds = "DISTANCE ((1.0 2.0))" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): bdo = Distance.from_wkt(bds) bps = "DISTANCE ((1.0 2.0 3.0 4.0 5.0)" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): bdo = Distance.from_wkt(bds) # NAN isn't supported, truncating not supported @@ -188,10 +189,10 @@ def test_point_parse(self): # Test bad point strings bps = "POIN (1.0 2.0)" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): bpo = Point.from_wkt(bps) bps = "POINT (1.0 2.0 3.0 4.0 5.0" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): bpo = Point.from_wkt(bps) # Points get truncated automatically @@ -235,10 +236,10 @@ def test_polygon_parse(self): # Test bad polygon strings bps = "POLYGONE ((30 10, 40 40, 20 40, 10 20, 30 10))" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): bpo = Polygon.from_wkt(bps) bps = "POLYGON (30 10, 40 40, 20 40, 10 20, 30 10)" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): bpo = Polygon.from_wkt(bps) # Polygons get truncated automatically diff --git a/tests/unit/advanced/test_graph.py b/tests/unit/advanced/test_graph.py index 696b26661c..5b82def245 100644 --- a/tests/unit/advanced/test_graph.py +++ b/tests/unit/advanced/test_graph.py @@ -24,6 +24,7 @@ Vertex, Edge, Path, VertexProperty) from cassandra.datastax.graph.query import _graph_options from tests.util import assertRegex +import pytest class GraphResultTests(unittest.TestCase): @@ -37,22 +38,22 @@ def test_result_value(self): def test_result_attr(self): # value is not a dict result = self._make_result(123) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): result.something expected = {'a': 1, 'b': 2} result = self._make_result(expected) assert result.a == 1 assert result.b == 2 - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): result.not_present def test_result_item(self): # value is not a dict, list result = self._make_result(123) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): result['something'] - with self.assertRaises(ValueError): + with pytest.raises(ValueError): result[0] # dict key access @@ -60,9 +61,9 @@ def test_result_item(self): result = self._make_result(expected) assert result['a'] == 1 assert result['b'] == 2 - with self.assertRaises(KeyError): + with pytest.raises(KeyError): result['not_present'] - with self.assertRaises(ValueError): + with pytest.raises(ValueError): result[0] # list index access @@ -70,9 +71,9 @@ def test_result_item(self): result = self._make_result(expected) assert result[0] == 0 assert result[1] == 1 - with self.assertRaises(IndexError): + with pytest.raises(IndexError): result[2] - with self.assertRaises(ValueError): + with pytest.raises(ValueError): result['something'] def test_as_vertex(self): @@ -101,14 +102,16 @@ def test_as_vertex(self): modified_vertex_dict = vertex_dict.copy() modified_vertex_dict['type'] = 'notavertex' result = self._make_result(modified_vertex_dict) - self.assertRaises(TypeError, result.as_vertex) + with pytest.raises(TypeError): + result.as_vertex() # missing required properties for attr in required_attrs: modified_vertex_dict = vertex_dict.copy() del modified_vertex_dict[attr] result = self._make_result(modified_vertex_dict) - self.assertRaises(TypeError, result.as_vertex) + with pytest.raises(TypeError): + result.as_vertex() def test_as_edge(self): prop_name = 'name' @@ -140,14 +143,16 @@ def test_as_edge(self): modified_edge_dict = edge_dict.copy() modified_edge_dict['type'] = 'notanedge' result = self._make_result(modified_edge_dict) - self.assertRaises(TypeError, result.as_edge) + with pytest.raises(TypeError): + result.as_edge() # missing required properties for attr in required_attrs: modified_edge_dict = edge_dict.copy() del modified_edge_dict[attr] result = self._make_result(modified_edge_dict) - self.assertRaises(TypeError, result.as_edge) + with pytest.raises(TypeError): + result.as_edge() def test_as_path(self): vertex_dict = {'id': object(), @@ -180,7 +185,8 @@ def test_as_path(self): modified_path_dict = path_dict.copy() del modified_path_dict[attr] result = self._make_result(modified_path_dict) - self.assertRaises(TypeError, result.as_path) + with pytest.raises(TypeError): + result.as_path() def test_str(self): for v in self._values: @@ -388,7 +394,8 @@ def test_init(self): # but not a bogus parameter kwargs['bogus'] = object() - self.assertRaises(TypeError, SimpleGraphStatement, **kwargs) + with pytest.raises(TypeError): + SimpleGraphStatement(**kwargs) class GraphRowFactoryTests(unittest.TestCase): diff --git a/tests/unit/column_encryption/test_policies.py b/tests/unit/column_encryption/test_policies.py index c3d8302bd5..1bd83ecf89 100644 --- a/tests/unit/column_encryption/test_policies.py +++ b/tests/unit/column_encryption/test_policies.py @@ -18,6 +18,7 @@ from cassandra.policies import ColDesc from cassandra.column_encryption.policies import AES256ColumnEncryptionPolicy, \ AES256_BLOCK_SIZE_BYTES, AES256_KEY_SIZE_BYTES +import pytest @unittest.skip("Skip until https://github.com/scylladb/python-driver/issues/365 is sorted out") class AES256ColumnEncryptionPolicyTest(unittest.TestCase): @@ -50,10 +51,10 @@ def test_add_column_invalid_key_size_raises(self): coldesc = ColDesc('ks1','table1','col1') policy = AES256ColumnEncryptionPolicy() for key_size in range(1,AES256_KEY_SIZE_BYTES - 1): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): policy.add_column(coldesc, os.urandom(key_size), "blob") for key_size in range(AES256_KEY_SIZE_BYTES + 1,(2 * AES256_KEY_SIZE_BYTES) - 1): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): policy.add_column(coldesc, os.urandom(key_size), "blob") def test_add_column_invalid_iv_size_raises(self): @@ -64,54 +65,54 @@ def test_iv_size(iv_size): coldesc = ColDesc('ks1','table1','col1') for iv_size in range(1,AES256_BLOCK_SIZE_BYTES - 1): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): test_iv_size(iv_size) for iv_size in range(AES256_BLOCK_SIZE_BYTES + 1,(2 * AES256_BLOCK_SIZE_BYTES) - 1): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): test_iv_size(iv_size) # Finally, confirm that the expected IV size has no issue test_iv_size(AES256_BLOCK_SIZE_BYTES) def test_add_column_null_coldesc_raises(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): policy = AES256ColumnEncryptionPolicy() policy.add_column(None, self._random_block(), "blob") def test_add_column_null_key_raises(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): policy = AES256ColumnEncryptionPolicy() coldesc = ColDesc('ks1','table1','col1') policy.add_column(coldesc, None, "blob") def test_add_column_null_type_raises(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): policy = AES256ColumnEncryptionPolicy() coldesc = ColDesc('ks1','table1','col1') policy.add_column(coldesc, self._random_block(), None) def test_add_column_unknown_type_raises(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): policy = AES256ColumnEncryptionPolicy() coldesc = ColDesc('ks1','table1','col1') policy.add_column(coldesc, self._random_block(), "foobar") def test_encode_and_encrypt_null_coldesc_raises(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): policy = AES256ColumnEncryptionPolicy() coldesc = ColDesc('ks1','table1','col1') policy.add_column(coldesc, self._random_key(), "blob") policy.encode_and_encrypt(None, self._random_block()) def test_encode_and_encrypt_null_obj_raises(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): policy = AES256ColumnEncryptionPolicy() coldesc = ColDesc('ks1','table1','col1') policy.add_column(coldesc, self._random_key(), "blob") policy.encode_and_encrypt(coldesc, None) def test_encode_and_encrypt_unknown_coldesc_raises(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): policy = AES256ColumnEncryptionPolicy() coldesc = ColDesc('ks1','table1','col1') policy.add_column(coldesc, self._random_key(), "blob") @@ -128,7 +129,7 @@ def test_contains_column(self): assert not policy.contains_column(ColDesc('ks2','table2','col2')) def test_encrypt_unknown_column(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): policy = AES256ColumnEncryptionPolicy() coldesc = ColDesc('ks1','table1','col1') policy.add_column(coldesc, self._random_key(), "blob") @@ -139,7 +140,7 @@ def test_decrypt_unknown_column(self): coldesc = ColDesc('ks1','table1','col1') policy.add_column(coldesc, self._random_key(), "blob") encrypted_bytes = policy.encrypt(coldesc, self._random_block()) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): policy.decrypt(ColDesc('ks2','table2','col2'), encrypted_bytes) def test_cache_info(self): diff --git a/tests/unit/cqlengine/test_connection.py b/tests/unit/cqlengine/test_connection.py index eca73531cf..9bce715f4e 100644 --- a/tests/unit/cqlengine/test_connection.py +++ b/tests/unit/cqlengine/test_connection.py @@ -18,6 +18,7 @@ from cassandra.cluster import _ConfigMode from cassandra.cqlengine import connection from cassandra.query import dict_factory +import pytest class ConnectionTest(unittest.TestCase): @@ -46,12 +47,12 @@ def test_get_session_fails_without_existing_connection(self): """ Users can't get the default session without having a default connection set. """ - with self.assertRaisesRegex(connection.CQLEngineException, self.no_registered_connection_msg): + with pytest.raises(connection.CQLEngineException, match=self.no_registered_connection_msg): connection.get_session(connection=None) def test_get_cluster_fails_without_existing_connection(self): """ Users can't get the default cluster without having a default connection set. """ - with self.assertRaisesRegex(connection.CQLEngineException, self.no_registered_connection_msg): + with pytest.raises(connection.CQLEngineException, match=self.no_registered_connection_msg): connection.get_cluster(connection=None) diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 7fcdd642e9..180f4b6a8a 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -27,6 +27,7 @@ from cassandra.query import SimpleStatement, named_tuple_factory, tuple_factory from tests.unit.utils import mock_session_pools from tests import connection_class +import pytest log = logging.getLogger(__name__) @@ -100,9 +101,9 @@ def test_tuple_for_contact_points(self): assert cp.port == 9999 def test_invalid_contact_point_types(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Cluster(contact_points=[None], protocol_version=4, connect_timeout=1) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): Cluster(contact_points="not a sequence", protocol_version=4, connect_timeout=1) def test_port_str(self): @@ -112,13 +113,13 @@ def test_port_str(self): if cp.address in ('::1', '127.0.0.1'): assert cp.port == 1111 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): cluster = Cluster(contact_points=['127.0.0.1'], port='string') def test_port_range(self): for invalid_port in [0, 65536, -1]: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): cluster = Cluster(contact_points=['127.0.0.1'], port=invalid_port) @@ -189,9 +190,9 @@ def test_default_serial_consistency_level_legacy(self, *_): assert s.default_serial_consistency_level is None # Should fail - with self.assertRaises(ValueError): + with pytest.raises(ValueError): s.default_serial_consistency_level = ConsistencyLevel.ANY - with self.assertRaises(ValueError): + with pytest.raises(ValueError): s.default_serial_consistency_level = 1001 for cl in (None, ConsistencyLevel.LOCAL_SERIAL, ConsistencyLevel.SERIAL): @@ -290,7 +291,7 @@ def test_default_profile(self): assert ep == session.get_execution_profile(name) # invalid ep - with self.assertRaises(ValueError): + with pytest.raises(ValueError): session.get_execution_profile('non-existent') def test_serial_consistency_level_validation(self): @@ -299,9 +300,9 @@ def test_serial_consistency_level_validation(self): ep = ExecutionProfile(RoundRobinPolicy(), serial_consistency_level=ConsistencyLevel.LOCAL_SERIAL) # should not pass - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ep = ExecutionProfile(RoundRobinPolicy(), serial_consistency_level=ConsistencyLevel.ANY) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ep = ExecutionProfile(RoundRobinPolicy(), serial_consistency_level=42) @mock_session_pools @@ -352,14 +353,18 @@ def test_statement_params_override_profile(self): @mock_session_pools def test_no_profile_with_legacy(self): # don't construct with both - self.assertRaises(ValueError, Cluster, load_balancing_policy=RoundRobinPolicy(), execution_profiles={'a': ExecutionProfile()}) - self.assertRaises(ValueError, Cluster, default_retry_policy=DowngradingConsistencyRetryPolicy(), execution_profiles={'a': ExecutionProfile()}) - self.assertRaises(ValueError, Cluster, load_balancing_policy=RoundRobinPolicy(), + with pytest.raises(ValueError): + Cluster(load_balancing_policy=RoundRobinPolicy(), execution_profiles={'a': ExecutionProfile()}) + with pytest.raises(ValueError): + Cluster(default_retry_policy=DowngradingConsistencyRetryPolicy(), execution_profiles={'a': ExecutionProfile()}) + with pytest.raises(ValueError): + Cluster(load_balancing_policy=RoundRobinPolicy(), default_retry_policy=DowngradingConsistencyRetryPolicy(), execution_profiles={'a': ExecutionProfile()}) # can't add after cluster = Cluster(load_balancing_policy=RoundRobinPolicy()) - self.assertRaises(ValueError, cluster.add_execution_profile, 'name', ExecutionProfile()) + with pytest.raises(ValueError): + cluster.add_execution_profile('name', ExecutionProfile()) # session settings lock out profiles cluster = Cluster() @@ -370,10 +375,12 @@ def test_no_profile_with_legacy(self): ('row_factory', tuple_factory)): cluster._config_mode = _ConfigMode.UNCOMMITTED setattr(session, attr, value) - self.assertRaises(ValueError, cluster.add_execution_profile, 'name' + attr, ExecutionProfile()) + with pytest.raises(ValueError): + cluster.add_execution_profile('name' + attr, ExecutionProfile()) # don't accept profile - self.assertRaises(ValueError, session.execute_async, "query", execution_profile='some name here') + with pytest.raises(ValueError): + session.execute_async("query", execution_profile='some name here') @mock_session_pools def test_no_legacy_with_profile(self): @@ -385,13 +392,15 @@ def test_no_legacy_with_profile(self): # don't allow legacy parameters set for attr, value in (('default_retry_policy', RetryPolicy()), ('load_balancing_policy', default_lbp_factory())): - self.assertRaises(ValueError, setattr, cluster, attr, value) + with pytest.raises(ValueError): + setattr(cluster, attr, value) session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) for attr, value in (('default_timeout', 1), ('default_consistency_level', ConsistencyLevel.ANY), ('default_serial_consistency_level', ConsistencyLevel.SERIAL), ('row_factory', tuple_factory)): - self.assertRaises(ValueError, setattr, session, attr, value) + with pytest.raises(ValueError): + setattr(session, attr, value) @mock_session_pools def test_profile_name_value(self): @@ -437,23 +446,27 @@ def test_exec_profile_clone(self): assert getattr(all_updated, attr) != getattr(active, attr) # cannot clone nonexistent profile - self.assertRaises(ValueError, session.execution_profile_clone_update, 'DOES NOT EXIST', **profile_attrs) + with pytest.raises(ValueError): + session.execution_profile_clone_update('DOES NOT EXIST', **profile_attrs) def test_no_profiles_same_name(self): # can override default in init cluster = Cluster(execution_profiles={EXEC_PROFILE_DEFAULT: ExecutionProfile(), 'one': ExecutionProfile()}) # cannot update default - self.assertRaises(ValueError, cluster.add_execution_profile, EXEC_PROFILE_DEFAULT, ExecutionProfile()) + with pytest.raises(ValueError): + cluster.add_execution_profile(EXEC_PROFILE_DEFAULT, ExecutionProfile()) # cannot update named init - self.assertRaises(ValueError, cluster.add_execution_profile, 'one', ExecutionProfile()) + with pytest.raises(ValueError): + cluster.add_execution_profile('one', ExecutionProfile()) # can add new name cluster.add_execution_profile('two', ExecutionProfile()) # cannot add a profile added dynamically - self.assertRaises(ValueError, cluster.add_execution_profile, 'two', ExecutionProfile()) + with pytest.raises(ValueError): + cluster.add_execution_profile('two', ExecutionProfile()) def test_warning_on_no_lbp_with_contact_points_legacy_mode(self): """ diff --git a/tests/unit/test_concurrent.py b/tests/unit/test_concurrent.py index 0d97fb2c62..a3587a3e16 100644 --- a/tests/unit/test_concurrent.py +++ b/tests/unit/test_concurrent.py @@ -28,6 +28,7 @@ from cassandra.pool import Host from cassandra.policies import SimpleConvictionPolicy from tests.unit.utils import mock_session_pools +import pytest class MockResponseResponseFuture(): @@ -248,7 +249,8 @@ def test_recursion_limited(self): """ max_recursion = sys.getrecursionlimit() s = Session(Cluster(), [Host("127.0.0.1", SimpleConvictionPolicy)]) - self.assertRaises(TypeError, execute_concurrent_with_args, s, "doesn't matter", [('param',)] * max_recursion, raise_on_first_error=True) + with pytest.raises(TypeError): + execute_concurrent_with_args(s, "doesn't matter", [('param',)] * max_recursion, raise_on_first_error=True) results = execute_concurrent_with_args(s, "doesn't matter", [('param',)] * max_recursion, raise_on_first_error=False) # previously assert len(results) == max_recursion diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 10233f09f1..c21f5c7212 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -28,6 +28,7 @@ SupportedMessage, ProtocolHandler) from tests.util import wait_until, assertRegex +import pytest class ConnectionTest(unittest.TestCase): @@ -241,7 +242,8 @@ def test_not_implemented(self): Ensure the following methods throw NIE's. If not, come back and test them. """ c = self.make_connection() - self.assertRaises(NotImplementedError, c.close) + with pytest.raises(NotImplementedError): + c.close() def test_set_keyspace_blocking(self): c = self.make_connection() diff --git a/tests/unit/test_host_connection_pool.py b/tests/unit/test_host_connection_pool.py index 9fbd2debe6..3fac0b18ef 100644 --- a/tests/unit/test_host_connection_pool.py +++ b/tests/unit/test_host_connection_pool.py @@ -27,6 +27,7 @@ from cassandra.pool import HostConnection from cassandra.pool import Host, NoConnectionsAvailable from cassandra.policies import HostDistance, SimpleConvictionPolicy +import pytest LOGGER = logging.getLogger(__name__) @@ -77,7 +78,8 @@ def test_failed_wait_for_connection(self): # we're already at the max number of requests for this connection, # so we this should fail - self.assertRaises(NoConnectionsAvailable, pool.borrow_connection, 0) + with pytest.raises(NoConnectionsAvailable): + pool.borrow_connection(0) def test_successful_wait_for_connection(self): host = Mock(spec=Host, address='ip1') @@ -126,7 +128,8 @@ def test_spawn_when_at_max(self): # we don't care about making this borrow_connection call succeed for the # purposes of this test, as long as it results in a new connection # creation being scheduled - self.assertRaises(NoConnectionsAvailable, pool.borrow_connection, 0) + with pytest.raises(NoConnectionsAvailable): + pool.borrow_connection(0) if not self.uses_single_connection: session.submit.assert_called_once_with(pool._create_new_connection) @@ -204,9 +207,12 @@ def test_host_instantiations(self): Ensure Host fails if not initialized properly """ - self.assertRaises(ValueError, Host, None, None) - self.assertRaises(ValueError, Host, '127.0.0.1', None) - self.assertRaises(ValueError, Host, None, SimpleConvictionPolicy) + with pytest.raises(ValueError): + Host(None, None) + with pytest.raises(ValueError): + Host('127.0.0.1', None) + with pytest.raises(ValueError): + Host(None, SimpleConvictionPolicy) def test_host_equality(self): """ diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 72874d6406..3069f6bced 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -34,6 +34,7 @@ from cassandra.policies import SimpleConvictionPolicy from cassandra.pool import Host from tests.util import assertCountEqual +import pytest log = logging.getLogger(__name__) @@ -54,10 +55,14 @@ def test_replication_factor_parsing(self): assert rf.transient_replicas == 1 assert str(rf) == '3/1' - self.assertRaises(ValueError, ReplicationFactor.create, '3/') - self.assertRaises(ValueError, ReplicationFactor.create, 'a/1') - self.assertRaises(ValueError, ReplicationFactor.create, 'a') - self.assertRaises(ValueError, ReplicationFactor.create, '3/a') + with pytest.raises(ValueError): + ReplicationFactor.create('3/') + with pytest.raises(ValueError): + ReplicationFactor.create('a/1') + with pytest.raises(ValueError): + ReplicationFactor.create('a') + with pytest.raises(ValueError): + ReplicationFactor.create('3/a') def test_replication_factor_equality(self): assert ReplicationFactor.create('3/1') == ReplicationFactor.create('3/1') @@ -99,8 +104,10 @@ def test_replication_strategy(self): assert rs.create('xxxxxxxx', fake_options_map) == _UnknownStrategy('xxxxxxxx', fake_options_map) - self.assertRaises(NotImplementedError, rs.make_token_replica_map, None, None) - self.assertRaises(NotImplementedError, rs.export_for_schema) + with pytest.raises(NotImplementedError): + rs.make_token_replica_map(None, None) + with pytest.raises(NotImplementedError): + rs.export_for_schema() def test_simple_replication_type_parsing(self): """ Test equality between passing numeric and string replication factor for simple strategy """ diff --git a/tests/unit/test_orderedmap.py b/tests/unit/test_orderedmap.py index f3c65c16fd..156bbd5f30 100644 --- a/tests/unit/test_orderedmap.py +++ b/tests/unit/test_orderedmap.py @@ -17,6 +17,7 @@ from cassandra.util import OrderedMap, OrderedMapSerializedKey from cassandra.cqltypes import EMPTY, UTF8Type, lookup_casstype from tests.util import assertListEqual +import pytest class OrderedMapTest(unittest.TestCase): def test_init(self): @@ -34,7 +35,7 @@ def test_init(self): assert d['key1'] == 'v1' assert d['key2'] == 'v2' - with self.assertRaises(TypeError): + with pytest.raises(TypeError): OrderedMap('too', 'many', 'args') def test_contains(self): @@ -107,7 +108,7 @@ def test_getitem(self): for v, k in enumerate(keys): assert om[k] == v - with self.assertRaises(KeyError): + with pytest.raises(KeyError): om['notthere'] def test_iter(self): @@ -118,7 +119,8 @@ def test_iter(self): itr = iter(om) assert sum([1 for _ in itr]) == len(keys) - self.assertRaises(StopIteration, next, itr) + with pytest.raises(StopIteration): + next(itr) assert list(iter(om)) == keys assert list(om.items()) == items @@ -145,19 +147,22 @@ def test_popitem(self): item = (1, 2) om = OrderedMap((item,)) assert om.popitem() == item - self.assertRaises(KeyError, om.popitem) + with pytest.raises(KeyError): + om.popitem() def test_delitem(self): om = OrderedMap({1: 1, 2: 2}) - self.assertRaises(KeyError, om.__delitem__, 3) + with pytest.raises(KeyError): + om.__delitem__(3) del om[1] assert om == {2: 2} del om[2] assert not om - self.assertRaises(KeyError, om.__delitem__, 1) + with pytest.raises(KeyError): + om.__delitem__(1) class OrderedMapSerializedKeyTest(unittest.TestCase): diff --git a/tests/unit/test_parameter_binding.py b/tests/unit/test_parameter_binding.py index c8f9a71e65..5416ac461d 100644 --- a/tests/unit/test_parameter_binding.py +++ b/tests/unit/test_parameter_binding.py @@ -92,16 +92,18 @@ def setUpClass(cls): def test_invalid_argument_type(self): values = (0, 0, 0, 'string not int') - with pytest.raises(TypeError) as e: + with pytest.raises(TypeError) as exc: self.bound.bind(values) + e = exc.value assert 'v0' in str(e) assert 'Int32Type' in str(e) assert 'str' in str(e) values = (['1', '2'], 0, 0, 0) - with pytest.raises(TypeError) as e: + with pytest.raises(TypeError) as exc: self.bound.bind(values) + e = exc.value assert 'rk0' in str(e) assert 'Int32Type' in str(e) assert 'list' in str(e) @@ -128,26 +130,32 @@ def test_inherit_fetch_size(self): assert 1234 == bound_statement.fetch_size def test_too_few_parameters_for_routing_key(self): - self.assertRaises(ValueError, self.prepared.bind, (1,)) + with pytest.raises(ValueError): + self.prepared.bind((1,)) bound = self.prepared.bind((1, 2)) assert bound.keyspace == 'keyspace' def test_dict_missing_routing_key(self): - self.assertRaises(KeyError, self.bound.bind, {'rk0': 0, 'ck0': 0, 'v0': 0}) - self.assertRaises(KeyError, self.bound.bind, {'rk1': 0, 'ck0': 0, 'v0': 0}) + with pytest.raises(KeyError): + self.bound.bind({'rk0': 0, 'ck0': 0, 'v0': 0}) + with pytest.raises(KeyError): + self.bound.bind({'rk1': 0, 'ck0': 0, 'v0': 0}) def test_missing_value(self): - self.assertRaises(KeyError, self.bound.bind, {'rk0': 0, 'rk1': 0, 'ck0': 0}) + with pytest.raises(KeyError): + self.bound.bind({'rk0': 0, 'rk1': 0, 'ck0': 0}) def test_extra_value(self): self.bound.bind({'rk0': 0, 'rk1': 0, 'ck0': 0, 'v0': 0, 'should_not_be_here': 123}) # okay to have extra keys in dict assert self.bound.values == [b'\x00' * 4] * 4 # four encoded zeros - self.assertRaises(ValueError, self.bound.bind, (0, 0, 0, 0, 123)) + with pytest.raises(ValueError): + self.bound.bind((0, 0, 0, 0, 123)) def test_values_none(self): # should have values - self.assertRaises(ValueError, self.bound.bind, None) + with pytest.raises(ValueError): + self.bound.bind(None) # prepared statement with no values prepared_statement = PreparedStatement(column_metadata=[], @@ -171,8 +179,10 @@ def test_bind_none(self): assert self.bound.values[-1] == None def test_unset_value(self): - self.assertRaises(ValueError, self.bound.bind, {'rk0': 0, 'rk1': 0, 'ck0': 0, 'v0': UNSET_VALUE}) - self.assertRaises(ValueError, self.bound.bind, (0, 0, 0, UNSET_VALUE)) + with pytest.raises(ValueError): + self.bound.bind({'rk0': 0, 'rk1': 0, 'ck0': 0, 'v0': UNSET_VALUE}) + with pytest.raises(ValueError): + self.bound.bind((0, 0, 0, UNSET_VALUE)) class BoundStatementTestV4(BoundStatementTestV3): @@ -181,8 +191,10 @@ class BoundStatementTestV4(BoundStatementTestV3): def test_dict_missing_routing_key(self): # in v4 it implicitly binds UNSET_VALUE for missing items, # UNSET_VALUE is ValueError for routing keys - self.assertRaises(ValueError, self.bound.bind, {'rk0': 0, 'ck0': 0, 'v0': 0}) - self.assertRaises(ValueError, self.bound.bind, {'rk1': 0, 'ck0': 0, 'v0': 0}) + with pytest.raises(ValueError): + self.bound.bind({'rk0': 0, 'ck0': 0, 'v0': 0}) + with pytest.raises(ValueError): + self.bound.bind({'rk1': 0, 'ck0': 0, 'v0': 0}) def test_missing_value(self): # in v4 missing values are UNSET_VALUE diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index 8f419b31f1..9f5d3d2b7c 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -48,16 +48,24 @@ def test_non_implemented(self): host = Host(DefaultEndPoint("ip1"), SimpleConvictionPolicy) host.set_location_info("dc1", "rack1") - self.assertRaises(NotImplementedError, policy.distance, host) - self.assertRaises(NotImplementedError, policy.populate, None, host) - self.assertRaises(NotImplementedError, policy.make_query_plan) - self.assertRaises(NotImplementedError, policy.on_up, host) - self.assertRaises(NotImplementedError, policy.on_down, host) - self.assertRaises(NotImplementedError, policy.on_add, host) - self.assertRaises(NotImplementedError, policy.on_remove, host) + with pytest.raises(NotImplementedError): + policy.distance(host) + with pytest.raises(NotImplementedError): + policy.populate(None, host) + with pytest.raises(NotImplementedError): + policy.make_query_plan() + with pytest.raises(NotImplementedError): + policy.on_up(host) + with pytest.raises(NotImplementedError): + policy.on_down(host) + with pytest.raises(NotImplementedError): + policy.on_add(host) + with pytest.raises(NotImplementedError): + policy.on_remove(host) def test_instance_check(self): - self.assertRaises(TypeError, Cluster, load_balancing_policy=RoundRobinPolicy) + with pytest.raises(TypeError): + Cluster(load_balancing_policy=RoundRobinPolicy) class RoundRobinPolicyTest(unittest.TestCase): @@ -857,8 +865,10 @@ def test_not_implemented(self): """ conviction_policy = ConvictionPolicy(1) - self.assertRaises(NotImplementedError, conviction_policy.add_failure, 1) - self.assertRaises(NotImplementedError, conviction_policy.reset) + with pytest.raises(NotImplementedError): + conviction_policy.add_failure(1) + with pytest.raises(NotImplementedError): + conviction_policy.reset() class SimpleConvictionPolicyTest(unittest.TestCase): @@ -879,7 +889,8 @@ def test_basic_responses(self): """ policy = ReconnectionPolicy() - self.assertRaises(NotImplementedError, policy.new_schedule) + with pytest.raises(NotImplementedError): + policy.new_schedule() class ConstantReconnectionPolicyTest(unittest.TestCase): @@ -889,7 +900,8 @@ def test_bad_vals(self): Test initialization values """ - self.assertRaises(ValueError, ConstantReconnectionPolicy, -1, 0) + with pytest.raises(ValueError): + ConstantReconnectionPolicy(-1, 0) def test_schedule(self): """ @@ -930,10 +942,14 @@ def _assert_between(self, value, min, max): assert min <= value <= max def test_bad_vals(self): - self.assertRaises(ValueError, ExponentialReconnectionPolicy, -1, 0) - self.assertRaises(ValueError, ExponentialReconnectionPolicy, 0, -1) - self.assertRaises(ValueError, ExponentialReconnectionPolicy, 9000, 1) - self.assertRaises(ValueError, ExponentialReconnectionPolicy, 1, 2, -1) + with pytest.raises(ValueError): + ExponentialReconnectionPolicy(-1, 0) + with pytest.raises(ValueError): + ExponentialReconnectionPolicy(0, -1) + with pytest.raises(ValueError): + ExponentialReconnectionPolicy(9000, 1) + with pytest.raises(ValueError): + ExponentialReconnectionPolicy(1, 2, -1) def test_schedule_no_max(self): base_delay = 2.0 @@ -1376,7 +1392,7 @@ def test_immutable_predicate(self): expected_message_regex = "can't set attribute" hfp = HostFilterPolicy(child_policy=Mock(name='child_policy'), predicate=Mock(name='predicate')) - with self.assertRaisesRegex(AttributeError, expected_message_regex): + with pytest.raises(AttributeError, match=expected_message_regex): hfp.predicate = object() diff --git a/tests/unit/test_protocol.py b/tests/unit/test_protocol.py index 5ea7775b94..57261654df 100644 --- a/tests/unit/test_protocol.py +++ b/tests/unit/test_protocol.py @@ -26,6 +26,7 @@ from cassandra.query import BatchType from cassandra.marshal import uint32_unpack from cassandra.cluster import ContinuousPagingOptions +import pytest class MessageTest(unittest.TestCase): @@ -109,7 +110,8 @@ def test_continuous_paging(self): io = Mock() for version in [version for version in ProtocolVersion.SUPPORTED_VERSIONS if not ProtocolVersion.has_continuous_paging_support(version)]: - self.assertRaises(UnsupportedOperation, message.send_body, io, version) + with pytest.raises(UnsupportedOperation): + message.send_body(io, version) io.reset_mock() message.send_body(io, ProtocolVersion.DSE_V1) @@ -161,7 +163,7 @@ def test_prepare_flag_with_keyspace(self): (b'ks',), ]) else: - with self.assertRaises(UnsupportedOperation): + with pytest.raises(UnsupportedOperation): message.send_body(io, version) io.reset_mock() @@ -169,7 +171,7 @@ def test_keyspace_flag_raises_before_v5(self): keyspace_message = QueryMessage('a', consistency_level=3, keyspace='ks') io = Mock(name='io') - with self.assertRaisesRegex(UnsupportedOperation, 'Keyspaces.*set'): + with pytest.raises(UnsupportedOperation, match='Keyspaces.*set'): keyspace_message.send_body(io, protocol_version=4) io.assert_not_called() diff --git a/tests/unit/test_response_future.py b/tests/unit/test_response_future.py index 0aa8f72c95..bcca28ac73 100644 --- a/tests/unit/test_response_future.py +++ b/tests/unit/test_response_future.py @@ -32,6 +32,7 @@ from cassandra.pool import NoConnectionsAvailable from cassandra.query import SimpleStatement from tests.util import assertEqual, assertIsInstance +import pytest class ResponseFutureTests(unittest.TestCase): @@ -93,7 +94,8 @@ def test_unknown_result_class(self): rf = self.make_response_future(session) rf.send_request() rf._set_result(None, None, None, object()) - self.assertRaises(ConnectionException, rf.result) + with pytest.raises(ConnectionException): + rf.result() def test_set_keyspace_result(self): session = self.make_session() @@ -160,7 +162,8 @@ def test_heartbeat_defunct_deadlock(self): # Simulate ResponseFuture timing out rf._on_timeout() - self.assertRaisesRegex(OperationTimedOut, "Connection defunct by heartbeat", rf.result) + with pytest.raises(OperationTimedOut, match="Connection defunct by heartbeat"): + rf.result() def test_read_timeout_error_message(self): session = self.make_session() @@ -174,7 +177,8 @@ def test_read_timeout_error_message(self): "received_responses":1, "consistency": 1}) rf._set_result(None, None, None, result) - self.assertRaises(Exception, rf.result) + with pytest.raises(Exception): + rf.result() def test_write_timeout_error_message(self): session = self.make_session() @@ -187,7 +191,8 @@ def test_write_timeout_error_message(self): result = Mock(spec=WriteTimeoutErrorMessage, info={"write_type": 1, "required_responses":2, "received_responses":1, "consistency": 1}) rf._set_result(None, None, None, result) - self.assertRaises(Exception, rf.result) + with pytest.raises(Exception): + rf.result() def test_unavailable_error_message(self): session = self.make_session() @@ -202,7 +207,8 @@ def test_unavailable_error_message(self): result = Mock(spec=UnavailableErrorMessage, info={"required_replicas":2, "alive_replicas": 1, "consistency": 1}) rf._set_result(None, None, None, result) - self.assertRaises(Exception, rf.result) + with pytest.raises(Exception): + rf.result() def test_request_error_with_prepare_message(self): session = self.make_session() @@ -345,7 +351,8 @@ def test_all_retries_fail(self): rf.session.cluster.scheduler.schedule.assert_called_with(ANY, rf._retry_task, False, host) rf._retry_task(False, host) - self.assertRaises(NoHostAvailable, rf.result) + with pytest.raises(NoHostAvailable): + rf.result() def test_exponential_retry_policy_fail(self): session = self.make_session() @@ -377,7 +384,8 @@ def test_all_pools_shutdown(self): rf = ResponseFuture(session, Mock(), Mock(), 1) rf.send_request() - self.assertRaises(NoHostAvailable, rf.result) + with pytest.raises(NoHostAvailable): + rf.result() def test_first_pool_shutdown(self): session = self.make_basic_session() @@ -464,7 +472,8 @@ def test_errback(self): result.to_exception.return_value = Exception() rf._set_result(None, None, None, result) - self.assertRaises(Exception, rf.result) + with pytest.raises(Exception): + rf.result() # this should get called immediately now that the error is set rf.add_errback(assertIsInstance, Exception) @@ -522,7 +531,8 @@ def test_multiple_errbacks(self): result.to_exception.return_value = expected_exception rf._set_result(None, None, None, result) rf._event.set() - self.assertRaises(Exception, rf.result) + with pytest.raises(Exception): + rf.result() callback.assert_called_once_with(expected_exception, arg, **kwargs) callback2.assert_called_once_with(expected_exception, arg2, **kwargs2) @@ -545,7 +555,8 @@ def test_add_callbacks(self): info={"required_replicas":2, "alive_replicas": 1, "consistency": 1}) result.to_exception.return_value = Exception() rf._set_result(None, None, None, result) - self.assertRaises(Exception, rf.result) + with pytest.raises(Exception): + rf.result() # test callback rf = ResponseFuture(session, message, query, 1) @@ -607,7 +618,8 @@ def test_prepared_query_not_found_bad_keyspace(self): result = Mock(spec=PreparedQueryNotFound, info='a' * 16) rf._set_result(None, None, None, result) - self.assertRaises(ValueError, rf.result) + with pytest.raises(ValueError): + rf.result() def test_repeat_orig_query_after_succesful_reprepare(self): query_id = b'abc123' # Just a random binary string so we don't hit id mismatch exception @@ -651,7 +663,8 @@ def test_timeout_does_not_release_stream_id(self): rf._on_timeout() pool.return_connection.assert_called_once_with(connection, stream_was_orphaned=True) - self.assertRaisesRegex(OperationTimedOut, "Client request timeout", rf.result) + with pytest.raises(OperationTimedOut, match="Client request timeout"): + rf.result() assert len(connection.request_ids) == 0, \ "Request IDs should be empty but it's not: {}".format(connection.request_ids) diff --git a/tests/unit/test_resultset.py b/tests/unit/test_resultset.py index c1dc1afae1..80e9c21ff9 100644 --- a/tests/unit/test_resultset.py +++ b/tests/unit/test_resultset.py @@ -19,6 +19,7 @@ from cassandra.query import named_tuple_factory, dict_factory, tuple_factory from tests.util import assertListEqual +import pytest class ResultSetTests(unittest.TestCase): @@ -85,11 +86,11 @@ def test_iterate_then_index(self): rs = ResultSet(Mock(has_more_pages=False), expected) itr = iter(rs) # before consuming - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): rs[0] list(itr) # after consuming - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): rs[0] assert not rs @@ -102,14 +103,14 @@ def test_iterate_then_index(self): type(response_future).has_more_pages = PropertyMock(side_effect=(True, False)) itr = iter(rs) # before consuming - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): rs[0] for row in itr: # while consuming - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): rs[0] # after consuming - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): rs[0] assert not rs assert not list(rs) @@ -175,17 +176,17 @@ def test_bool(self): def test_was_applied(self): # unknown row factory raises - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ResultSet(Mock(), []).was_applied response_future = Mock(row_factory=named_tuple_factory) # no row - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ResultSet(response_future, []).was_applied # too many rows - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): ResultSet(response_future, [tuple(), tuple()]).was_applied # various internal row factories diff --git a/tests/unit/test_segment.py b/tests/unit/test_segment.py index aeca631162..bfc273db05 100644 --- a/tests/unit/test_segment.py +++ b/tests/unit/test_segment.py @@ -19,6 +19,7 @@ from cassandra import DriverException from cassandra.segment import Segment, CrcException from cassandra.connection import segment_codec_no_compression, segment_codec_lz4 +import pytest def to_bits(b): @@ -71,7 +72,7 @@ def test_encode_uncompressed_header_with_max_payload(self): def test_encode_header_fails_if_payload_too_big(self): buffer = BytesIO() for codec in [c for c in [segment_codec_no_compression, segment_codec_lz4] if c is not None]: - with self.assertRaises(DriverException): + with pytest.raises(DriverException): codec.encode_header(buffer, len(self.large_msg), -1, False) def test_encode_uncompressed_header_not_self_contained_msg(self): @@ -131,7 +132,7 @@ def test_decode_header_fails_if_corrupted(self): buffer.write(b'0') buffer.seek(0) - with self.assertRaises(CrcException): + with pytest.raises(CrcException): segment_codec_no_compression.decode_header(buffer) def test_decode_uncompressed_self_contained_segment(self): @@ -186,7 +187,7 @@ def test_decode_fails_if_corrupted(self): buffer.write(b'0') buffer.seek(0) header = segment_codec_lz4.decode_header(buffer) - with self.assertRaises(CrcException): + with pytest.raises(CrcException): segment_codec_lz4.decode(buffer, header) @unittest.skipUnless(segment_codec_lz4, ' lz4 not installed') diff --git a/tests/unit/test_sortedset.py b/tests/unit/test_sortedset.py index a6e0123ef9..071907d53e 100644 --- a/tests/unit/test_sortedset.py +++ b/tests/unit/test_sortedset.py @@ -194,20 +194,22 @@ def test_pop(self): def test_remove(self): ss = sortedset([2, 1]) assert len(ss) == 2 - self.assertRaises(KeyError, ss.remove, 3) + with pytest.raises(KeyError): + ss.remove(3) assert len(ss) == 2 ss.remove(1) assert len(ss) == 1 ss.remove(2) assert not ss - self.assertRaises(KeyError, ss.remove, 2) + with pytest.raises(KeyError): + ss.remove(2) assert not ss def test_getitem(self): ss = sortedset(range(3)) for i in range(len(ss)): assert ss[i] == i - with self.assertRaises(IndexError): + with pytest.raises(IndexError): ss[len(ss)] def test_delitem(self): @@ -216,7 +218,7 @@ def test_delitem(self): for i in range(len(ss)): assertListEqual(list(ss), expected[i:]) del ss[0] - with self.assertRaises(IndexError): + with pytest.raises(IndexError): ss[0] def test_delslice(self): @@ -230,7 +232,7 @@ def test_delslice(self): assertListEqual(list(ss), [1]) del ss[:] assert not ss - with self.assertRaises(IndexError): + with pytest.raises(IndexError): del ss[0] def test_reversed(self): diff --git a/tests/unit/test_time_util.py b/tests/unit/test_time_util.py index 29fb811b75..6c2c46d180 100644 --- a/tests/unit/test_time_util.py +++ b/tests/unit/test_time_util.py @@ -20,6 +20,7 @@ import datetime import time import uuid +import pytest class TimeUtilTest(unittest.TestCase): @@ -77,11 +78,11 @@ def test_uuid_from_time(self): assert u1.clock_seq != u2.clock_seq # node too large - with self.assertRaises(ValueError): + with pytest.raises(ValueError): u = util.uuid_from_time(t, node=2 ** 48) # clock_seq too large - with self.assertRaises(ValueError): + with pytest.raises(ValueError): u = util.uuid_from_time(t, clock_seq=0x4000) # construct from datetime diff --git a/tests/unit/test_timestamps.py b/tests/unit/test_timestamps.py index 70280a3eb6..8ef747d515 100644 --- a/tests/unit/test_timestamps.py +++ b/tests/unit/test_timestamps.py @@ -18,6 +18,7 @@ from cassandra import timestamps from tests.util import assertRegex from threading import Thread, Lock +import pytest class _TimestampTestMixin(object): @@ -46,7 +47,7 @@ def _call_and_check_results(self, assert actual == expected # assert we patched timestamps.time.time correctly - with self.assertRaises(StopIteration): + with pytest.raises(StopIteration): tsg() diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index ef5bd5196a..3390f6dbd6 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -120,7 +120,8 @@ def test_lookup_casstype(self): assert str(lookup_casstype('unknown')) == str(cassandra.cqltypes.mkUnrecognizedType('unknown')) - self.assertRaises(ValueError, lookup_casstype, 'AsciiType~') + with pytest.raises(ValueError): + lookup_casstype('AsciiType~') def test_casstype_parameterized(self): assert LongType.cass_parameterized_type_with(()) == 'LongType' @@ -462,50 +463,50 @@ def test_cql_parameterized_type(self): def test_serialization_fixed_size_too_small(self): ctype = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType, 5)") - with self.assertRaisesRegex(ValueError, "Expected sequence of size 5 for vector of type float and dimension 5, observed sequence of length 4"): + with pytest.raises(ValueError, match="Expected sequence of size 5 for vector of type float and dimension 5, observed sequence of length 4"): ctype.serialize([1.2, 3.4, 5.6, 7.8], 0) def test_serialization_fixed_size_too_big(self): ctype = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType, 4)") - with self.assertRaisesRegex(ValueError, "Expected sequence of size 4 for vector of type float and dimension 4, observed sequence of length 5"): + with pytest.raises(ValueError, match="Expected sequence of size 4 for vector of type float and dimension 4, observed sequence of length 5"): ctype.serialize([1.2, 3.4, 5.6, 7.8, 9.10], 0) def test_serialization_variable_size_too_small(self): ctype = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.IntegerType, 5)") - with self.assertRaisesRegex(ValueError, "Expected sequence of size 5 for vector of type varint and dimension 5, observed sequence of length 4"): + with pytest.raises(ValueError, match="Expected sequence of size 5 for vector of type varint and dimension 5, observed sequence of length 4"): ctype.serialize([1, 2, 3, 4], 0) def test_serialization_variable_size_too_big(self): ctype = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.IntegerType, 4)") - with self.assertRaisesRegex(ValueError, "Expected sequence of size 4 for vector of type varint and dimension 4, observed sequence of length 5"): + with pytest.raises(ValueError, match="Expected sequence of size 4 for vector of type varint and dimension 4, observed sequence of length 5"): ctype.serialize([1, 2, 3, 4, 5], 0) def test_deserialization_fixed_size_too_small(self): ctype_four = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType, 4)") ctype_four_bytes = ctype_four.serialize([1.2, 3.4, 5.6, 7.8], 0) ctype_five = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType, 5)") - with self.assertRaisesRegex(ValueError, "Expected vector of type float and dimension 5 to have serialized size 20; observed serialized size of 16 instead"): + with pytest.raises(ValueError, match="Expected vector of type float and dimension 5 to have serialized size 20; observed serialized size of 16 instead"): ctype_five.deserialize(ctype_four_bytes, 0) def test_deserialization_fixed_size_too_big(self): ctype_five = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType, 5)") ctype_five_bytes = ctype_five.serialize([1.2, 3.4, 5.6, 7.8, 9.10], 0) ctype_four = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType, 4)") - with self.assertRaisesRegex(ValueError, "Expected vector of type float and dimension 4 to have serialized size 16; observed serialized size of 20 instead"): + with pytest.raises(ValueError, match="Expected vector of type float and dimension 4 to have serialized size 16; observed serialized size of 20 instead"): ctype_four.deserialize(ctype_five_bytes, 0) def test_deserialization_variable_size_too_small(self): ctype_four = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.IntegerType, 4)") ctype_four_bytes = ctype_four.serialize([1, 2, 3, 4], 0) ctype_five = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.IntegerType, 5)") - with self.assertRaisesRegex(ValueError, "Error reading additional data during vector deserialization after successfully adding 4 elements"): + with pytest.raises(ValueError, match="Error reading additional data during vector deserialization after successfully adding 4 elements"): ctype_five.deserialize(ctype_four_bytes, 0) def test_deserialization_variable_size_too_big(self): ctype_five = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.IntegerType, 5)") ctype_five_bytes = ctype_five.serialize([1, 2, 3, 4, 5], 0) ctype_four = parse_casstype_args("org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.IntegerType, 4)") - with self.assertRaisesRegex(ValueError, "Additional bytes remaining after vector deserialization completed"): + with pytest.raises(ValueError, match="Additional bytes remaining after vector deserialization completed"): ctype_four.deserialize(ctype_five_bytes, 0) @@ -560,14 +561,14 @@ def test_decode_precision(self): assert DateRangeType._decode_precision(6) == 'MILLISECOND' def test_decode_precision_error(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): DateRangeType._decode_precision(-1) def test_encode_precision(self): assert DateRangeType._encode_precision('SECOND') == 5 def test_encode_precision_error(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): DateRangeType._encode_precision('INVALID') def test_deserialize_single_value(self): @@ -703,12 +704,14 @@ def test_serialize_both_open(self): assert int8_pack(4) == serialized def test_failure_to_serialize_no_value_object(self): - self.assertRaises(ValueError, DateRangeType.serialize, object(), 5) + with pytest.raises(ValueError): + DateRangeType.serialize(object(), 5) def test_failure_to_serialize_no_bounds_object(self): class no_bounds_object(object): value = lower_bound = None - self.assertRaises(ValueError, DateRangeType.serialize, no_bounds_object, 5) + with pytest.raises(ValueError): + DateRangeType.serialize(no_bounds_object, 5) def test_serialized_value_round_trip(self): vals = [b'\x01\x00\x00\x01%\xe9a\xf9\xd1\x06\x00\x00\x01v\xbb>o\xff\x00', diff --git a/tests/unit/test_util_types.py b/tests/unit/test_util_types.py index d8383c4fb5..4a115affbc 100644 --- a/tests/unit/test_util_types.py +++ b/tests/unit/test_util_types.py @@ -16,6 +16,7 @@ import datetime from cassandra.util import Date, Time, Duration, Version, maybe_add_timeout_to_query +import pytest class DateTests(unittest.TestCase): @@ -56,8 +57,10 @@ def test_limits(self): assert Date(max_builtin.days_from_epoch + 1).days_from_epoch == max_builtin.days_from_epoch + 1 def test_invalid_init(self): - self.assertRaises(ValueError, Date, '-1999-10-10') - self.assertRaises(TypeError, Date, 1.234) + with pytest.raises(ValueError): + Date('-1999-10-10') + with pytest.raises(TypeError): + Date(1.234) def test_str(self): date_str = '2015-03-16' @@ -141,10 +144,14 @@ def test_str_repr(self): assert repr(Time(1)) == 'Time(1)' def test_invalid_init(self): - self.assertRaises(ValueError, Time, '1999-10-10 11:11:11.1234') - self.assertRaises(TypeError, Time, 1.234) - self.assertRaises(ValueError, Time, 123456789000000) - self.assertRaises(TypeError, Time, datetime.datetime(2004, 12, 23, 11, 11, 1)) + with pytest.raises(ValueError): + Time('1999-10-10 11:11:11.1234') + with pytest.raises(TypeError): + Time(1.234) + with pytest.raises(ValueError): + Time(123456789000000) + with pytest.raises(TypeError): + Time(datetime.datetime(2004, 12, 23, 11, 11, 1)) class DurationTests(unittest.TestCase): @@ -229,7 +236,7 @@ def test_version_parsing(self): assert v.prerelease == expected_result[4] # not supported version formats - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Version('test.1.0') def test_version_compare(self): From 38b23da449b652722fa551a360692fbe29fee3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Wed, 16 Jul 2025 18:30:23 +0200 Subject: [PATCH 079/298] assert_quiescent_pool_state: Use plain asserts --- .../integration/simulacron/test_connection.py | 8 ++++---- .../standard/test_authentication.py | 12 +++++------ tests/integration/standard/test_cluster.py | 4 ++-- tests/integration/util.py | 20 +++++++++---------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/integration/simulacron/test_connection.py b/tests/integration/simulacron/test_connection.py index 756bf3ac68..818d0b46b9 100644 --- a/tests/integration/simulacron/test_connection.py +++ b/tests/integration/simulacron/test_connection.py @@ -185,7 +185,7 @@ def test_callbacks_and_pool_when_oto(self): with pytest.raises(OperationTimedOut): future.result() - assert_quiescent_pool_state(self, cluster) + assert_quiescent_pool_state(cluster) time.sleep(server_delay + 1) # PYTHON-630 -- only the errback should be called @@ -358,11 +358,11 @@ def test_retry_after_defunct(self): # Might take some time to close the previous connections and reconnect time.sleep(10) - assert_quiescent_pool_state(self, cluster) + assert_quiescent_pool_state(cluster) clear_queries() time.sleep(10) - assert_quiescent_pool_state(self, cluster) + assert_quiescent_pool_state(cluster) def test_idle_connection_is_not_closed(self): """ @@ -425,7 +425,7 @@ def test_host_is_not_set_to_down_after_query_oto(self): assert isinstance(f._final_exception, OperationTimedOut) assert listener.hosts_marked_down == [] - assert_quiescent_pool_state(self, cluster) + assert_quiescent_pool_state(cluster) def test_can_shutdown_connection_subclass(self): start_and_prime_singledc() diff --git a/tests/integration/standard/test_authentication.py b/tests/integration/standard/test_authentication.py index d40aa33852..eb8019bf65 100644 --- a/tests/integration/standard/test_authentication.py +++ b/tests/integration/standard/test_authentication.py @@ -107,7 +107,7 @@ def test_auth_connect(self): session = cluster.connect(wait_for_all_pools=True) try: assert session.execute("SELECT release_version FROM system.local WHERE key='local'") - assert_quiescent_pool_state(self, cluster, wait=1) + assert_quiescent_pool_state(cluster, wait=1) for pool in session.get_pools(): connection, _ = pool.borrow_connection(timeout=0) assert connection.authenticator.server_authenticator_class == 'org.apache.cassandra.auth.PasswordAuthenticator' @@ -116,7 +116,7 @@ def test_auth_connect(self): cluster.shutdown() finally: root_session.execute('DROP USER %s', user) - assert_quiescent_pool_state(self, root_session.cluster, wait=1) + assert_quiescent_pool_state(root_session.cluster, wait=1) root_session.cluster.shutdown() def test_connect_wrong_pwd(self): @@ -124,7 +124,7 @@ def test_connect_wrong_pwd(self): try: with pytest.raises(NoHostAvailable, match='.*AuthenticationFailed.'): cluster.connect() - assert_quiescent_pool_state(self, cluster) + assert_quiescent_pool_state(cluster) finally: cluster.shutdown() @@ -133,7 +133,7 @@ def test_connect_wrong_username(self): try: with pytest.raises(NoHostAvailable, match='.*AuthenticationFailed.*'): cluster.connect() - assert_quiescent_pool_state(self, cluster) + assert_quiescent_pool_state(cluster) finally: cluster.shutdown() @@ -142,7 +142,7 @@ def test_connect_empty_pwd(self): try: with pytest.raises(NoHostAvailable, match='.*AuthenticationFailed.*'): cluster.connect() - assert_quiescent_pool_state(self, cluster) + assert_quiescent_pool_state(cluster) finally: cluster.shutdown() @@ -151,7 +151,7 @@ def test_connect_no_auth_provider(self): try: with pytest.raises(NoHostAvailable, match='.*AuthenticationFailed.*'): cluster.connect() - assert_quiescent_pool_state(self, cluster) + assert_quiescent_pool_state(cluster) finally: cluster.shutdown() diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 746dc8db38..c83f454a0f 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -781,7 +781,7 @@ def test_idle_heartbeat(self): cluster._idle_heartbeat.stop() cluster._idle_heartbeat.join() - assert_quiescent_pool_state(self, cluster) + assert_quiescent_pool_state(cluster) cluster.shutdown() @@ -829,7 +829,7 @@ def test_pool_management(self): cluster.refresh_schema_metadata() cluster.refresh_schema_metadata(max_schema_agreement_wait=0) - assert_quiescent_pool_state(self, cluster) + assert_quiescent_pool_state(cluster) cluster.shutdown() diff --git a/tests/integration/util.py b/tests/integration/util.py index bcc4cb829b..7cbdfdb22d 100644 --- a/tests/integration/util.py +++ b/tests/integration/util.py @@ -18,14 +18,14 @@ import time -def assert_quiescent_pool_state(test_case, cluster, wait=None): +def assert_quiescent_pool_state(cluster, wait=None): """ Checking the quiescent pool state checks that none of the requests ids have been lost. However, the callback corresponding to a request_id is called before the request_id is returned back to the pool, therefore session.execute("SELECT * from system.local") - assert_quiescent_pool_state(self, session.cluster) + assert_quiescent_pool_state(session.cluster) (with no wait) might fail because when execute comes back the request_id hasn't yet been returned to the pool, therefore the wait. @@ -35,23 +35,23 @@ def assert_quiescent_pool_state(test_case, cluster, wait=None): for session in cluster.sessions: pool_states = session.get_pool_state().values() - test_case.assertTrue(pool_states) + assert pool_states for state in pool_states: - test_case.assertFalse(state['shutdown']) - test_case.assertGreater(state['open_count'], 0) + assert not state['shutdown'] + assert state['open_count'] > 0 no_in_flight = all((i == 0 for i in state['in_flights'])) orphans_and_inflights = zip(state['orphan_requests'],state['in_flights']) all_orphaned = all((len(orphans) == inflight for (orphans,inflight) in orphans_and_inflights)) - test_case.assertTrue(no_in_flight or all_orphaned) + assert no_in_flight or all_orphaned for holder in cluster.get_connection_holders(): for connection in holder.get_connections(): # all ids are unique req_ids = connection.request_ids orphan_ids = connection.orphaned_request_ids - test_case.assertEqual(len(req_ids), len(set(req_ids))) - test_case.assertEqual(connection.highest_request_id, len(req_ids) + len(orphan_ids) - 1) - test_case.assertEqual(connection.highest_request_id, max(chain(req_ids, orphan_ids))) + assert len(req_ids) == len(set(req_ids)) + assert connection.highest_request_id == len(req_ids) + len(orphan_ids) - 1 + assert connection.highest_request_id == max(chain(req_ids, orphan_ids)) if PROTOCOL_VERSION < 3: - test_case.assertEqual(connection.highest_request_id, connection.max_request_id) + assert connection.highest_request_id == connection.max_request_id From c233c262895fe01cf3207c338291ed85db5e4177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Bary=C5=82a?= Date: Thu, 17 Jul 2025 20:57:53 +0200 Subject: [PATCH 080/298] Test ConstantReconnectionPolicy: Fix the copy-paste bug This test compared the variable with itself. This is obviously an error. I think it meant to compare it with the configured delay. --- tests/unit/test_policies.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index 9f5d3d2b7c..c98511ab34 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -908,13 +908,13 @@ def test_schedule(self): Test ConstantReconnectionPolicy schedule """ - delay = 2 + configured_delay = 2 max_attempts = 100 - policy = ConstantReconnectionPolicy(delay=delay, max_attempts=max_attempts) + policy = ConstantReconnectionPolicy(delay=configured_delay, max_attempts=max_attempts) schedule = list(policy.new_schedule()) assert len(schedule) == max_attempts for i, delay in enumerate(schedule): - assert delay == delay + assert delay == configured_delay def test_schedule_negative_max_attempts(self): """ From 77b8684f1b6cae8ba111a87916bc548cf1be90b6 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Wed, 16 Jul 2025 23:20:56 -0400 Subject: [PATCH 081/298] Make sure that _read_watcher is not empty before start it Prevent following to happen: ``` File "cassandra/io/libevreactor.py", line 197, in _loop_will_run conn._read_watcher.start() AttributeError: 'NoneType' object has no attribute 'start' ``` --- cassandra/io/libevreactor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cassandra/io/libevreactor.py b/cassandra/io/libevreactor.py index 29039653f4..2a63396e64 100644 --- a/cassandra/io/libevreactor.py +++ b/cassandra/io/libevreactor.py @@ -194,7 +194,8 @@ def _loop_will_run(self, prepare): self._new_conns = set() for conn in to_start: - conn._read_watcher.start() + if conn._read_watcher: + conn._read_watcher.start() changed = True From 472b6796fc89b553fb4473d3e3135ea9e5e85b4e Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Wed, 16 Jul 2025 23:25:04 -0400 Subject: [PATCH 082/298] Make lbevreactor remove connection from new connections list on discard WHen connection is destroyed it is not removed from `self._new_conns`, which is why the following error happens: ``` File "/123/cassandra/io/libevreactor.py", line 197, in _loop_will_run conn._read_watcher.start() AttributeError: 'NoneType' object has no attribute 'start' ``` --- cassandra/io/libevreactor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cassandra/io/libevreactor.py b/cassandra/io/libevreactor.py index 2a63396e64..58c876fdcc 100644 --- a/cassandra/io/libevreactor.py +++ b/cassandra/io/libevreactor.py @@ -165,6 +165,10 @@ def connection_created(self, conn): def connection_destroyed(self, conn): with self._conn_set_lock: + new_conns = self._new_conns.copy() + new_conns.discard(conn) + self._new_conns = new_conns + new_live_conns = self._live_conns.copy() new_live_conns.discard(conn) self._live_conns = new_live_conns From 8acf8f9223a8e835887167d0361864b837679501 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Wed, 6 Aug 2025 17:29:22 +0300 Subject: [PATCH 083/298] (improvement) log a warning when importing of lz4 or snappy packages fails. If it is not available, the driver will silently not use compression. Not very very silently, as you will see in debug level only something like: "No available compression types supported on both ends. locally supported: odict_keys([]). remotely supported: ['lz4', 'snappy']" Make this log line a an error level log. Add to the import failure a debug level log. I think it wouldn't be too noisy and is clear enough for the developer. Signed-off-by: Yaniv Kaul --- cassandra/connection.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cassandra/connection.py b/cassandra/connection.py index 6c5e3e2645..7cd104ab29 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -64,6 +64,7 @@ try: import lz4 except ImportError: + log.debug("lz4 package could not be imported. LZ4 Compression will not be available") pass else: # The compress and decompress functions we need were moved from the lz4 to @@ -102,6 +103,7 @@ def lz4_decompress(byts): try: import snappy except ImportError: + log.debug("snappy package could not be imported. Snappy Compression will not be available") pass else: # work around apparently buggy snappy decompress @@ -1408,7 +1410,7 @@ def _handle_options_response(self, options_response): overlap = (set(locally_supported_compressions.keys()) & set(remote_supported_compressions)) if len(overlap) == 0: - log.debug("No available compression types supported on both ends." + log.error("No available compression types supported on both ends." " locally supported: %r. remotely supported: %r", locally_supported_compressions.keys(), remote_supported_compressions) From 1262d4350b93f474ba38936b6f0d8d631869a6c9 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Thu, 7 Aug 2025 12:10:25 +0300 Subject: [PATCH 084/298] (minor improvement): small refactoring in NetworkTopologyStrategy's make_token_replica_map() While trying to look at some random (flaky?) test (https://github.com/scylladb/python-driver/pull/510#issuecomment-3162760630 ) I saw some (minor) improvements that can be made to NetworkTopologyStrategy's make_token_replica_map(): 1. Remove some redundant len() calls to outside the loop(s) 2. Align some variable names, start with num_ ... for them. 3. Move token_offset and host assignment within the loop to closer to where it's used. 4. Only add DCs and hosts that are in the map All those are probably very very minor improvements, perhaps in a large cluster it'll be noticable. Signed-off-by: Yaniv Kaul --- cassandra/metadata.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/cassandra/metadata.py b/cassandra/metadata.py index 25d7561989..d07fdfb107 100644 --- a/cassandra/metadata.py +++ b/cassandra/metadata.py @@ -629,10 +629,14 @@ def make_token_replica_map(self, token_to_host_owner, ring): hosts_per_dc = defaultdict(set) for i, token in enumerate(ring): host = token_to_host_owner[token] - dc_to_token_offset[host.datacenter].append(i) - if host.datacenter and host.rack: - dc_racks[host.datacenter].add(host.rack) - hosts_per_dc[host.datacenter].add(host) + host_dc = host.datacenter + if host_dc in dc_rf_map: + # if the host is in a DC that has a replication factor, add it + # to the list of token offsets for that DC + dc_to_token_offset[host_dc].append(i) + if host.rack: + dc_racks[host_dc].add(host.rack) + hosts_per_dc[host_dc].add(host) # A map of DCs to an index into the dc_to_token_offset value for that dc. # This is how we keep track of advancing around the ring for each DC. @@ -644,8 +648,6 @@ def make_token_replica_map(self, token_to_host_owner, ring): # go through each DC and find the replicas in that DC for dc in dc_to_token_offset.keys(): - if dc not in dc_rf_map: - continue # advance our per-DC index until we're up to at least the # current token in the ring @@ -657,34 +659,34 @@ def make_token_replica_map(self, token_to_host_owner, ring): dc_to_current_index[dc] = index replicas_remaining = dc_rf_map[dc] - replicas_this_dc = 0 + num_replicas_this_dc = 0 skipped_hosts = [] racks_placed = set() - racks_this_dc = dc_racks[dc] - hosts_this_dc = len(hosts_per_dc[dc]) + num_racks_this_dc = len(dc_racks[dc]) + num_hosts_this_dc = len(hosts_per_dc[dc]) for token_offset_index in range(index, index+num_tokens): - if token_offset_index >= len(token_offsets): - token_offset_index = token_offset_index - len(token_offsets) + if replicas_remaining == 0 or num_replicas_this_dc == num_hosts_this_dc: + break + + if token_offset_index >= num_tokens: + token_offset_index = token_offset_index - num_tokens token_offset = token_offsets[token_offset_index] host = token_to_host_owner[ring[token_offset]] - if replicas_remaining == 0 or replicas_this_dc == hosts_this_dc: - break - if host in replicas: continue - if host.rack in racks_placed and len(racks_placed) < len(racks_this_dc): + if host.rack in racks_placed and len(racks_placed) < num_racks_this_dc: skipped_hosts.append(host) continue replicas.append(host) - replicas_this_dc += 1 + num_replicas_this_dc += 1 replicas_remaining -= 1 racks_placed.add(host.rack) - if len(racks_placed) == len(racks_this_dc): + if len(racks_placed) == num_racks_this_dc: for host in skipped_hosts: if replicas_remaining == 0: break From cad1e912bbbcec753e3f670916a2f5fc80c34996 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Mon, 11 Aug 2025 12:28:47 +0300 Subject: [PATCH 085/298] (minor improvement): small refactoring in SimpleStrategy's make_token_replica_map() While trying to look at some random (flaky?) test (https://github.com/scylladb/python-driver/pull/510#issuecomment-3162760630 ) I saw some (minor) improvements that can be made to SimpleStrategy's make_token_replica_map(): Specifically - remove some redundant len() calls to outside the loop(s) Probably very very minor improvement, perhaps in a large cluster it'll be noticable - but no one should use SimpleStrategy anyway! Signed-off-by: Yaniv Kaul --- cassandra/metadata.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cassandra/metadata.py b/cassandra/metadata.py index d07fdfb107..a8c0aea950 100644 --- a/cassandra/metadata.py +++ b/cassandra/metadata.py @@ -569,10 +569,11 @@ def __init__(self, options_map): def make_token_replica_map(self, token_to_host_owner, ring): replica_map = {} - for i in range(len(ring)): + ring_len = len(ring) + for i in range(ring_len): j, hosts = 0, list() - while len(hosts) < self.replication_factor and j < len(ring): - token = ring[(i + j) % len(ring)] + while len(hosts) < self.replication_factor and j < ring_len: + token = ring[(i + j) % ring_len] host = token_to_host_owner[token] if host not in hosts: hosts.append(host) From c8d35cac902ae59263f59a53468865fe9d78acf9 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sun, 10 Aug 2025 15:08:40 +0300 Subject: [PATCH 086/298] (Improvement) Remove in-memory Scylla tables We don't support it anymore, it's therefore a query we can remove altogether. Signed-off-by: Yaniv Kaul --- cassandra/metadata.py | 40 +++------------------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/cassandra/metadata.py b/cassandra/metadata.py index a8c0aea950..6379de069a 100644 --- a/cassandra/metadata.py +++ b/cassandra/metadata.py @@ -2075,7 +2075,6 @@ def __init__(self, connection, timeout, fetch_size, metadata_request_timeout): self.types_result = [] self.functions_result = [] self.aggregates_result = [] - self.scylla_result = [] self.keyspace_table_rows = defaultdict(list) self.keyspace_table_col_rows = defaultdict(lambda: defaultdict(list)) @@ -2083,7 +2082,6 @@ def __init__(self, connection, timeout, fetch_size, metadata_request_timeout): self.keyspace_func_rows = defaultdict(list) self.keyspace_agg_rows = defaultdict(list) self.keyspace_table_trigger_rows = defaultdict(lambda: defaultdict(list)) - self.keyspace_scylla_rows = defaultdict(lambda: defaultdict(list)) def get_all_keyspaces(self): self._query_all() @@ -2529,23 +2527,9 @@ def _query_all(self): self._aggregate_results() def _aggregate_results(self): - m = self.keyspace_scylla_rows - for row in self.scylla_result: - ksname = row["keyspace_name"] - cfname = row[self._table_name_col] - m[ksname][cfname].append(row) - m = self.keyspace_table_rows for row in self.tables_result: ksname = row["keyspace_name"] - cfname = row[self._table_name_col] - # in_memory property is stored in scylla private table - # add it to table properties if enabled - try: - if self.keyspace_scylla_rows[ksname][cfname][0]["in_memory"] == True: - row["in_memory"] = True - except (IndexError, KeyError): - pass m[ksname].append(row) m = self.keyspace_table_col_rows @@ -2591,7 +2575,6 @@ class SchemaParserV3(SchemaParserV22): _SELECT_FUNCTIONS = "SELECT * FROM system_schema.functions" _SELECT_AGGREGATES = "SELECT * FROM system_schema.aggregates" _SELECT_VIEWS = "SELECT * FROM system_schema.views" - _SELECT_SCYLLA = "SELECT * FROM system_schema.scylla_tables" _table_name_col = 'table_name' @@ -2646,9 +2629,6 @@ def get_table(self, keyspaces, keyspace, table): triggers_query = QueryMessage( query=maybe_add_timeout_to_query(self._SELECT_TRIGGERS + where_clause, self.metadata_request_timeout), consistency_level=cl, fetch_size=fetch_size) - scylla_query = QueryMessage( - query=maybe_add_timeout_to_query(self._SELECT_SCYLLA + where_clause, self.metadata_request_timeout), - consistency_level=cl, fetch_size=fetch_size) # in protocol v4 we don't know if this event is a view or a table, so we look for both where_clause = bind_params(" WHERE keyspace_name = %s AND view_name = %s", (keyspace, table), _encoder) @@ -2657,26 +2637,16 @@ def get_table(self, keyspaces, keyspace, table): consistency_level=cl, fetch_size=fetch_size) ((cf_success, cf_result), (col_success, col_result), (indexes_sucess, indexes_result), (triggers_success, triggers_result), - (view_success, view_result), - (scylla_success, scylla_result)) = ( + (view_success, view_result)) = ( self.connection.wait_for_responses( cf_query, col_query, indexes_query, triggers_query, - view_query, scylla_query, timeout=self.timeout, fail_on_error=False) + view_query, timeout=self.timeout, fail_on_error=False) ) table_result = self._handle_results(cf_success, cf_result, query_msg=cf_query) col_result = self._handle_results(col_success, col_result, query_msg=col_query) if table_result: indexes_result = self._handle_results(indexes_sucess, indexes_result, query_msg=indexes_query) triggers_result = self._handle_results(triggers_success, triggers_result, query_msg=triggers_query) - # in_memory property is stored in scylla private table - # add it to table properties if enabled - scylla_result = self._handle_results(scylla_success, scylla_result, expected_failures=(InvalidRequest,), - query_msg=scylla_query) - try: - if scylla_result[0]["in_memory"] == True: - table_result[0]["in_memory"] = True - except (IndexError, KeyError): - pass return self._build_table_metadata(table_result[0], col_result, triggers_result, indexes_result) view_result = self._handle_results(view_success, view_result, query_msg=view_query) @@ -2844,8 +2814,6 @@ def _query_all(self): fetch_size=fetch_size, consistency_level=cl), QueryMessage(query=maybe_add_timeout_to_query(self._SELECT_VIEWS, self.metadata_request_timeout), fetch_size=fetch_size, consistency_level=cl), - QueryMessage(query=maybe_add_timeout_to_query(self._SELECT_SCYLLA, self.metadata_request_timeout), - fetch_size=fetch_size, consistency_level=cl), ] ((ks_success, ks_result), @@ -2856,8 +2824,7 @@ def _query_all(self): (aggregates_success, aggregates_result), (triggers_success, triggers_result), (indexes_success, indexes_result), - (views_success, views_result), - (scylla_success, scylla_result)) = self.connection.wait_for_responses( + (views_success, views_result)) = self.connection.wait_for_responses( *queries, timeout=self.timeout, fail_on_error=False ) @@ -2870,7 +2837,6 @@ def _query_all(self): self.aggregates_result = self._handle_results(aggregates_success, aggregates_result, query_msg=queries[5]) self.indexes_result = self._handle_results(indexes_success, indexes_result, query_msg=queries[7]) self.views_result = self._handle_results(views_success, views_result, query_msg=queries[8]) - self.scylla_result = self._handle_results(scylla_success, scylla_result, expected_failures=(InvalidRequest,), query_msg=queries[9]) self._aggregate_results() From 9b46f1d71e6c36ae54d737c54d24d89e8e36a4bd Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 11 Aug 2025 09:12:04 -0400 Subject: [PATCH 087/298] cicd: update windows openssl lib Release pipeline is failing due to the lack of openssl library. https://slproweb.com don't keep old openssl versions. --- .github/workflows/lib-build-and-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index b68ef4eba5..624f89eb10 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -115,7 +115,7 @@ jobs: - name: Install OpenSSL for Windows if: runner.os == 'Windows' run: | - choco install openssl --version=3.5.1 -f -y --no-progress + choco install openssl --version=3.5.2 -f -y --no-progress - name: Install Conan if: runner.os == 'Windows' From a6a49c37ce2657e93e869c970e1b837abf11841b Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 11 Aug 2025 12:31:15 -0400 Subject: [PATCH 088/298] cicd: make SCYLLA_VERSION consistant --- .github/workflows/integration-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index b75ef57c42..0684a5c49b 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -23,6 +23,8 @@ jobs: name: test ${{ matrix.event_loop_manager }} (${{ matrix.python-version }}) if: "!contains(github.event.pull_request.labels.*.name, 'disable-integration-tests')" runs-on: ubuntu-24.04 + env: + SCYLLA_VERSION: release:2025.2 strategy: fail-fast: false matrix: @@ -61,13 +63,11 @@ jobs: # Not strictly necessary for running tests. - name: Download Scylla run: | - export SCYLLA_VERSION='release:6.2' uv run ccm create scylla-driver-temp -n 1 --scylla --version ${SCYLLA_VERSION} uv run ccm remove - name: Test with pytest run: | export EVENT_LOOP_MANAGER=${{ matrix.event_loop_manager }} - export SCYLLA_VERSION='release:2025.2' export PROTOCOL_VERSION=4 uv run pytest tests/integration/standard/ tests/integration/cqlengine/ From 84c854bfb19d92eab7efcaac638389a0e4d18717 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 8 Aug 2025 19:11:15 +0300 Subject: [PATCH 089/298] pyproject.toml: add optional Snappy and LZ4 package dependencies. Fixes: https://github.com/scylladb/python-driver/issues/515 Signed-off-by: Yaniv Kaul --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 196a25b45c..f0664df914 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,8 @@ requires-python = ">=3.9" [project.optional-dependencies] graph = ['gremlinpython==3.4.6'] cle = ['cryptography>=35.0'] +compress-lz4 = ['lz4'] +compress-snappy = ['python-snappy'] [dependency-groups] dev = [ From dfbe1017e74bc1c57bfca7a0ca303f04d72b947e Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Wed, 16 Jul 2025 23:04:28 +0300 Subject: [PATCH 090/298] Remove protocol versions >5 from MAX_SUPPORTED The main goal of this patch is to have the driver start negotiating protocol versions at 5, and not begin by attempting DSE-specific protocols or protocol version 6, which Scylla doesn't support and cause protocol errors which only wastes time in the beginning of each connection. But then, since the driver won't even try DSE-specific protocols, we don't need to support them and can begin to remove DSE-specific code. This patch only removes some obvious things, but there's still a bunch of DSE-specific code that hasn't been removed yet. Refs: #244 Signed-off-by: Yaniv Kaul --- cassandra/__init__.py | 12 +----- cassandra/cluster.py | 35 +++------------ cassandra/connection.py | 27 ------------ cassandra/protocol.py | 17 ++------ tests/integration/__init__.py | 3 +- .../simulacron/test_empty_column.py | 4 -- tests/integration/standard/test_cluster.py | 43 +------------------ tests/integration/standard/test_metadata.py | 15 +------ tests/unit/test_cluster.py | 7 --- tests/unit/test_connection.py | 8 ++-- tests/unit/test_protocol.py | 22 +--------- 11 files changed, 21 insertions(+), 172 deletions(-) diff --git a/cassandra/__init__.py b/cassandra/__init__.py index c1ee195aa2..6f5fd372fc 100644 --- a/cassandra/__init__.py +++ b/cassandra/__init__.py @@ -170,9 +170,9 @@ class ProtocolVersion(object): DSE private protocol v2, supported in DSE 6.0+ """ - SUPPORTED_VERSIONS = (DSE_V2, DSE_V1, V6, V5, V4, V3) + SUPPORTED_VERSIONS = (V5, V4, V3) """ - A tuple of all supported protocol versions + A tuple of all supported protocol versions for ScyllaDB, including future v5 version. """ BETA_VERSIONS = (V6,) @@ -223,14 +223,6 @@ def uses_error_code_map(cls, version): def uses_keyspace_flag(cls, version): return version >= cls.V5 and version != cls.DSE_V1 - @classmethod - def has_continuous_paging_support(cls, version): - return version >= cls.DSE_V1 - - @classmethod - def has_continuous_paging_next_pages(cls, version): - return version >= cls.DSE_V2 - @classmethod def has_checksumming_support(cls, version): return cls.V5 <= version < cls.DSE_V1 diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 68ab370f93..8e23cba197 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -51,7 +51,7 @@ from cassandra.connection import (ConnectionException, ConnectionShutdown, ConnectionHeartbeat, ProtocolVersionUnsupported, EndPoint, DefaultEndPoint, DefaultEndPointFactory, - ContinuousPagingState, SniEndPointFactory, ConnectionBusy) + SniEndPointFactory, ConnectionBusy) from cassandra.cqltypes import UserType import cassandra.cqltypes as types from cassandra.encoder import Encoder @@ -672,7 +672,7 @@ class Cluster(object): server will be automatically used. """ - protocol_version = ProtocolVersion.DSE_V2 + protocol_version = ProtocolVersion.V5 """ The maximum version of the native protocol to use. @@ -680,7 +680,7 @@ class Cluster(object): If not set in the constructor, the driver will automatically downgrade version based on a negotiation with the server, but it is most efficient - to set this to the maximum supported by your version of Cassandra. + to set this to the maximum supported by your version of ScyllaDB. Setting this will also prevent conflicting versions negotiated if your cluster is upgraded. @@ -2692,7 +2692,6 @@ def __init__(self, cluster, hosts, keyspace=None): raise NoHostAvailable(msg, [h.address for h in hosts]) self.session_id = uuid.uuid4() - self._graph_paging_available = self._check_graph_paging_available() if self.cluster.column_encryption_policy is not None: try: @@ -2889,26 +2888,10 @@ def execute_graph_async(self, query, parameters=None, trace=False, execution_pro def _maybe_set_graph_paging(self, execution_profile): graph_paging = execution_profile.continuous_paging_options if execution_profile.continuous_paging_options is _NOT_SET: - graph_paging = ContinuousPagingOptions() if self._graph_paging_available else None + graph_paging = None execution_profile.continuous_paging_options = graph_paging - def _check_graph_paging_available(self): - """Verify if we can enable graph paging. This executed only once when the session is created.""" - - if not ProtocolVersion.has_continuous_paging_next_pages(self._protocol_version): - return False - - for host in self.cluster.metadata.all_hosts(): - if host.dse_version is None: - return False - - version = Version(host.dse_version) - if version < _GRAPH_PAGING_MIN_DSE_VERSION: - return False - - return True - def _resolve_execution_profile_options(self, execution_profile): """ Determine the GraphSON protocol and row factory for a graph query. This is useful @@ -3053,14 +3036,6 @@ def _create_response_future(self, query, parameters, trace, custom_payload, else: timestamp = None - supports_continuous_paging_state = ( - ProtocolVersion.has_continuous_paging_next_pages(self._protocol_version) - ) - if continuous_paging_options and supports_continuous_paging_state: - continuous_paging_state = ContinuousPagingState(continuous_paging_options.max_queue_size) - else: - continuous_paging_state = None - if isinstance(query, SimpleStatement): query_string = query.query_string statement_keyspace = query.keyspace if ProtocolVersion.uses_keyspace_flag(self._protocol_version) else None @@ -3104,7 +3079,7 @@ def _create_response_future(self, query, parameters, trace, custom_payload, self, message, query, timeout, metrics=self._metrics, prepared_statement=prepared_statement, retry_policy=retry_policy, row_factory=row_factory, load_balancer=load_balancing_policy, start_time=start_time, speculative_execution_plan=spec_exec_plan, - continuous_paging_state=continuous_paging_state, host=host) + continuous_paging_state=None, host=host) def get_execution_profile(self, name): """ diff --git a/cassandra/connection.py b/cassandra/connection.py index 7cd104ab29..39baeea884 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -445,33 +445,6 @@ class ProtocolError(Exception): class CrcMismatchException(ConnectionException): pass - -class ContinuousPagingState(object): - """ - A class for specifying continuous paging state, only supported starting with DSE_V2. - """ - - num_pages_requested = None - """ - How many pages we have already requested - """ - - num_pages_received = None - """ - How many pages we have already received - """ - - max_queue_size = None - """ - The max queue size chosen by the user via the options - """ - - def __init__(self, max_queue_size): - self.num_pages_requested = max_queue_size # the initial query requests max_queue_size - self.num_pages_received = 0 - self.max_queue_size = max_queue_size - - class ContinuousPagingSession(object): def __init__(self, stream_id, decoder, row_factory, connection, state): self.stream_id = stream_id diff --git a/cassandra/protocol.py b/cassandra/protocol.py index 5fe4ed2be4..e9240cd74f 100644 --- a/cassandra/protocol.py +++ b/cassandra/protocol.py @@ -575,12 +575,8 @@ def _write_query_params(self, f, protocol_version): flags |= _PROTOCOL_TIMESTAMP_FLAG if self.continuous_paging_options: - if ProtocolVersion.has_continuous_paging_support(protocol_version): - flags |= _PAGING_OPTIONS_FLAG - else: - raise UnsupportedOperation( - "Continuous paging may only be used with protocol version " - "ProtocolVersion.DSE_V1 or higher. Consider setting Cluster.protocol_version to ProtocolVersion.DSE_V1.") + raise UnsupportedOperation( + "Continuous paging may only be used with future protocol versions") if self.keyspace is not None: if ProtocolVersion.uses_keyspace_flag(protocol_version): @@ -615,8 +611,6 @@ def _write_query_params(self, f, protocol_version): def _write_paging_options(self, f, paging_options, protocol_version): write_int(f, paging_options.max_pages) write_int(f, paging_options.max_pages_per_second) - if ProtocolVersion.has_continuous_paging_next_pages(protocol_version): - write_int(f, paging_options.max_queue_size) class QueryMessage(_QueryMessage): @@ -1050,12 +1044,9 @@ def send_body(self, f, protocol_version): if self.op_type == ReviseRequestMessage.RevisionType.PAGING_BACKPRESSURE: if self.next_pages <= 0: raise UnsupportedOperation("Continuous paging backpressure requires next_pages > 0") - elif not ProtocolVersion.has_continuous_paging_next_pages(protocol_version): - raise UnsupportedOperation( - "Continuous paging backpressure may only be used with protocol version " - "ProtocolVersion.DSE_V2 or higher. Consider setting Cluster.protocol_version to ProtocolVersion.DSE_V2.") else: - write_int(f, self.next_pages) + raise UnsupportedOperation( + "Continuous paging backpressure is not supported.") class _ProtocolHandler(object): diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index e01029a7cd..7a8845750e 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -193,7 +193,7 @@ def get_supported_protocol_versions(): 4.0(C*) -> 6(beta),5,4,3 ` """ if CASSANDRA_VERSION >= Version('4.0-beta5'): - return (3, 4, 5, 6) + return (3, 4, 5) if CASSANDRA_VERSION >= Version('4.0-a'): return (3, 4, 5) elif CASSANDRA_VERSION >= Version('3.10'): @@ -269,7 +269,6 @@ def xfail_scylla_version(filter: Callable[[Version], bool], reason: str, *args, local = local_decorator_creator() notprotocolv1 = unittest.skipUnless(PROTOCOL_VERSION > 1, 'Protocol v1 not supported') greaterthanprotocolv3 = unittest.skipUnless(PROTOCOL_VERSION >= 4, 'Protocol versions less than 4 are not supported') -protocolv6 = unittest.skipUnless(6 in get_supported_protocol_versions(), 'Protocol versions less than 6 are not supported') greaterthancass20 = unittest.skipUnless(CASSANDRA_VERSION >= Version('2.1'), 'Cassandra version 2.1 or greater required') greaterthancass21 = unittest.skipUnless(CASSANDRA_VERSION >= Version('2.2'), 'Cassandra version 2.2 or greater required') diff --git a/tests/integration/simulacron/test_empty_column.py b/tests/integration/simulacron/test_empty_column.py index 589f730f04..2dbf3985ad 100644 --- a/tests/integration/simulacron/test_empty_column.py +++ b/tests/integration/simulacron/test_empty_column.py @@ -29,10 +29,6 @@ from tests.integration.simulacron.utils import PrimeQuery, prime_request -PROTOCOL_VERSION = 4 if PROTOCOL_VERSION in \ - (ProtocolVersion.DSE_V1, ProtocolVersion.DSE_V2) else PROTOCOL_VERSION - - @requiressimulacron class EmptyColumnTests(SimulacronCluster): """ diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index c83f454a0f..35ad5312f3 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -42,7 +42,7 @@ from tests import notwindows, notasyncio from tests.integration import use_cluster, get_server_versions, CASSANDRA_VERSION, \ execute_until_pass, execute_with_long_wait_retry, get_node, MockLoggingHandler, get_unsupported_lower_protocol, \ - get_unsupported_upper_protocol, protocolv6, local, CASSANDRA_IP, greaterthanorequalcass30, \ + get_unsupported_upper_protocol, local, CASSANDRA_IP, greaterthanorequalcass30, \ lessthanorequalcass40, TestCluster, PROTOCOL_VERSION, xfail_scylla, incorrect_test from tests.integration.util import assert_quiescent_pool_state from tests.util import assertListEqual @@ -1478,47 +1478,6 @@ def test_prepare_on_ignored_hosts(self): cluster.shutdown() -@protocolv6 -class BetaProtocolTest(unittest.TestCase): - - @protocolv6 - def test_invalid_protocol_version_beta_option(self): - """ - Test cluster connection with protocol v6 and beta flag not set - - @since 3.7.0 - @jira_ticket PYTHON-614, PYTHON-1232 - @expected_result client shouldn't connect with V6 and no beta flag set - - @test_category connection - """ - - - cluster = TestCluster(protocol_version=cassandra.ProtocolVersion.V6, allow_beta_protocol_version=False) - try: - with pytest.raises(NoHostAvailable): - cluster.connect() - except Exception as e: - pytest.fail("Unexpected error encountered {0}".format(e.message)) - - @protocolv6 - def test_valid_protocol_version_beta_options_connect(self): - """ - Test cluster connection with protocol version 5 and beta flag set - - @since 3.7.0 - @jira_ticket PYTHON-614, PYTHON-1232 - @expected_result client should connect with protocol v6 and beta flag set. - - @test_category connection - """ - cluster = Cluster(protocol_version=cassandra.ProtocolVersion.V6, allow_beta_protocol_version=True) - session = cluster.connect() - assert cluster.protocol_version == cassandra.ProtocolVersion.V6 - assert session.execute("select release_version from system.local").one() - cluster.shutdown() - - class DeprecationWarningTest(unittest.TestCase): def test_deprecation_warnings_legacy_parameters(self): """ diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index c27d18dccb..7b675fdf7b 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -2541,16 +2541,5 @@ def _assert_group_keys_by_host(self, keys, table_name, stmt): for key in keys: routing_key = prepared_stmt.bind(key).routing_key hosts = self.cluster.metadata.get_replicas(self.ks_name, routing_key) - assert 1 == len(hosts) # RF is 1 for this keyspace - assert key in keys_per_host[hosts[0]] - - -class VirtualKeypaceTest(BasicSharedKeyspaceUnitTestCase): - virtual_ks_names = ('system_virtual_schema', 'system_views') - - def test_existing_keyspaces_have_correct_virtual_tags(self): - for name, ks in self.cluster.metadata.keyspaces.items(): - if name in self.virtual_ks_names: - assert ks.virtual, 'incorrect .virtual value for {}'.format(name) - else: - assert not ks.virtual, 'incorrect .virtual value for {}'.format(name) + self.assertEqual(1, len(hosts)) # RF is 1 for this keyspace + self.assertIn(key, keys_per_host[hosts[0]]) diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 180f4b6a8a..383a4de7c8 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -208,10 +208,6 @@ def test_default_serial_consistency_level_legacy(self, *_): class ProtocolVersionTests(unittest.TestCase): def test_protocol_downgrade_test(self): - lower = ProtocolVersion.get_lower_supported(ProtocolVersion.DSE_V2) - assert ProtocolVersion.DSE_V1 == lower - lower = ProtocolVersion.get_lower_supported(ProtocolVersion.DSE_V1) - assert ProtocolVersion.V5 == lower lower = ProtocolVersion.get_lower_supported(ProtocolVersion.V5) assert ProtocolVersion.V4 == lower lower = ProtocolVersion.get_lower_supported(ProtocolVersion.V4) @@ -219,9 +215,6 @@ def test_protocol_downgrade_test(self): lower = ProtocolVersion.get_lower_supported(ProtocolVersion.V3) assert 0 == lower - assert ProtocolVersion.uses_error_code_map(ProtocolVersion.DSE_V1) - assert ProtocolVersion.uses_int_query_flags(ProtocolVersion.DSE_V1) - assert not ProtocolVersion.uses_error_code_map(ProtocolVersion.V4) assert not ProtocolVersion.uses_int_query_flags(ProtocolVersion.V4) diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index c21f5c7212..1d31b81a31 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -189,15 +189,14 @@ def test_use_requested_compression(self, *args): c = self.make_connection() c._requests = {0: (c._handle_options_response, ProtocolHandler.decode_message, [])} c.defunct = Mock() - # request snappy compression - c.compression = "snappy" + # request LZ4 compression + c.compression = "lz4" locally_supported_compressions.pop('lz4', None) locally_supported_compressions.pop('snappy', None) locally_supported_compressions['lz4'] = ('lz4compress', 'lz4decompress') locally_supported_compressions['snappy'] = ('snappycompress', 'snappydecompress') - # the server only supports snappy options_buf = BytesIO() write_stringmultimap(options_buf, { 'CQL_VERSION': ['3.0.3'], @@ -207,7 +206,8 @@ def test_use_requested_compression(self, *args): c.process_msg(_Frame(version=4, flags=0, stream=0, opcode=SupportedMessage.opcode, body_offset=9, end_pos=9 + len(options)), options) - assert c.decompressor == locally_supported_compressions['snappy'][1] + + assert c.decompressor == locally_supported_compressions['lz4'][1] def test_disable_compression(self, *args): c = self.make_connection() diff --git a/tests/unit/test_protocol.py b/tests/unit/test_protocol.py index 57261654df..6a42fbf11b 100644 --- a/tests/unit/test_protocol.py +++ b/tests/unit/test_protocol.py @@ -93,9 +93,7 @@ def _check_calls(self, io, expected): def test_continuous_paging(self): """ - Test to check continuous paging throws an Exception if it's not supported and the correct valuesa - are written to the buffer if the option is enabled. - + Test to check continuous paging throws an Exception as it's not supported @since DSE 2.0b3 GRAPH 1.0b1 @jira_ticket PYTHON-694 @expected_result the values are correctly written @@ -108,26 +106,10 @@ def test_continuous_paging(self): max_pages_per_second=max_pages_per_second) message = QueryMessage("a", 3, continuous_paging_options=continuous_paging_options) io = Mock() - for version in [version for version in ProtocolVersion.SUPPORTED_VERSIONS - if not ProtocolVersion.has_continuous_paging_support(version)]: + for version in ProtocolVersion.SUPPORTED_VERSIONS: with pytest.raises(UnsupportedOperation): message.send_body(io, version) - io.reset_mock() - message.send_body(io, ProtocolVersion.DSE_V1) - - # continuous paging adds two write calls to the buffer - assert len(io.write.mock_calls) == 6 - # Check that the appropriate flag is set to True - assert uint32_unpack(io.write.mock_calls[3][1][0]) & _WITH_SERIAL_CONSISTENCY_FLAG == 0 - assert uint32_unpack(io.write.mock_calls[3][1][0]) & _PAGE_SIZE_FLAG == 0 - assert uint32_unpack(io.write.mock_calls[3][1][0]) & _WITH_PAGING_STATE_FLAG == 0 - assert uint32_unpack(io.write.mock_calls[3][1][0]) & _PAGING_OPTIONS_FLAG == _PAGING_OPTIONS_FLAG - - # Test max_pages and max_pages_per_second are correctly written - assert uint32_unpack(io.write.mock_calls[4][1][0]) == max_pages - assert uint32_unpack(io.write.mock_calls[5][1][0]) == max_pages_per_second - def test_prepare_flag(self): """ Test to check the prepare flag is properly set, This should only happen for V5 at the moment. From 237163461a864bb3ed79917fecc696bd691b7443 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Thu, 17 Jul 2025 11:30:14 +0300 Subject: [PATCH 091/298] Remove DSE only continuous_paging_options feature I'm not removing it entirely from the code, but ignoring it where it matters, so it's essentially useless. I think it's a reasonable balance between breaking the API of some classes and removal of the feature. Signed-off-by: Yaniv Kaul --- cassandra/cluster.py | 1 - cassandra/protocol.py | 7 ----- .../standard/test_custom_protocol_handler.py | 31 ------------------- tests/unit/test_protocol.py | 19 ------------ 4 files changed, 58 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 8e23cba197..e38ed3306c 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -4498,7 +4498,6 @@ class ResponseFuture(object): _timer = None _protocol_handler = ProtocolHandler _spec_execution_plan = NoSpeculativeExecutionPlan() - _continuous_paging_options = None _continuous_paging_session = None _host = None diff --git a/cassandra/protocol.py b/cassandra/protocol.py index e9240cd74f..d8716f4eeb 100644 --- a/cassandra/protocol.py +++ b/cassandra/protocol.py @@ -553,7 +553,6 @@ def __init__(self, query_params, consistency_level, self.paging_state = paging_state self.timestamp = timestamp self.skip_meta = skip_meta - self.continuous_paging_options = continuous_paging_options self.keyspace = keyspace def _write_query_params(self, f, protocol_version): @@ -574,10 +573,6 @@ def _write_query_params(self, f, protocol_version): if self.timestamp is not None: flags |= _PROTOCOL_TIMESTAMP_FLAG - if self.continuous_paging_options: - raise UnsupportedOperation( - "Continuous paging may only be used with future protocol versions") - if self.keyspace is not None: if ProtocolVersion.uses_keyspace_flag(protocol_version): flags |= _WITH_KEYSPACE_FLAG @@ -605,8 +600,6 @@ def _write_query_params(self, f, protocol_version): write_long(f, self.timestamp) if self.keyspace is not None: write_string(f, self.keyspace) - if self.continuous_paging_options: - self._write_paging_options(f, self.continuous_paging_options, protocol_version) def _write_paging_options(self, f, paging_options, protocol_version): write_int(f, paging_options.max_pages) diff --git a/tests/integration/standard/test_custom_protocol_handler.py b/tests/integration/standard/test_custom_protocol_handler.py index a9025bba97..239f7e7336 100644 --- a/tests/integration/standard/test_custom_protocol_handler.py +++ b/tests/integration/standard/test_custom_protocol_handler.py @@ -121,37 +121,6 @@ def test_custom_raw_row_results_all_types(self): assert len(CustomResultMessageTracked.checked_rev_row_set) == len(PRIMITIVE_DATATYPES)-1 cluster.shutdown() - @unittest.expectedFailure - @greaterthanorequalcass40 - def test_protocol_divergence_v5_fail_by_continuous_paging(self): - """ - Test to validate that V5 and DSE_V1 diverge. ContinuousPagingOptions is not supported by V5 - - @since DSE 2.0b3 GRAPH 1.0b1 - @jira_ticket PYTHON-694 - @expected_result NoHostAvailable will be risen when the continuous_paging_options parameter is set - - @test_category connection - """ - cluster = TestCluster(protocol_version=ProtocolVersion.V5, allow_beta_protocol_version=True) - session = cluster.connect() - - max_pages = 4 - max_pages_per_second = 3 - continuous_paging_options = ContinuousPagingOptions(max_pages=max_pages, - max_pages_per_second=max_pages_per_second) - - future = self._send_query_message(session, timeout=session.default_timeout, - consistency_level=ConsistencyLevel.ONE, - continuous_paging_options=continuous_paging_options) - - # This should raise NoHostAvailable because continuous paging is not supported under ProtocolVersion.DSE_V1 - with pytest.raises(NoHostAvailable) as context: - future.result() - assert "Continuous paging may only be used with protocol version ProtocolVersion.DSE_V1 or higher" in str(context.value) - - cluster.shutdown() - @greaterthanorequalcass30 def test_protocol_divergence_v4_fail_by_flag_uses_int(self): """ diff --git a/tests/unit/test_protocol.py b/tests/unit/test_protocol.py index 6a42fbf11b..9704811239 100644 --- a/tests/unit/test_protocol.py +++ b/tests/unit/test_protocol.py @@ -91,25 +91,6 @@ def test_query_message(self): def _check_calls(self, io, expected): assert tuple(c[1] for c in io.write.mock_calls) == tuple(expected) - def test_continuous_paging(self): - """ - Test to check continuous paging throws an Exception as it's not supported - @since DSE 2.0b3 GRAPH 1.0b1 - @jira_ticket PYTHON-694 - @expected_result the values are correctly written - - @test_category connection - """ - max_pages = 4 - max_pages_per_second = 3 - continuous_paging_options = ContinuousPagingOptions(max_pages=max_pages, - max_pages_per_second=max_pages_per_second) - message = QueryMessage("a", 3, continuous_paging_options=continuous_paging_options) - io = Mock() - for version in ProtocolVersion.SUPPORTED_VERSIONS: - with pytest.raises(UnsupportedOperation): - message.send_body(io, version) - def test_prepare_flag(self): """ Test to check the prepare flag is properly set, This should only happen for V5 at the moment. From 8fec502c637a9d376c292f9794ec521ab27a4d9e Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Tue, 12 Aug 2025 12:47:44 +0300 Subject: [PATCH 092/298] Fix test_use_requested_compression() to use Protocol version 4 Per review comments, reverted the changes to test, but changed it to explicitly use v4 of the protocol. --- tests/unit/test_connection.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 1d31b81a31..97dbe39957 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -33,8 +33,8 @@ class ConnectionTest(unittest.TestCase): - def make_connection(self): - c = Connection(DefaultEndPoint('1.2.3.4')) + def make_connection(self, **kwargs): + c = Connection(DefaultEndPoint('1.2.3.4'), **kwargs) c._socket = Mock() c._socket.send.side_effect = lambda x: len(x) return c @@ -186,17 +186,18 @@ def test_requested_compression_not_available(self, *args): assert isinstance(args[0], ProtocolError) def test_use_requested_compression(self, *args): - c = self.make_connection() + c = self.make_connection(protocol_version=4) c._requests = {0: (c._handle_options_response, ProtocolHandler.decode_message, [])} c.defunct = Mock() - # request LZ4 compression - c.compression = "lz4" + # request snappy compression + c.compression = "snappy" locally_supported_compressions.pop('lz4', None) locally_supported_compressions.pop('snappy', None) locally_supported_compressions['lz4'] = ('lz4compress', 'lz4decompress') locally_supported_compressions['snappy'] = ('snappycompress', 'snappydecompress') + # the server only supports snappy options_buf = BytesIO() write_stringmultimap(options_buf, { 'CQL_VERSION': ['3.0.3'], @@ -206,8 +207,7 @@ def test_use_requested_compression(self, *args): c.process_msg(_Frame(version=4, flags=0, stream=0, opcode=SupportedMessage.opcode, body_offset=9, end_pos=9 + len(options)), options) - - assert c.decompressor == locally_supported_compressions['lz4'][1] + assert c.decompressor == locally_supported_compressions['snappy'][1] def test_disable_compression(self, *args): c = self.make_connection() From 8a6d95b6f8a3a5a5e3e8d102ddeaa38897f32120 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Wed, 13 Aug 2025 13:09:21 +0300 Subject: [PATCH 093/298] (improvement) set monitor_reporting_enabled False monitor_reporting_enabled is a DSE feature. ScyllaDB doesn't support it, so we can set the default to False. Signed-off-by: Yaniv Kaul --- cassandra/cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index e38ed3306c..15885ff9a9 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -1036,7 +1036,7 @@ def default_retry_policy(self, policy): documentation for :meth:`Session.timestamp_generator`. """ - monitor_reporting_enabled = True + monitor_reporting_enabled = False """ A boolean indicating if monitor reporting, which sends gathered data to Insights when running against DSE 6.8 and higher. From 0fa952086cdb982fc1d49f8ea6d303c0af8a05f0 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Wed, 13 Aug 2025 13:24:23 +0300 Subject: [PATCH 094/298] (cleanup) remove code related to v1,v2 protocols I'm not sure why I did not remove it as part of https://github.com/scylladb/python-driver/pull/493 Signed-off-by: Yaniv Kaul --- cassandra/cluster.py | 88 ------------------- cassandra/concurrent.py | 8 +- docs/api/cassandra/cluster.rst | 8 -- tests/integration/standard/test_cluster.py | 2 - tests/integration/standard/test_concurrent.py | 2 - tests/integration/standard/test_query.py | 4 - .../integration/standard/test_query_paging.py | 2 - tests/unit/test_host_connection_pool.py | 5 -- 8 files changed, 1 insertion(+), 118 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 15885ff9a9..28235e0823 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -192,15 +192,6 @@ def _connection_reduce_fn(val,import_fn): log = logging.getLogger(__name__) -DEFAULT_MIN_REQUESTS = 5 -DEFAULT_MAX_REQUESTS = 100 - -DEFAULT_MIN_CONNECTIONS_PER_LOCAL_HOST = 2 -DEFAULT_MAX_CONNECTIONS_PER_LOCAL_HOST = 8 - -DEFAULT_MIN_CONNECTIONS_PER_REMOTE_HOST = 1 -DEFAULT_MAX_CONNECTIONS_PER_REMOTE_HOST = 2 - _GRAPH_PAGING_MIN_DSE_VERSION = Version('6.8.0') _NOT_SET = object() @@ -1449,18 +1440,6 @@ def __init__(self, self._user_types = defaultdict(dict) - self._core_connections_per_host = { - HostDistance.LOCAL_RACK: DEFAULT_MIN_CONNECTIONS_PER_LOCAL_HOST, - HostDistance.LOCAL: DEFAULT_MIN_CONNECTIONS_PER_LOCAL_HOST, - HostDistance.REMOTE: DEFAULT_MIN_CONNECTIONS_PER_REMOTE_HOST - } - - self._max_connections_per_host = { - HostDistance.LOCAL_RACK: DEFAULT_MAX_CONNECTIONS_PER_LOCAL_HOST, - HostDistance.LOCAL: DEFAULT_MAX_CONNECTIONS_PER_LOCAL_HOST, - HostDistance.REMOTE: DEFAULT_MAX_CONNECTIONS_PER_REMOTE_HOST - } - self.executor = self._create_thread_pool_executor(max_workers=executor_threads) self.scheduler = _Scheduler(self.executor) @@ -1651,73 +1630,6 @@ def add_execution_profile(self, name, profile, pool_wait_timeout=5): if not_done: raise OperationTimedOut("Failed to create all new connection pools in the %ss timeout.") - def get_core_connections_per_host(self, host_distance): - """ - Gets the minimum number of connections per Session that will be opened - for each host with :class:`~.HostDistance` equal to `host_distance`. - The default is 2 for :attr:`~HostDistance.LOCAL` and 1 for - :attr:`~HostDistance.REMOTE`. - - This property is ignored if :attr:`~.Cluster.protocol_version` is - 3 or higher. - """ - return self._core_connections_per_host[host_distance] - - def set_core_connections_per_host(self, host_distance, core_connections): - """ - Sets the minimum number of connections per Session that will be opened - for each host with :class:`~.HostDistance` equal to `host_distance`. - The default is 2 for :attr:`~HostDistance.LOCAL` and 1 for - :attr:`~HostDistance.REMOTE`. - - Protocol version 1 and 2 are limited in the number of concurrent - requests they can send per connection. The driver implements connection - pooling to support higher levels of concurrency. - - If :attr:`~.Cluster.protocol_version` is set to 3 or higher, this - is not supported (there is always one connection per host, unless - the host is remote and :attr:`connect_to_remote_hosts` is :const:`False`) - and using this will result in an :exc:`~.UnsupportedOperation`. - """ - if self.protocol_version >= 3: - raise UnsupportedOperation( - "Cluster.set_core_connections_per_host() only has an effect " - "when using protocol_version 1 or 2.") - old = self._core_connections_per_host[host_distance] - self._core_connections_per_host[host_distance] = core_connections - if old < core_connections: - self._ensure_core_connections() - - def get_max_connections_per_host(self, host_distance): - """ - Gets the maximum number of connections per Session that will be opened - for each host with :class:`~.HostDistance` equal to `host_distance`. - The default is 8 for :attr:`~HostDistance.LOCAL` and 2 for - :attr:`~HostDistance.REMOTE`. - - This property is ignored if :attr:`~.Cluster.protocol_version` is - 3 or higher. - """ - return self._max_connections_per_host[host_distance] - - def set_max_connections_per_host(self, host_distance, max_connections): - """ - Sets the maximum number of connections per Session that will be opened - for each host with :class:`~.HostDistance` equal to `host_distance`. - The default is 2 for :attr:`~HostDistance.LOCAL` and 1 for - :attr:`~HostDistance.REMOTE`. - - If :attr:`~.Cluster.protocol_version` is set to 3 or higher, this - is not supported (there is always one connection per host, unless - the host is remote and :attr:`connect_to_remote_hosts` is :const:`False`) - and using this will result in an :exc:`~.UnsupportedOperation`. - """ - if self.protocol_version >= 3: - raise UnsupportedOperation( - "Cluster.set_max_connections_per_host() only has an effect " - "when using protocol_version 1 or 2.") - self._max_connections_per_host[host_distance] = max_connections - def connection_factory(self, endpoint, host_conn = None, *args, **kwargs): """ Called to create a new connection with proper configuration. diff --git a/cassandra/concurrent.py b/cassandra/concurrent.py index fb8f26e1cc..d6345ca452 100644 --- a/cassandra/concurrent.py +++ b/cassandra/concurrent.py @@ -33,13 +33,7 @@ def execute_concurrent(session, statements_and_parameters, concurrency=100, rais ``parameters`` item must be a sequence or :const:`None`. The `concurrency` parameter controls how many statements will be executed - concurrently. When :attr:`.Cluster.protocol_version` is set to 1 or 2, - it is recommended that this be kept below 100 times the number of - core connections per host times the number of connected hosts (see - :meth:`.Cluster.set_core_connections_per_host`). If that amount is exceeded, - the event loop thread may attempt to block on new connection creation, - substantially impacting throughput. If :attr:`~.Cluster.protocol_version` - is 3 or higher, you can safely experiment with higher levels of concurrency. + concurrently. If `raise_on_first_error` is left as :const:`True`, execution will stop after the first failed statement and the corresponding exception will be diff --git a/docs/api/cassandra/cluster.rst b/docs/api/cassandra/cluster.rst index 81da28c052..01a1e414a0 100644 --- a/docs/api/cassandra/cluster.rst +++ b/docs/api/cassandra/cluster.rst @@ -86,14 +86,6 @@ .. automethod:: add_execution_profile - .. automethod:: get_core_connections_per_host - - .. automethod:: set_core_connections_per_host - - .. automethod:: get_max_connections_per_host - - .. automethod:: set_max_connections_per_host - .. automethod:: get_control_connection_host .. automethod:: refresh_schema_metadata diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 35ad5312f3..d7f89ad598 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -729,8 +729,6 @@ def test_idle_heartbeat(self): interval = 2 cluster = TestCluster(idle_heartbeat_interval=interval, monitor_reporting_enabled=False) - if PROTOCOL_VERSION < 3: - cluster.set_core_connections_per_host(HostDistance.LOCAL, 1) session = cluster.connect(wait_for_all_pools=True) # This test relies on impl details of connection req id management to see if heartbeats diff --git a/tests/integration/standard/test_concurrent.py b/tests/integration/standard/test_concurrent.py index e4bd379dee..5e6b1ffd59 100644 --- a/tests/integration/standard/test_concurrent.py +++ b/tests/integration/standard/test_concurrent.py @@ -46,8 +46,6 @@ def setUpClass(cls): EXEC_PROFILE_DICT: ExecutionProfile(row_factory=dict_factory) } ) - if PROTOCOL_VERSION < 3: - cls.cluster.set_core_connections_per_host(HostDistance.LOCAL, 1) cls.session = cls.cluster.connect() @classmethod diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index a16a34233a..a3bdf8a735 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -664,8 +664,6 @@ def setUp(self): % (PROTOCOL_VERSION,)) self.cluster = TestCluster() - if PROTOCOL_VERSION < 3: - self.cluster.set_core_connections_per_host(HostDistance.LOCAL, 1) self.session = self.cluster.connect(wait_for_all_pools=True) def tearDown(self): @@ -800,8 +798,6 @@ def setUp(self): % (PROTOCOL_VERSION,)) self.cluster = TestCluster() - if PROTOCOL_VERSION < 3: - self.cluster.set_core_connections_per_host(HostDistance.LOCAL, 1) self.session = self.cluster.connect() def tearDown(self): diff --git a/tests/integration/standard/test_query_paging.py b/tests/integration/standard/test_query_paging.py index 28567d991b..e0c67cd309 100644 --- a/tests/integration/standard/test_query_paging.py +++ b/tests/integration/standard/test_query_paging.py @@ -46,8 +46,6 @@ def setUp(self): self.cluster = TestCluster( execution_profiles={EXEC_PROFILE_DEFAULT: ExecutionProfile(consistency_level=ConsistencyLevel.LOCAL_QUORUM)} ) - if PROTOCOL_VERSION < 3: - self.cluster.set_core_connections_per_host(HostDistance.LOCAL, 1) self.session = self.cluster.connect(wait_for_all_pools=True) self.session.execute("TRUNCATE test3rf.test") diff --git a/tests/unit/test_host_connection_pool.py b/tests/unit/test_host_connection_pool.py index 3fac0b18ef..56f6d942e9 100644 --- a/tests/unit/test_host_connection_pool.py +++ b/tests/unit/test_host_connection_pool.py @@ -39,8 +39,6 @@ class _PoolTests(unittest.TestCase): def make_session(self): session = NonCallableMagicMock(spec=Session, keyspace='foobarkeyspace') - session.cluster.get_core_connections_per_host.return_value = 1 - session.cluster.get_max_connections_per_host.return_value = 1 return session def test_borrow_and_return(self): @@ -113,9 +111,6 @@ def test_spawn_when_at_max(self): conn.max_request_id = 100 session.cluster.connection_factory.return_value = conn - # core conns = 1, max conns = 2 - session.cluster.get_max_connections_per_host.return_value = 2 - pool = self.PoolImpl(host, HostDistance.LOCAL, session) session.cluster.connection_factory.assert_called_once_with(host.endpoint, on_orphaned_stream_released=pool.on_orphaned_stream_released) From 07b9fbae823b9ea899a6f74c646dec1b24699804 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sat, 16 Aug 2025 00:39:40 -0400 Subject: [PATCH 095/298] Release 3.29.4: changelog, version and documentation --- CHANGELOG.rst | 26 ++++++++++++++++++++++++++ cassandra/__init__.py | 2 +- docs/installation.rst | 4 ++-- docs/poetry.lock | 2 +- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 80f8d52d7a..4f8f42689b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,29 @@ +3.29.4 +====== +August 16, 2025 + +Features +-------- +* Add Cluster.application_info to report application information to server (#486) +* Move to uv package manager (#496) + +Bug Fixes +--------- +* Fix deadlocks on evicting connection in HostConnectionPool and ConnectionPool (#499) +* Fix libevreactor crashing when connection added and closed right away (#508) + +Others +------ +* Remove outdated protocols support (v1 and v2) (#493, #525) +* Remove DSE integration tests (#502) +* Optimise shard port allocator (#506) +* Remove self.assert (#507) +* Minor performance improvement for make_token_replica_map (#513) +* Remove in-memory scylla tablets support (#518) +* Add optional dependancies for SNAPPY and LZ4 compressors (#517) +* Remove support for protocol versions not supported by scylla (#492) +* Set monitor_reporting_enabled False by default (#523) + 3.29.3 ====== Mart 11, 2025 diff --git a/cassandra/__init__.py b/cassandra/__init__.py index 6f5fd372fc..68dd5246bf 100644 --- a/cassandra/__init__.py +++ b/cassandra/__init__.py @@ -23,7 +23,7 @@ def emit(self, record): logging.getLogger('cassandra').addHandler(NullHandler()) -__version_info__ = (3, 29, 3) +__version_info__ = (3, 29, 4) __version__ = '.'.join(map(str, __version_info__)) diff --git a/docs/installation.rst b/docs/installation.rst index b57ad37f96..884380c2bc 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -26,7 +26,7 @@ To check if the installation was successful, you can run:: python -c 'import cassandra; print(cassandra.__version__)' -It should print something like "3.29.3". +It should print something like "3.29.4". (*Optional*) Compression Support -------------------------------- @@ -208,7 +208,7 @@ through `Homebrew `_. For example, on Mac OS X:: $ brew install libev -The libev extension can now be built for Windows as of Python driver version 3.29.3. You can +The libev extension can now be built for Windows as of Python driver version 3.29.4. You can install libev using any Windows package manager. For example, to install using `vcpkg `_: $ vcpkg install libev diff --git a/docs/poetry.lock b/docs/poetry.lock index b1d2ed96e3..a364aa86a0 100644 --- a/docs/poetry.lock +++ b/docs/poetry.lock @@ -1013,7 +1013,7 @@ six = "*" [[package]] name = "scylla-driver" -version = "3.29.3" +version = "3.29.4" description = "Scylla Driver for Apache Cassandra" optional = false python-versions = ">=3.9" From af400264a07d860a4f1d29b7879c58742cad6a1d Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Thu, 4 Sep 2025 09:10:58 +0300 Subject: [PATCH 096/298] Changelog - few typos Fixed few minor typos in the changelog. Signed-off-by: Yaniv Kaul --- CHANGELOG.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4f8f42689b..29db4ddf97 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,14 +19,14 @@ Others * Optimise shard port allocator (#506) * Remove self.assert (#507) * Minor performance improvement for make_token_replica_map (#513) -* Remove in-memory scylla tablets support (#518) -* Add optional dependancies for SNAPPY and LZ4 compressors (#517) -* Remove support for protocol versions not supported by scylla (#492) +* Remove in-memory Scylla tables support (#518) +* Add optional dependencies for SNAPPY and LZ4 compressors (#517) +* Remove support for protocol versions not supported by Scylla (#492) * Set monitor_reporting_enabled False by default (#523) 3.29.3 ====== -Mart 11, 2025 +March 11, 2025 Bug Fixes --------- From 849ebece3077d04c9a2fe1b27bc058d6c9409388 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 13:43:04 +0000 Subject: [PATCH 097/298] build(deps): bump eventlet from 0.33.3 to 0.40.3 in /docs Bumps [eventlet](https://github.com/eventlet/eventlet) from 0.33.3 to 0.40.3. - [Changelog](https://github.com/eventlet/eventlet/blob/master/NEWS) - [Commits](https://github.com/eventlet/eventlet/compare/v0.33.3...0.40.3) --- updated-dependencies: - dependency-name: eventlet dependency-version: 0.40.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docs/poetry.lock | 24 +++++++++++++----------- docs/pyproject.toml | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/poetry.lock b/docs/poetry.lock index a364aa86a0..6d3f13cba4 100644 --- a/docs/poetry.lock +++ b/docs/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "aenum" @@ -377,20 +377,22 @@ files = [ [[package]] name = "eventlet" -version = "0.33.3" +version = "0.40.3" description = "Highly concurrent networking library" optional = false -python-versions = "*" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "eventlet-0.33.3-py2.py3-none-any.whl", hash = "sha256:e43b9ae05ba4bb477a10307699c9aff7ff86121b2640f9184d29059f5a687df8"}, - {file = "eventlet-0.33.3.tar.gz", hash = "sha256:722803e7eadff295347539da363d68ae155b8b26ae6a634474d0a920be73cfda"}, + {file = "eventlet-0.40.3-py3-none-any.whl", hash = "sha256:e681cae6ee956cfb066a966b5c0541e734cc14879bda6058024104790595ac9d"}, + {file = "eventlet-0.40.3.tar.gz", hash = "sha256:290852db0065d78cec17a821b78c8a51cafb820a792796a354592ae4d5fceeb0"}, ] [package.dependencies] dnspython = ">=1.15.0" -greenlet = ">=0.3" -six = ">=1.10.0" +greenlet = ">=1.0" + +[package.extras] +dev = ["black", "build", "commitizen", "isort", "pip-tools", "pre-commit", "twine"] [[package]] name = "exceptiongroup" @@ -399,7 +401,7 @@ description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version == \"3.10\"" +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -1307,7 +1309,7 @@ description = "Extensions for Sphinx which allow for substitutions." optional = false python-versions = ">=3.10" groups = ["main"] -markers = "python_version == \"3.10\" or platform_python_implementation != \"CPython\"" +markers = "python_version < \"3.11\" or platform_python_implementation != \"CPython\"" files = [ {file = "sphinx_substitution_extensions-2025.1.2-py2.py3-none-any.whl", hash = "sha256:ff14f40e4393bd7434a196badb8d47983355d9755af884b902e3023fb456b958"}, {file = "sphinx_substitution_extensions-2025.1.2.tar.gz", hash = "sha256:53b8d394d5098a09aef36bc687fa310aeb28466319d2c750e996e46400fb2474"}, @@ -1511,7 +1513,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version == \"3.10\"" +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -1905,4 +1907,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "432ab6b3744422cd7526b9d863d4f2de57fec071049ecde4b690e15b6dd36551" +content-hash = "0fa7e6418ed0929d535552dbc2d2daa544dae02b57cf06cdc4caf2d04ebee9b7" diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 3d178f1936..929ff45e48 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Python Driver Contributors"] package-mode = false [tool.poetry.dependencies] -eventlet = "^0.33.3" +eventlet = "^0.40.3" futures = "2.2.0" gevent = "^23.9.1" gremlinpython = "3.4.7" From dd00221b4151bb11915318db5d482d67154df372 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Mon, 1 Sep 2025 17:22:04 +0300 Subject: [PATCH 098/298] (improvement) do not fetch * from system.local and system.peers, but specific columns Specifically, supported_features took quite some space. Fetching only what is useful for the client. In follow-up PRs, will remove some (now) dead code that may have relied on some of those columns. Refs: https://github.com/scylladb/scylla-drivers/issues/11 Signed-off-by: Yaniv Kaul --- cassandra/cluster.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 28235e0823..4661e629eb 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -3444,9 +3444,9 @@ class ControlConnection(object): Internal """ - _SELECT_PEERS = "SELECT * FROM system.peers" + _SELECT_PEERS = "SELECT peer, data_center, host_id, rack, release_version, rpc_address, schema_version, tokens FROM system.peers" _SELECT_PEERS_NO_TOKENS_TEMPLATE = "SELECT host_id, peer, data_center, rack, rpc_address, {nt_col_name}, release_version, schema_version FROM system.peers" - _SELECT_LOCAL = "SELECT * FROM system.local WHERE key='local'" + _SELECT_LOCAL = "SELECT broadcast_address, cluster_name, data_center, host_id, listen_address, partitioner, rack, release_version, rpc_address, schema_version, tokens FROM system.local WHERE key='local'" _SELECT_LOCAL_NO_TOKENS = "SELECT host_id, cluster_name, data_center, rack, partitioner, release_version, schema_version, rpc_address FROM system.local WHERE key='local'" # Used only when token_metadata_enabled is set to False _SELECT_LOCAL_NO_TOKENS_RPC_ADDRESS = "SELECT rpc_address FROM system.local WHERE key='local'" From f048a8e9123b78c2433f54097c5a84b11004797d Mon Sep 17 00:00:00 2001 From: David Garcia Date: Mon, 15 Sep 2025 19:36:36 +0100 Subject: [PATCH 099/298] docs: update theme 1.8.8 --- docs/poetry.lock | 683 ++++++++++++++++++++++++----------------------- 1 file changed, 353 insertions(+), 330 deletions(-) diff --git a/docs/poetry.lock b/docs/poetry.lock index 6d3f13cba4..ca7bb1fc1c 100644 --- a/docs/poetry.lock +++ b/docs/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "aenum" @@ -27,14 +27,14 @@ files = [ [[package]] name = "anyio" -version = "4.9.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" +version = "4.10.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, - {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, + {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, + {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, ] [package.dependencies] @@ -44,8 +44,6 @@ sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] trio = ["trio (>=0.26.1)"] [[package]] @@ -84,14 +82,14 @@ test-tox-coverage = ["coverage (>=5.5)"] [[package]] name = "beautifulsoup4" -version = "4.13.4" +version = "4.13.5" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" groups = ["main"] files = [ - {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, - {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, + {file = "beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a"}, + {file = "beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695"}, ] [package.dependencies] @@ -107,197 +105,201 @@ lxml = ["lxml"] [[package]] name = "certifi" -version = "2025.6.15" +version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"}, - {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"}, + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, ] [[package]] name = "cffi" -version = "1.17.1" +version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\"" files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, + {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, + {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, + {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, + {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, + {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, + {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, + {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, + {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, + {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, + {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, + {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, + {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, + {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, + {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, + {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, + {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, + {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, + {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, + {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, + {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] [package.dependencies] -pycparser = "*" +pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} [[package]] name = "charset-normalizer" -version = "3.4.2" +version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, - {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, - {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, ] [[package]] @@ -344,24 +346,24 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] [[package]] name = "dnspython" -version = "2.7.0" +version = "2.8.0" description = "DNS toolkit" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, - {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, + {file = "dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af"}, + {file = "dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f"}, ] [package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=43)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=1.0.0)"] -idna = ["idna (>=3.7)"] -trio = ["trio (>=0.23)"] -wmi = ["wmi (>=1.5.1)"] +dev = ["black (>=25.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.17.0)", "mypy (>=1.17)", "pylint (>=3)", "pytest (>=8.4)", "pytest-cov (>=6.2.0)", "quart-trio (>=0.12.0)", "sphinx (>=8.2.0)", "sphinx-rtd-theme (>=3.0.0)", "twine (>=6.1.0)", "wheel (>=0.45.0)"] +dnssec = ["cryptography (>=45)"] +doh = ["h2 (>=4.2.0)", "httpcore (>=1.0.0)", "httpx (>=0.28.0)"] +doq = ["aioquic (>=1.2.0)"] +idna = ["idna (>=3.10)"] +trio = ["trio (>=0.30)"] +wmi = ["wmi (>=1.5.1) ; platform_system == \"Windows\""] [[package]] name = "docutils" @@ -401,7 +403,7 @@ description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -508,71 +510,71 @@ test = ["cffi (>=1.12.2) ; platform_python_implementation == \"CPython\"", "cove [[package]] name = "greenlet" -version = "3.2.3" +version = "3.2.4" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "greenlet-3.2.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be"}, - {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac"}, - {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392"}, - {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c"}, - {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db"}, - {file = "greenlet-3.2.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b"}, - {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712"}, - {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00"}, - {file = "greenlet-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302"}, - {file = "greenlet-3.2.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822"}, - {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83"}, - {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf"}, - {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b"}, - {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147"}, - {file = "greenlet-3.2.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5"}, - {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc"}, - {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba"}, - {file = "greenlet-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34"}, - {file = "greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d"}, - {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b"}, - {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d"}, - {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264"}, - {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688"}, - {file = "greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb"}, - {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c"}, - {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163"}, - {file = "greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849"}, - {file = "greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad"}, - {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef"}, - {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3"}, - {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95"}, - {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb"}, - {file = "greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b"}, - {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0"}, - {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36"}, - {file = "greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3"}, - {file = "greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86"}, - {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97"}, - {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728"}, - {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a"}, - {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892"}, - {file = "greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141"}, - {file = "greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a"}, - {file = "greenlet-3.2.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:42efc522c0bd75ffa11a71e09cd8a399d83fafe36db250a87cf1dacfaa15dc64"}, - {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d760f9bdfe79bff803bad32b4d8ffb2c1d2ce906313fc10a83976ffb73d64ca7"}, - {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8324319cbd7b35b97990090808fdc99c27fe5338f87db50514959f8059999805"}, - {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8c37ef5b3787567d322331d5250e44e42b58c8c713859b8a04c6065f27efbf72"}, - {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ce539fb52fb774d0802175d37fcff5c723e2c7d249c65916257f0a940cee8904"}, - {file = "greenlet-3.2.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:003c930e0e074db83559edc8705f3a2d066d4aa8c2f198aff1e454946efd0f26"}, - {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7e70ea4384b81ef9e84192e8a77fb87573138aa5d4feee541d8014e452b434da"}, - {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:22eb5ba839c4b2156f18f76768233fe44b23a31decd9cc0d4cc8141c211fd1b4"}, - {file = "greenlet-3.2.3-cp39-cp39-win32.whl", hash = "sha256:4532f0d25df67f896d137431b13f4cdce89f7e3d4a96387a41290910df4d3a57"}, - {file = "greenlet-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:aaa7aae1e7f75eaa3ae400ad98f8644bb81e1dc6ba47ce8a93d3f17274e08322"}, - {file = "greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365"}, + {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, + {file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, + {file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, + {file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, + {file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, + {file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, + {file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, + {file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, + {file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, + {file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, + {file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"}, + {file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"}, + {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, + {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, ] [package.extras] docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] +test = ["objgraph", "psutil", "setuptools"] [[package]] name = "gremlinpython" @@ -668,6 +670,7 @@ description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" groups = ["main"] +markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\"" files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -686,6 +689,31 @@ profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +[[package]] +name = "markdown-it-py" +version = "4.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.10\" or platform_python_implementation != \"CPython\"" +files = [ + {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, + {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "markdown-it-pyrs", "mistletoe (>=1.0,<2.0)", "mistune (>=3.0,<4.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins (>=0.5.0)"] +profiling = ["gprof2dot"] +rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"] + [[package]] name = "markupsafe" version = "3.0.2" @@ -759,19 +787,19 @@ files = [ [[package]] name = "mdit-py-plugins" -version = "0.4.2" +version = "0.5.0" description = "Collection of plugins for markdown-it-py" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\"" files = [ - {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, - {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, + {file = "mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f"}, + {file = "mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6"}, ] [package.dependencies] -markdown-it-py = ">=1.0.0,<4.0.0" +markdown-it-py = ">=2.0.0,<5.0.0" [package.extras] code-style = ["pre-commit"] @@ -832,15 +860,15 @@ files = [ [[package]] name = "pycparser" -version = "2.22" +version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\"" +markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and implementation_name != \"PyPy\"" files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, + {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, + {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] [[package]] @@ -959,14 +987,14 @@ test = ["pre-commit", "pytest"] [[package]] name = "requests" -version = "2.32.4" +version = "2.32.5" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, - {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, ] [package.dependencies] @@ -981,20 +1009,19 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "14.0.0" +version = "14.1.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" groups = ["main"] files = [ - {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, - {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, + {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, + {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] @@ -1029,6 +1056,8 @@ pyyaml = ">5.0" [package.extras] cle = ["cryptography (>=35.0)"] +compress-lz4 = ["lz4"] +compress-snappy = ["python-snappy"] graph = ["gremlinpython (==3.4.6)"] [package.source] @@ -1037,14 +1066,14 @@ url = ".." [[package]] name = "setuptools" -version = "79.0.1" +version = "80.9.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "setuptools-79.0.1-py3-none-any.whl", hash = "sha256:e147c0549f27767ba362f9da434eab9c5dc0045d5304feb602a0af001089fc51"}, - {file = "setuptools-79.0.1.tar.gz", hash = "sha256:128ce7b8f33c3079fd1b067ecbb4051a66e8526e7b65f6cec075dfc650ddfa88"}, + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, ] [package.extras] @@ -1106,14 +1135,14 @@ files = [ [[package]] name = "soupsieve" -version = "2.7" +version = "2.8" description = "A modern CSS selector implementation for Beautiful Soup." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, - {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, + {file = "soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c"}, + {file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"}, ] [[package]] @@ -1263,20 +1292,20 @@ test = ["tox"] [[package]] name = "sphinx-scylladb-theme" -version = "1.8.7" +version = "1.8.8" description = "A Sphinx Theme for ScyllaDB documentation projects" optional = false python-versions = "<4.0,>=3.10" groups = ["main"] files = [ - {file = "sphinx_scylladb_theme-1.8.7-py3-none-any.whl", hash = "sha256:64c86e86737e16d8bbdbec492622865ec1e9c0c3a5915d747a9c109fd69145f1"}, - {file = "sphinx_scylladb_theme-1.8.7.tar.gz", hash = "sha256:7b84fc99e1156ebf14149f5c1f88b61b5ea852e367fb3940eb99f514db0a6c41"}, + {file = "sphinx_scylladb_theme-1.8.8-py3-none-any.whl", hash = "sha256:9b37f58b745bfc818b6f0869cbcc414a3ad6658023b22a6abbec44da5c09cf80"}, + {file = "sphinx_scylladb_theme-1.8.8.tar.gz", hash = "sha256:15af599424f8b2ddbf14644b267c0bd31bc3fbbd64bca5b97d7b31bdb84d2c3d"}, ] [package.dependencies] beautifulsoup4 = ">=4.12.3,<5.0.0" pyyaml = ">=6.0.1,<7.0.0" -setuptools = ">=70.1.1,<80.0.0" +setuptools = ">=70.1.1,<81.0.0" sphinx-collapse = ">=0.1.1,<0.2.0" sphinx-copybutton = ">=0.5.2,<0.6.0" sphinx-notfound-page = ">=1.0.4,<2.0.0" @@ -1286,14 +1315,14 @@ sphinxcontrib-mermaid = ">=1.0.0,<2.0.0" [[package]] name = "sphinx-sitemap" -version = "2.7.2" +version = "2.8.0" description = "Sitemap generator for Sphinx" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "sphinx_sitemap-2.7.2-py3-none-any.whl", hash = "sha256:1a6a8dcecb0ffb85fd37678f785cfcc40adfe3eebafb05e678971e5260b117e4"}, - {file = "sphinx_sitemap-2.7.2.tar.gz", hash = "sha256:819e028e27579b47efa0e2f863b87136b711c45f13e84730610e80316f6883da"}, + {file = "sphinx_sitemap-2.8.0-py3-none-any.whl", hash = "sha256:332042cd5b9385f61ec2861dfd550d9bccbdfcff86f6b68c7072cf40c9f16363"}, + {file = "sphinx_sitemap-2.8.0.tar.gz", hash = "sha256:749d7184a0c7b73d486a232b54b5c1b38a0e2d6f18cf19fb1b033b8162b44a82"}, ] [package.dependencies] @@ -1309,7 +1338,7 @@ description = "Extensions for Sphinx which allow for substitutions." optional = false python-versions = ">=3.10" groups = ["main"] -markers = "python_version < \"3.11\" or platform_python_implementation != \"CPython\"" +markers = "python_version == \"3.10\" or platform_python_implementation != \"CPython\"" files = [ {file = "sphinx_substitution_extensions-2025.1.2-py2.py3-none-any.whl", hash = "sha256:ff14f40e4393bd7434a196badb8d47983355d9755af884b902e3023fb456b958"}, {file = "sphinx_substitution_extensions-2025.1.2.tar.gz", hash = "sha256:53b8d394d5098a09aef36bc687fa310aeb28466319d2c750e996e46400fb2474"}, @@ -1489,14 +1518,14 @@ test = ["pytest"] [[package]] name = "starlette" -version = "0.47.1" +version = "0.48.0" description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "starlette-0.47.1-py3-none-any.whl", hash = "sha256:5e11c9f5c7c3f24959edbf2dffdc01bba860228acf657129467d8a7468591527"}, - {file = "starlette-0.47.1.tar.gz", hash = "sha256:aef012dd2b6be325ffa16698f9dc533614fb1cebd593a906b90dc1025529a79b"}, + {file = "starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659"}, + {file = "starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46"}, ] [package.dependencies] @@ -1513,7 +1542,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -1566,14 +1595,14 @@ files = [ [[package]] name = "typer" -version = "0.16.0" +version = "0.17.4" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855"}, - {file = "typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b"}, + {file = "typer-0.17.4-py3-none-any.whl", hash = "sha256:015534a6edaa450e7007eba705d5c18c3349dcea50a6ad79a5ed530967575824"}, + {file = "typer-0.17.4.tar.gz", hash = "sha256:b77dc07d849312fd2bb5e7f20a7af8985c7ec360c45b051ed5412f64d8dc1580"}, ] [package.dependencies] @@ -1584,14 +1613,14 @@ typing-extensions = ">=3.7.4.3" [[package]] name = "typing-extensions" -version = "4.14.1" +version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, - {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] [[package]] @@ -1832,68 +1861,62 @@ files = [ [[package]] name = "zope-event" -version = "5.1" +version = "6.0" description = "Very basic event publishing system" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "zope_event-5.1-py3-none-any.whl", hash = "sha256:53de8f0e9f61dc0598141ac591f49b042b6d74784dab49971b9cc91d0f73a7df"}, - {file = "zope_event-5.1.tar.gz", hash = "sha256:a153660e0c228124655748e990396b9d8295d6e4f546fa1b34f3319e1c666e7f"}, + {file = "zope_event-6.0-py3-none-any.whl", hash = "sha256:6f0922593407cc673e7d8766b492c519f91bdc99f3080fe43dcec0a800d682a3"}, + {file = "zope_event-6.0.tar.gz", hash = "sha256:0ebac894fa7c5f8b7a89141c272133d8c1de6ddc75ea4b1f327f00d1f890df92"}, ] [package.dependencies] -setuptools = "*" +setuptools = ">=75.8.2" [package.extras] docs = ["Sphinx"] -test = ["zope.testrunner"] +test = ["zope.testrunner (>=6.4)"] [[package]] name = "zope-interface" -version = "7.2" +version = "8.0" description = "Interfaces for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "zope.interface-7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce290e62229964715f1011c3dbeab7a4a1e4971fd6f31324c4519464473ef9f2"}, - {file = "zope.interface-7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05b910a5afe03256b58ab2ba6288960a2892dfeef01336dc4be6f1b9ed02ab0a"}, - {file = "zope.interface-7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550f1c6588ecc368c9ce13c44a49b8d6b6f3ca7588873c679bd8fd88a1b557b6"}, - {file = "zope.interface-7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ef9e2f865721553c6f22a9ff97da0f0216c074bd02b25cf0d3af60ea4d6931d"}, - {file = "zope.interface-7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27f926f0dcb058211a3bb3e0e501c69759613b17a553788b2caeb991bed3b61d"}, - {file = "zope.interface-7.2-cp310-cp310-win_amd64.whl", hash = "sha256:144964649eba4c5e4410bb0ee290d338e78f179cdbfd15813de1a664e7649b3b"}, - {file = "zope.interface-7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1909f52a00c8c3dcab6c4fad5d13de2285a4b3c7be063b239b8dc15ddfb73bd2"}, - {file = "zope.interface-7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80ecf2451596f19fd607bb09953f426588fc1e79e93f5968ecf3367550396b22"}, - {file = "zope.interface-7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:033b3923b63474800b04cba480b70f6e6243a62208071fc148354f3f89cc01b7"}, - {file = "zope.interface-7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a102424e28c6b47c67923a1f337ede4a4c2bba3965b01cf707978a801fc7442c"}, - {file = "zope.interface-7.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25e6a61dcb184453bb00eafa733169ab6d903e46f5c2ace4ad275386f9ab327a"}, - {file = "zope.interface-7.2-cp311-cp311-win_amd64.whl", hash = "sha256:3f6771d1647b1fc543d37640b45c06b34832a943c80d1db214a37c31161a93f1"}, - {file = "zope.interface-7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:086ee2f51eaef1e4a52bd7d3111a0404081dadae87f84c0ad4ce2649d4f708b7"}, - {file = "zope.interface-7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21328fcc9d5b80768bf051faa35ab98fb979080c18e6f84ab3f27ce703bce465"}, - {file = "zope.interface-7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6dd02ec01f4468da0f234da9d9c8545c5412fef80bc590cc51d8dd084138a89"}, - {file = "zope.interface-7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e7da17f53e25d1a3bde5da4601e026adc9e8071f9f6f936d0fe3fe84ace6d54"}, - {file = "zope.interface-7.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cab15ff4832580aa440dc9790b8a6128abd0b88b7ee4dd56abacbc52f212209d"}, - {file = "zope.interface-7.2-cp312-cp312-win_amd64.whl", hash = "sha256:29caad142a2355ce7cfea48725aa8bcf0067e2b5cc63fcf5cd9f97ad12d6afb5"}, - {file = "zope.interface-7.2-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:3e0350b51e88658d5ad126c6a57502b19d5f559f6cb0a628e3dc90442b53dd98"}, - {file = "zope.interface-7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15398c000c094b8855d7d74f4fdc9e73aa02d4d0d5c775acdef98cdb1119768d"}, - {file = "zope.interface-7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:802176a9f99bd8cc276dcd3b8512808716492f6f557c11196d42e26c01a69a4c"}, - {file = "zope.interface-7.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb23f58a446a7f09db85eda09521a498e109f137b85fb278edb2e34841055398"}, - {file = "zope.interface-7.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a71a5b541078d0ebe373a81a3b7e71432c61d12e660f1d67896ca62d9628045b"}, - {file = "zope.interface-7.2-cp313-cp313-win_amd64.whl", hash = "sha256:4893395d5dd2ba655c38ceb13014fd65667740f09fa5bb01caa1e6284e48c0cd"}, - {file = "zope.interface-7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d3a8ffec2a50d8ec470143ea3d15c0c52d73df882eef92de7537e8ce13475e8a"}, - {file = "zope.interface-7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:31d06db13a30303c08d61d5fb32154be51dfcbdb8438d2374ae27b4e069aac40"}, - {file = "zope.interface-7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e204937f67b28d2dca73ca936d3039a144a081fc47a07598d44854ea2a106239"}, - {file = "zope.interface-7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:224b7b0314f919e751f2bca17d15aad00ddbb1eadf1cb0190fa8175edb7ede62"}, - {file = "zope.interface-7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf95683cde5bc7d0e12d8e7588a3eb754d7c4fa714548adcd96bdf90169f021"}, - {file = "zope.interface-7.2-cp38-cp38-win_amd64.whl", hash = "sha256:7dc5016e0133c1a1ec212fc87a4f7e7e562054549a99c73c8896fa3a9e80cbc7"}, - {file = "zope.interface-7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bd449c306ba006c65799ea7912adbbfed071089461a19091a228998b82b1fdb"}, - {file = "zope.interface-7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a19a6cc9c6ce4b1e7e3d319a473cf0ee989cbbe2b39201d7c19e214d2dfb80c7"}, - {file = "zope.interface-7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72cd1790b48c16db85d51fbbd12d20949d7339ad84fd971427cf00d990c1f137"}, - {file = "zope.interface-7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52e446f9955195440e787596dccd1411f543743c359eeb26e9b2c02b077b0519"}, - {file = "zope.interface-7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad9913fd858274db8dd867012ebe544ef18d218f6f7d1e3c3e6d98000f14b75"}, - {file = "zope.interface-7.2-cp39-cp39-win_amd64.whl", hash = "sha256:1090c60116b3da3bfdd0c03406e2f14a1ff53e5771aebe33fec1edc0a350175d"}, - {file = "zope.interface-7.2.tar.gz", hash = "sha256:8b49f1a3d1ee4cdaf5b32d2e738362c7f5e40ac8b46dd7d1a65e82a4872728fe"}, + {file = "zope_interface-8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:daf4d6ba488a0fb560980b575244aa962a75e77b7c86984138b8d52bd4b5465f"}, + {file = "zope_interface-8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0caca2915522451e92c96c2aec404d2687e9c5cb856766940319b3973f62abb8"}, + {file = "zope_interface-8.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a26ae2fe77c58b4df8c39c2b7c3aadedfd44225a1b54a1d74837cd27057b2fc8"}, + {file = "zope_interface-8.0-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:453d2c6668778b8d2215430ed61e04417386e51afb23637ef2e14972b047b700"}, + {file = "zope_interface-8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a2c107cc6dff954be25399cd81ddc390667f79af306802fc0c1de98614348b70"}, + {file = "zope_interface-8.0-cp310-cp310-win_amd64.whl", hash = "sha256:c23af5b4c4e332253d721ec1222c809ad27ceae382ad5b8ff22c4c4fb6eb8ed5"}, + {file = "zope_interface-8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec1da7b9156ae000cea2d19bad83ddb5c50252f9d7b186da276d17768c67a3cb"}, + {file = "zope_interface-8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:160ba50022b342451baf516de3e3a2cd2d8c8dbac216803889a5eefa67083688"}, + {file = "zope_interface-8.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:879bb5bf937cde4acd738264e87f03c7bf7d45478f7c8b9dc417182b13d81f6c"}, + {file = "zope_interface-8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7fb931bf55c66a092c5fbfb82a0ff3cc3221149b185bde36f0afc48acb8dcd92"}, + {file = "zope_interface-8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1858d1e5bb2c5ae766890708184a603eb484bb7454e306e967932a9f3c558b07"}, + {file = "zope_interface-8.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e88c66ebedd1e839082f308b8372a50ef19423e01ee2e09600b80e765a10234"}, + {file = "zope_interface-8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b80447a3a5c7347f4ebf3e50de319c8d2a5dabd7de32f20899ac50fc275b145d"}, + {file = "zope_interface-8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:67047a4470cb2fddb5ba5105b0160a1d1c30ce4b300cf264d0563136adac4eac"}, + {file = "zope_interface-8.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:1bee9c1b42513148f98d3918affd829804a5c992c000c290dc805f25a75a6a3f"}, + {file = "zope_interface-8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:804ebacb2776eb89a57d9b5e9abec86930e0ee784a0005030801ae2f6c04d5d8"}, + {file = "zope_interface-8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c4d9d3982aaa88b177812cd911ceaf5ffee4829e86ab3273c89428f2c0c32cc4"}, + {file = "zope_interface-8.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea1f2e47bc0124a03ee1e5fb31aee5dfde876244bcc552b9e3eb20b041b350d7"}, + {file = "zope_interface-8.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:ee9ecad04269c2da4b1be403a47993981531ffd557064b870eab4094730e5062"}, + {file = "zope_interface-8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a9a8a71c38628af82a9ea1f7be58e5d19360a38067080c8896f6cbabe167e4f8"}, + {file = "zope_interface-8.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c0cc51ebd984945362fd3abdc1e140dbd837c3e3b680942b3fa24fe3aac26ef8"}, + {file = "zope_interface-8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:07405019f635a93b318807cb2ec7b05a5ef30f67cf913d11eb2f156ddbcead0d"}, + {file = "zope_interface-8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:450ab3357799eed6093f3a9f1fa22761b3a9de9ebaf57f416da2c9fb7122cdcb"}, + {file = "zope_interface-8.0-cp313-cp313-win_amd64.whl", hash = "sha256:e38bb30a58887d63b80b01115ab5e8be6158b44d00b67197186385ec7efe44c7"}, + {file = "zope_interface-8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:778458ea69413cf8131a3fcc6f0ea2792d07df605422fb03ad87daca3f8f78ce"}, + {file = "zope_interface-8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b207966f39c2e6fcfe9b68333acb7b19afd3fdda29eccc4643f8d52c180a3185"}, + {file = "zope_interface-8.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:e3cf57f90a760c56c55668f650ba20c3444cde8332820db621c9a1aafc217471"}, + {file = "zope_interface-8.0-cp39-cp39-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5cffe23eb610e32a83283dde5413ab7a17938fa3fbd023ca3e529d724219deb0"}, + {file = "zope_interface-8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4d639d5015c1753031e180b8ef81e72bb7d47b0aca0218694ad1f19b0a6c6b63"}, + {file = "zope_interface-8.0-cp39-cp39-win_amd64.whl", hash = "sha256:dee2d1db1067e8a4b682dde7eb4bff21775412358e142f4f98c9066173f9dacd"}, + {file = "zope_interface-8.0.tar.gz", hash = "sha256:b14d5aac547e635af749ce20bf49a3f5f93b8a854d2a6b1e95d4d5e5dc618f7d"}, ] [package.dependencies] From 021485b5ea4ea4a527e5cc5981b298b6804d420c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 14:01:57 +0000 Subject: [PATCH 100/298] Add renovate.json --- renovate.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000000..5db72dd6a9 --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ] +} From 152653d4cfb3f5cc4250b1de0af4664612ec953d Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 26 Sep 2025 06:46:37 -0400 Subject: [PATCH 101/298] cluster: default metadata_request_timeout to control_connection_timeout metadata_request_timeout and control_connection_timeout are going hand by hand, control_connection_timeout is client side timeout, while metadata_request_timeout is server side. In general case they always have to be eual. --- cassandra/cluster.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 4661e629eb..bc039ed0b5 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -1083,10 +1083,19 @@ def default_retry_policy(self, policy): used for columns in this cluster. """ - metadata_request_timeout = datetime.timedelta(seconds=2) + metadata_request_timeout: Optional[float] = None """ - Timeout for all queries used by driver it self. - Supported only by Scylla clusters. + Specifies a server-side timeout (in seconds) for all internal driver queries, + such as schema metadata lookups and cluster topology requests. + + The timeout is enforced by appending `USING TIMEOUT ` to queries + executed by the driver. + + - A value of `0` disables explicit timeout enforcement. In this case, + the driver does not add `USING TIMEOUT`, and the timeout is determined + by the server's defaults. + - Only supported when connected to Scylla clusters. + - If not explicitly set, defaults to the value of `control_connection_timeout`. """ @property @@ -1303,8 +1312,6 @@ def __init__(self, self.no_compact = no_compact self.auth_provider = auth_provider - if metadata_request_timeout is not None: - self.metadata_request_timeout = metadata_request_timeout if load_balancing_policy is not None: if isinstance(load_balancing_policy, type): @@ -1415,6 +1422,7 @@ def __init__(self, self.cql_version = cql_version self.max_schema_agreement_wait = max_schema_agreement_wait self.control_connection_timeout = control_connection_timeout + self.metadata_request_timeout = self.control_connection_timeout if metadata_request_timeout is None else metadata_request_timeout self.idle_heartbeat_interval = idle_heartbeat_interval self.idle_heartbeat_timeout = idle_heartbeat_timeout self.schema_event_refresh_window = schema_event_refresh_window From 42df52bed6947afd34f8d1fe383c2575459fca1d Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 26 Sep 2025 06:50:45 -0400 Subject: [PATCH 102/298] control_connection: read actual metadata_request_timeout from Cluster.metadata_request_timeout PR #361 that have introduced `metadata_request_timeout` intentionally pulled actual `metadata_request_timeout` from `control_connection_timeout`, because both `metadata_request_timeout` and `control_connection_timeout` has to be inline. Since this PR brings `Cluster.metadata_request_timeout` back, we need to pull it from `Cluster.metadata_request_timeout` --- cassandra/cluster.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index bc039ed0b5..14c5cb4bd9 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -1214,7 +1214,7 @@ def __init__(self, cloud=None, scylla_cloud=None, shard_aware_options=None, - metadata_request_timeout=None, + metadata_request_timeout: Optional[float] = None, column_encryption_policy=None, application_info:Optional[ApplicationInfoBase]=None ): @@ -3622,9 +3622,11 @@ def _try_connect(self, host): if connection.features.sharding_info is not None: self._uses_peers_v2 = False - # Cassandra does not support "USING TIMEOUT" - self._metadata_request_timeout = None if connection.features.sharding_info is None \ - else datetime.timedelta(seconds=self._cluster.control_connection_timeout) + # Only ScyllaDB supports "USING TIMEOUT" + # Sharding information signals it is ScyllaDB + self._metadata_request_timeout = None if connection.features.sharding_info is None or not self._cluster.metadata_request_timeout \ + else datetime.timedelta(seconds=self._cluster.metadata_request_timeout) + self._tablets_routing_v1 = connection.features.tablets_routing_v1 # use weak references in both directions From 51dbe8b752d30853c92ba28908ff7a946b6a8cc3 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 26 Sep 2025 12:17:20 -0400 Subject: [PATCH 103/298] tests: add more integration tests for metadata_request_timeout New behavior needs to be tested: 1. metadata_request_timeout can be borrowed from control_connection_timeout 2. metadata_request_timeout can be set to 0, to disable it. 3. metadata_request_timeout can be set to some value to override default. 4. When both metadata_request_timeout and control_connection_timeou it should be disabled --- tests/integration/standard/test_metadata.py | 40 ++++++++++++++++----- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 7b675fdf7b..d3ccafd76d 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -20,6 +20,8 @@ import sys import time import os +from typing import Optional + from packaging.version import Version from unittest.mock import Mock, patch import pytest @@ -1323,20 +1325,39 @@ def test_token(self): cluster.shutdown() -class MetadataTimeoutTest(unittest.TestCase): +class TestMetadataTimeout: """ Test of TokenMap creation and other behavior. """ - def test_timeout(self): - cluster = TestCluster() - cluster.metadata_request_timeout = None + @pytest.mark.parametrize( + "opts, expected_query_chunk", + [ + ( + {"metadata_request_timeout": None}, + # Should be borrowed from control_connection_timeout + "USING TIMEOUT 2000ms" + ), + ( + {"metadata_request_timeout": 0.0}, + False + ), + ( + {"metadata_request_timeout": 4.0}, + "USING TIMEOUT 4000ms" + ), + ( + {"metadata_request_timeout": None, "control_connection_timeout": None}, + False, + ) + ], + ids=["default", "zero", "4s", "both none"] + ) + def test_timeout(self, opts, expected_query_chunk): + cluster = TestCluster(**opts) stmts = [] class ConnectionWrapper(cluster.connection_class): - def __init__(self, *args, **kwargs): - super(ConnectionWrapper, self).__init__(*args, **kwargs) - def send_msg(self, msg, request_id, cb, encoder=ProtocolHandler.encode_message, decoder=ProtocolHandler.decode_message, result_metadata=None): if isinstance(msg, QueryMessage): @@ -1351,7 +1372,10 @@ def send_msg(self, msg, request_id, cb, encoder=ProtocolHandler.encode_message, for stmt in stmts: if "SELECT now() FROM system.local WHERE key='local'" in stmt: continue - assert "USING TIMEOUT 2000ms" in stmt, f"query `{stmt}` does not contain `USING TIMEOUT 2000ms`" + if expected_query_chunk: + assert expected_query_chunk in stmt, f"query `{stmt}` does not contain `{expected_query_chunk}`" + else: + assert 'USING TIMEOUT' not in stmt, f"query `{stmt}` should not contain `USING TIMEOUT`" class KeyspaceAlterMetadata(unittest.TestCase): From 4893f3f447624006e4a8855a1d0fcee8c3157032 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 28 Sep 2025 03:29:26 +0000 Subject: [PATCH 104/298] chore(deps): update actions/setup-java action to v5 --- .github/workflows/integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 0684a5c49b..85e509789b 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK ${{ matrix.java-version }} - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: ${{ matrix.java-version }} distribution: 'adopt' From 8725487d32c00c92606188577de3cedfc2c1ffff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 28 Sep 2025 03:29:23 +0000 Subject: [PATCH 105/298] chore(deps): update actions/download-artifact action to v5 --- .github/workflows/build-push.yml | 2 +- .github/workflows/lib-build-and-push.yml | 2 +- .github/workflows/publish-manually.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 4e73e811fa..1ac2fda9bb 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -23,7 +23,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: path: dist merge-multiple: true diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index 624f89eb10..7a1ab79ac6 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -187,7 +187,7 @@ jobs: id-token: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: path: dist merge-multiple: true diff --git a/.github/workflows/publish-manually.yml b/.github/workflows/publish-manually.yml index d2dda897ed..3bc260e820 100644 --- a/.github/workflows/publish-manually.yml +++ b/.github/workflows/publish-manually.yml @@ -53,7 +53,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: path: dist merge-multiple: true From 8e7b50a813f695de6eb5f22af58e6f51e8f64d36 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 27 Sep 2025 06:55:55 +0000 Subject: [PATCH 106/298] chore(deps): update actions/checkout action to v5 --- .github/workflows/docs-pages.yaml | 2 +- .github/workflows/docs-pr.yaml | 2 +- .github/workflows/integration-tests.yml | 2 +- .github/workflows/lib-build-and-push.yml | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docs-pages.yaml b/.github/workflows/docs-pages.yaml index 31f8dc74c5..ba2b7dc1e2 100644 --- a/.github/workflows/docs-pages.yaml +++ b/.github/workflows/docs-pages.yaml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ github.event.repository.default_branch }} persist-credentials: false diff --git a/.github/workflows/docs-pr.yaml b/.github/workflows/docs-pr.yaml index 28a74f2e58..553d6ba92f 100644 --- a/.github/workflows/docs-pr.yaml +++ b/.github/workflows/docs-pr.yaml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: persist-credentials: false fetch-depth: 0 diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 85e509789b..349d4ee842 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -38,7 +38,7 @@ jobs: event_loop_manager: "asyncore" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK ${{ matrix.java-version }} uses: actions/setup-java@v5 diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index 7a1ab79ac6..fe4846cfd7 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -83,11 +83,11 @@ jobs: include: ${{ fromJson(needs.prepare-matrix.outputs.matrix) }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Checkout tag ${{ inputs.target_tag }} if: inputs.target_tag != '' - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ inputs.target_tag }} @@ -164,7 +164,7 @@ jobs: name: Build source distribution runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install uv uses: astral-sh/setup-uv@v6 From 08b738f2e62fb21425c004643634c6e60a2a2e66 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 26 Sep 2025 15:39:15 -0400 Subject: [PATCH 107/298] windows: update openssl library --- .github/workflows/lib-build-and-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index fe4846cfd7..901e2139e0 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -115,7 +115,7 @@ jobs: - name: Install OpenSSL for Windows if: runner.os == 'Windows' run: | - choco install openssl --version=3.5.2 -f -y --no-progress + choco install openssl --version=3.5.3 -f -y --no-progress - name: Install Conan if: runner.os == 'Windows' From 031da6f35af072809c3919a09c0a71c93c66817d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 12:08:05 +0000 Subject: [PATCH 108/298] chore(deps): update actions/setup-python action to v6 --- .github/workflows/docs-pages.yaml | 2 +- .github/workflows/docs-pr.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs-pages.yaml b/.github/workflows/docs-pages.yaml index ba2b7dc1e2..f12124c04c 100644 --- a/.github/workflows/docs-pages.yaml +++ b/.github/workflows/docs-pages.yaml @@ -22,7 +22,7 @@ jobs: persist-credentials: false fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.10' - name: Set up env diff --git a/.github/workflows/docs-pr.yaml b/.github/workflows/docs-pr.yaml index 553d6ba92f..55e4b6e423 100644 --- a/.github/workflows/docs-pr.yaml +++ b/.github/workflows/docs-pr.yaml @@ -21,7 +21,7 @@ jobs: persist-credentials: false fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.10' - name: Set up env From fd91dbf82089f0925435c695dad9524381fc322c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 12:11:14 +0000 Subject: [PATCH 109/298] fix(deps): update dependency gremlinpython to v3.7.4 --- docs/poetry.lock | 666 +++++++++++++++++++++++++++++++++++++++++++- docs/pyproject.toml | 2 +- pyproject.toml | 2 +- 3 files changed, 660 insertions(+), 10 deletions(-) diff --git a/docs/poetry.lock b/docs/poetry.lock index ca7bb1fc1c..8cb08edbba 100644 --- a/docs/poetry.lock +++ b/docs/poetry.lock @@ -13,6 +13,143 @@ files = [ {file = "aenum-2.2.6.tar.gz", hash = "sha256:260225470b49429f5893a195a8b99c73a8d182be42bf90c37c93e7b20e44eaae"}, ] +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, +] + +[[package]] +name = "aiohttp" +version = "3.12.15" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc"}, + {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af"}, + {file = "aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1"}, + {file = "aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a"}, + {file = "aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830"}, + {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117"}, + {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe"}, + {file = "aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685"}, + {file = "aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b"}, + {file = "aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d"}, + {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7"}, + {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444"}, + {file = "aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3"}, + {file = "aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1"}, + {file = "aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34"}, + {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315"}, + {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd"}, + {file = "aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51"}, + {file = "aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0"}, + {file = "aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84"}, + {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:691d203c2bdf4f4637792efbbcdcd157ae11e55eaeb5e9c360c1206fb03d4d98"}, + {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e995e1abc4ed2a454c731385bf4082be06f875822adc4c6d9eaadf96e20d406"}, + {file = "aiohttp-3.12.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bd44d5936ab3193c617bfd6c9a7d8d1085a8dc8c3f44d5f1dcf554d17d04cf7d"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46749be6e89cd78d6068cdf7da51dbcfa4321147ab8e4116ee6678d9a056a0cf"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c643f4d75adea39e92c0f01b3fb83d57abdec8c9279b3078b68a3a52b3933b6"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a23918fedc05806966a2438489dcffccbdf83e921a1170773b6178d04ade142"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74bdd8c864b36c3673741023343565d95bfbd778ffe1eb4d412c135a28a8dc89"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a146708808c9b7a988a4af3821379e379e0f0e5e466ca31a73dbdd0325b0263"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7011a70b56facde58d6d26da4fec3280cc8e2a78c714c96b7a01a87930a9530"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3bdd6e17e16e1dbd3db74d7f989e8af29c4d2e025f9828e6ef45fbdee158ec75"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57d16590a351dfc914670bd72530fd78344b885a00b250e992faea565b7fdc05"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bc9a0f6569ff990e0bbd75506c8d8fe7214c8f6579cca32f0546e54372a3bb54"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:536ad7234747a37e50e7b6794ea868833d5220b49c92806ae2d7e8a9d6b5de02"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f0adb4177fa748072546fb650d9bd7398caaf0e15b370ed3317280b13f4083b0"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14954a2988feae3987f1eb49c706bff39947605f4b6fa4027c1d75743723eb09"}, + {file = "aiohttp-3.12.15-cp39-cp39-win32.whl", hash = "sha256:b784d6ed757f27574dca1c336f968f4e81130b27595e458e69457e6878251f5d"}, + {file = "aiohttp-3.12.15-cp39-cp39-win_amd64.whl", hash = "sha256:86ceded4e78a992f835209e236617bffae649371c4a50d5e5a3987f237db84b8"}, + {file = "aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.5.0" +aiosignal = ">=1.4.0" +async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" + +[package.extras] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] + +[[package]] +name = "aiosignal" +version = "1.4.0" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, + {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" +typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} + [[package]] name = "alabaster" version = "0.7.16" @@ -46,6 +183,38 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] trio = ["trio (>=0.26.1)"] +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "attrs" +version = "25.3.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, +] + +[package.extras] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] + [[package]] name = "babel" version = "2.17.0" @@ -415,6 +584,120 @@ typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "frozenlist" +version = "1.7.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718"}, + {file = "frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e"}, + {file = "frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56"}, + {file = "frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7"}, + {file = "frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"}, + {file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"}, + {file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"}, + {file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"}, + {file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cea3dbd15aea1341ea2de490574a4a37ca080b2ae24e4b4f4b51b9057b4c3630"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d536ee086b23fecc36c2073c371572374ff50ef4db515e4e503925361c24f71"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfcebf56f703cb2e346315431699f00db126d158455e513bd14089d992101e44"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974c5336e61d6e7eb1ea5b929cb645e882aadab0095c5a6974a111e6479f8878"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c70db4a0ab5ab20878432c40563573229a7ed9241506181bba12f6b7d0dc41cb"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1137b78384eebaf70560a36b7b229f752fb64d463d38d1304939984d5cb887b6"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e793a9f01b3e8b5c0bc646fb59140ce0efcc580d22a3468d70766091beb81b35"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74739ba8e4e38221d2c5c03d90a7e542cb8ad681915f4ca8f68d04f810ee0a87"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e63344c4e929b1a01e29bc184bbb5fd82954869033765bfe8d65d09e336a677"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ea2a7369eb76de2217a842f22087913cdf75f63cf1307b9024ab82dfb525938"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:836b42f472a0e006e02499cef9352ce8097f33df43baaba3e0a28a964c26c7d2"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e22b9a99741294b2571667c07d9f8cceec07cb92aae5ccda39ea1b6052ed4319"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a19e85cc503d958abe5218953df722748d87172f71b73cf3c9257a91b999890"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f22dac33bb3ee8fe3e013aa7b91dc12f60d61d05b7fe32191ffa84c3aafe77bd"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ccec739a99e4ccf664ea0775149f2749b8a6418eb5b8384b4dc0a7d15d304cb"}, + {file = "frozenlist-1.7.0-cp39-cp39-win32.whl", hash = "sha256:b3950f11058310008a87757f3eee16a8e1ca97979833239439586857bc25482e"}, + {file = "frozenlist-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:43a82fce6769c70f2f5a06248b614a7d268080a9d20f7457ef10ecee5af82b63"}, + {file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"}, + {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, +] + [[package]] name = "futures" version = "2.2.0" @@ -578,21 +861,26 @@ test = ["objgraph", "psutil", "setuptools"] [[package]] name = "gremlinpython" -version = "3.4.7" +version = "3.7.4" description = "Gremlin-Python for Apache TinkerPop" optional = false -python-versions = "*" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "gremlinpython-3.4.7-py2.py3-none-any.whl", hash = "sha256:3fc60881638d370fdd0acc005a536baf2fdb3539d5150f2c787e460382548ac4"}, - {file = "gremlinpython-3.4.7.tar.gz", hash = "sha256:0ebe51bba36606d7d731bdeb4f8558ea7f88abf15f841693da47b994a29ac424"}, + {file = "gremlinpython-3.7.4-py3-none-any.whl", hash = "sha256:b6b336320d0110382b6a3832bc19b4e2bf72e4b3f38dab25fdbedfa1a3167987"}, + {file = "gremlinpython-3.7.4.tar.gz", hash = "sha256:d41579a8ef83c1dce9e51ccff2b5fb496170be0fdb0f491d4124c29e7df9b14d"}, ] [package.dependencies] -aenum = ">=1.4.5,<3.0.0" +aenum = ">=1.4.5,<4.0.0" +aiohttp = ">=3.8.0,<4.0.0" +async-timeout = ">=4.0.3,<5.0.0" isodate = ">=0.6.0,<1.0.0" -six = ">=1.10.0,<2.0.0" -tornado = ">=4.4.1,<6.0" +nest-asyncio = "*" + +[package.extras] +kerberos = ["kerberos (>=1.3.0,<2.0.0)"] +ujson = ["ujson (>=2.0.0)"] [[package]] name = "h11" @@ -818,6 +1106,129 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "multidict" +version = "6.6.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, + {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, + {file = "multidict-6.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0af5f9dee472371e36d6ae38bde009bd8ce65ac7335f55dcc240379d7bed1495"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d24f351e4d759f5054b641c81e8291e5d122af0fca5c72454ff77f7cbe492de8"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db6a3810eec08280a172a6cd541ff4a5f6a97b161d93ec94e6c4018917deb6b7"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a1b20a9d56b2d81e2ff52ecc0670d583eaabaa55f402e8d16dd062373dbbe796"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c9854df0eaa610a23494c32a6f44a3a550fb398b6b51a56e8c6b9b3689578db"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4bb7627fd7a968f41905a4d6343b0d63244a0623f006e9ed989fa2b78f4438a0"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caebafea30ed049c57c673d0b36238b1748683be2593965614d7b0e99125c877"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ad887a8250eb47d3ab083d2f98db7f48098d13d42eb7a3b67d8a5c795f224ace"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ed8358ae7d94ffb7c397cecb62cbac9578a83ecefc1eba27b9090ee910e2efb6"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecab51ad2462197a4c000b6d5701fc8585b80eecb90583635d7e327b7b6923eb"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c5c97aa666cf70e667dfa5af945424ba1329af5dd988a437efeb3a09430389fb"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9a950b7cf54099c1209f455ac5970b1ea81410f2af60ed9eb3c3f14f0bfcf987"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:163c7ea522ea9365a8a57832dea7618e6cbdc3cd75f8c627663587459a4e328f"}, + {file = "multidict-6.6.4-cp310-cp310-win32.whl", hash = "sha256:17d2cbbfa6ff20821396b25890f155f40c986f9cfbce5667759696d83504954f"}, + {file = "multidict-6.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce9a40fbe52e57e7edf20113a4eaddfacac0561a0879734e636aa6d4bb5e3fb0"}, + {file = "multidict-6.6.4-cp310-cp310-win_arm64.whl", hash = "sha256:01d0959807a451fe9fdd4da3e139cb5b77f7328baf2140feeaf233e1d777b729"}, + {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c"}, + {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb"}, + {file = "multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f"}, + {file = "multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2"}, + {file = "multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e"}, + {file = "multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf"}, + {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8"}, + {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3"}, + {file = "multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24"}, + {file = "multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793"}, + {file = "multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e"}, + {file = "multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364"}, + {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e"}, + {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657"}, + {file = "multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a"}, + {file = "multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69"}, + {file = "multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf"}, + {file = "multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605"}, + {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb"}, + {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e"}, + {file = "multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92"}, + {file = "multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e"}, + {file = "multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4"}, + {file = "multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad"}, + {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:af7618b591bae552b40dbb6f93f5518328a949dac626ee75927bba1ecdeea9f4"}, + {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b6819f83aef06f560cb15482d619d0e623ce9bf155115150a85ab11b8342a665"}, + {file = "multidict-6.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d09384e75788861e046330308e7af54dd306aaf20eb760eb1d0de26b2bea2cb"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a59c63061f1a07b861c004e53869eb1211ffd1a4acbca330e3322efa6dd02978"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350f6b0fe1ced61e778037fdc7613f4051c8baf64b1ee19371b42a3acdb016a0"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c5cbac6b55ad69cb6aa17ee9343dfbba903118fd530348c330211dc7aa756d1"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:630f70c32b8066ddfd920350bc236225814ad94dfa493fe1910ee17fe4365cbb"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8d4916a81697faec6cb724a273bd5457e4c6c43d82b29f9dc02c5542fd21fc9"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e42332cf8276bb7645d310cdecca93a16920256a5b01bebf747365f86a1675b"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f3be27440f7644ab9a13a6fc86f09cdd90b347c3c5e30c6d6d860de822d7cb53"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:21f216669109e02ef3e2415ede07f4f8987f00de8cdfa0cc0b3440d42534f9f0"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d9890d68c45d1aeac5178ded1d1cccf3bc8d7accf1f976f79bf63099fb16e4bd"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:edfdcae97cdc5d1a89477c436b61f472c4d40971774ac4729c613b4b133163cb"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0b2e886624be5773e69cf32bcb8534aecdeb38943520b240fed3d5596a430f2f"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:be5bf4b3224948032a845d12ab0f69f208293742df96dc14c4ff9b09e508fc17"}, + {file = "multidict-6.6.4-cp39-cp39-win32.whl", hash = "sha256:10a68a9191f284fe9d501fef4efe93226e74df92ce7a24e301371293bd4918ae"}, + {file = "multidict-6.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee25f82f53262f9ac93bd7e58e47ea1bdcc3393cef815847e397cba17e284210"}, + {file = "multidict-6.6.4-cp39-cp39-win_arm64.whl", hash = "sha256:f9867e55590e0855bcec60d4f9a092b69476db64573c9fe17e92b0c50614c16a"}, + {file = "multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c"}, + {file = "multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + [[package]] name = "myst-parser" version = "4.0.1" @@ -846,6 +1257,18 @@ rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-bo testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pygments (<2.19)", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +groups = ["main"] +files = [ + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, +] + [[package]] name = "packaging" version = "25.0" @@ -858,6 +1281,114 @@ files = [ {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] +[[package]] +name = "propcache" +version = "0.3.2" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"}, + {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"}, + {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"}, + {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"}, + {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"}, + {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"}, + {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"}, + {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"}, + {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"}, + {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"}, + {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"}, + {file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"}, + {file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"}, + {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"}, + {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"}, +] + [[package]] name = "pycparser" version = "2.23" @@ -1859,6 +2390,125 @@ files = [ {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}, ] +[[package]] +name = "yarl" +version = "1.20.1" +description = "Yet another URL library" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"}, + {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"}, + {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"}, + {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"}, + {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"}, + {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"}, + {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"}, + {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"}, + {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"}, + {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"}, + {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"}, + {file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"}, + {file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"}, + {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"}, + {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.1" + [[package]] name = "zope-event" version = "6.0" @@ -1930,4 +2580,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "0fa7e6418ed0929d535552dbc2d2daa544dae02b57cf06cdc4caf2d04ebee9b7" +content-hash = "6aadd8b52593976cfa23d2355dbf15a89a71117e37185eb46b32693955e87219" diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 929ff45e48..51a46007ff 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -9,7 +9,7 @@ package-mode = false eventlet = "^0.40.3" futures = "2.2.0" gevent = "^23.9.1" -gremlinpython = "3.4.7" +gremlinpython = "3.7.4" python = "^3.10" pygments = "^2.18.0" recommonmark = "0.7.1" diff --git a/pyproject.toml b/pyproject.toml index f0664df914..a462273fdb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ requires-python = ">=3.9" "Issues" = "https://github.com/scylladb/python-driver/issues" [project.optional-dependencies] -graph = ['gremlinpython==3.4.6'] +graph = ['gremlinpython==3.7.4'] cle = ['cryptography>=35.0'] compress-lz4 = ['lz4'] compress-snappy = ['python-snappy'] From 22f3e3329fb1cbec564a9d5d4cc117c1476d0058 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 01:52:09 +0000 Subject: [PATCH 110/298] chore(deps): update dependency sphinx-sitemap to v2.9.0 --- docs/poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/poetry.lock b/docs/poetry.lock index 8cb08edbba..2e23599d91 100644 --- a/docs/poetry.lock +++ b/docs/poetry.lock @@ -1846,14 +1846,14 @@ sphinxcontrib-mermaid = ">=1.0.0,<2.0.0" [[package]] name = "sphinx-sitemap" -version = "2.8.0" +version = "2.9.0" description = "Sitemap generator for Sphinx" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "sphinx_sitemap-2.8.0-py3-none-any.whl", hash = "sha256:332042cd5b9385f61ec2861dfd550d9bccbdfcff86f6b68c7072cf40c9f16363"}, - {file = "sphinx_sitemap-2.8.0.tar.gz", hash = "sha256:749d7184a0c7b73d486a232b54b5c1b38a0e2d6f18cf19fb1b033b8162b44a82"}, + {file = "sphinx_sitemap-2.9.0-py3-none-any.whl", hash = "sha256:f1f1d3a9ad012ba17a7ef0b560d303bff2d0db26647567d6e810bcc754466664"}, + {file = "sphinx_sitemap-2.9.0.tar.gz", hash = "sha256:70f97bcdf444e3d68e118355cf82a1f54c4d3c03d651cd17fe87398b26e25e21"}, ] [package.dependencies] From 49a4e394a9c516f24f749e72b012f41049149782 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 04:28:38 +0000 Subject: [PATCH 111/298] chore(deps): update astral-sh/setup-uv action to v7 --- .github/workflows/integration-tests.yml | 2 +- .github/workflows/lib-build-and-push.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 349d4ee842..71d2d4a1b0 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -50,7 +50,7 @@ jobs: run: sudo apt-get install libev4 libev-dev - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index 901e2139e0..bb10b89edb 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -104,7 +104,7 @@ jobs: echo "CIBW_BEFORE_TEST_WINDOWS=(exit 0)" >> $GITHUB_ENV; - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: python-version: ${{ inputs.python-version }} @@ -167,7 +167,7 @@ jobs: - uses: actions/checkout@v5 - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: python-version: ${{ inputs.python-version }} From d6ae04037fbe9cee637e3f5acb2596493b79fa0d Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 12 Oct 2025 10:49:57 -0400 Subject: [PATCH 112/298] Stop support for 3.9 Python 3.9 reached it EOL date. We can drop it from everywhere. --- .github/workflows/integration-tests.yml | 2 +- .github/workflows/lib-build-and-push.yml | 2 +- README.rst | 2 +- docs/index.rst | 2 +- docs/installation.rst | 2 +- pyproject.toml | 3 ++- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 71d2d4a1b0..0572e1d58e 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -29,7 +29,7 @@ jobs: fail-fast: false matrix: java-version: [8] - python-version: ["3.9", "3.11", "3.12", "3.13"] + python-version: ["3.11", "3.12", "3.13"] event_loop_manager: ["libev", "asyncio", "asyncore"] exclude: - python-version: "3.12" diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index bb10b89edb..9e7e4bf496 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -98,7 +98,7 @@ jobs: echo "CIBW_TEST_COMMAND=true" >> $GITHUB_ENV; echo "CIBW_TEST_COMMAND_WINDOWS=(exit 0)" >> $GITHUB_ENV; echo "CIBW_TEST_SKIP=*" >> $GITHUB_ENV; - echo "CIBW_SKIP=cp2* cp36* pp36* cp37* pp37* cp38* pp38* *i686 *musllinux*" >> $GITHUB_ENV; + echo "CIBW_SKIP=cp2* cp36* pp36* cp37* pp37* cp38* pp38* cp39* pp39* *i686 *musllinux*" >> $GITHUB_ENV; echo "CIBW_BUILD=cp3* pp3*" >> $GITHUB_ENV; echo "CIBW_BEFORE_TEST=true" >> $GITHUB_ENV; echo "CIBW_BEFORE_TEST_WINDOWS=(exit 0)" >> $GITHUB_ENV; diff --git a/README.rst b/README.rst index f6a983a5b2..e71af2f47b 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ Scylla Enterprise (2018.1.x+) using exclusively Cassandra's binary protocol and .. image:: https://github.com/scylladb/python-driver/actions/workflows/integration-tests.yml/badge.svg?branch=master :target: https://github.com/scylladb/python-driver/actions/workflows/integration-tests.yml?query=event%3Apush+branch%3Amaster -The driver supports Python versions 3.9-3.13. +The driver supports Python versions 3.10-3.13. .. **Note:** This driver does not support big-endian systems. diff --git a/docs/index.rst b/docs/index.rst index 8c38dc315c..3192a17102 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,7 +4,7 @@ A Python client driver for `Scylla `_. This driver works exclusively with the Cassandra Query Language v3 (CQL3) and Cassandra's native protocol. -The driver supports Python 3.9-3.13. +The driver supports Python 3.10-3.13. This driver is open source under the `Apache v2 License `_. diff --git a/docs/installation.rst b/docs/installation.rst index 884380c2bc..41cd374c7d 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -3,7 +3,7 @@ Installation Supported Platforms ------------------- -Python versions 3.9-3.13 are supported. Both CPython (the standard Python +Python versions 3.10-3.13 are supported. Both CPython (the standard Python implementation) and `PyPy `_ are supported and tested. Linux, OSX, and Windows are supported. diff --git a/pyproject.toml b/pyproject.toml index a462273fdb..8b83c5fb5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,6 @@ classifiers = [ 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', @@ -137,6 +136,8 @@ skip = [ "pp37*", "cp38*", "pp38*", + "cp39*", + "pp39*", "*i686", "*musllinux*", ] From 377777e27df066ac128cd33ea3971ef17e15e729 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 9 Oct 2025 09:56:00 -0400 Subject: [PATCH 113/298] cicd: make openssl installation step stable It always getting broken due to the version being removed from the server. If we drop version it will pull newest version, which should work just fine. --- .github/workflows/lib-build-and-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index 9e7e4bf496..c1fea0cd4f 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -115,7 +115,7 @@ jobs: - name: Install OpenSSL for Windows if: runner.os == 'Windows' run: | - choco install openssl --version=3.5.3 -f -y --no-progress + choco install openssl.light --no-progress -y - name: Install Conan if: runner.os == 'Windows' From 38816d884779ea3042828300b39d98110244ea0e Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 12 Oct 2025 10:41:53 -0400 Subject: [PATCH 114/298] Update cibuildwheel to 3.2.1 New version does not support pypy wheels, so just remove them. Also new version does not support cp2* cp36* and cp37* builds anymore, so we can safely drop them. Exclude building freethreaded python wheels. --- .github/workflows/lib-build-and-push.yml | 6 ++---- pyproject.toml | 12 +++--------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index c1fea0cd4f..f7df5361e4 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -98,8 +98,6 @@ jobs: echo "CIBW_TEST_COMMAND=true" >> $GITHUB_ENV; echo "CIBW_TEST_COMMAND_WINDOWS=(exit 0)" >> $GITHUB_ENV; echo "CIBW_TEST_SKIP=*" >> $GITHUB_ENV; - echo "CIBW_SKIP=cp2* cp36* pp36* cp37* pp37* cp38* pp38* cp39* pp39* *i686 *musllinux*" >> $GITHUB_ENV; - echo "CIBW_BUILD=cp3* pp3*" >> $GITHUB_ENV; echo "CIBW_BEFORE_TEST=true" >> $GITHUB_ENV; echo "CIBW_BEFORE_TEST_WINDOWS=(exit 0)" >> $GITHUB_ENV; @@ -110,7 +108,7 @@ jobs: - name: Install cibuildwheel run: | - uv tool install 'cibuildwheel==2.22.0' + uv tool install 'cibuildwheel==3.2.1' - name: Install OpenSSL for Windows if: runner.os == 'Windows' @@ -127,7 +125,7 @@ jobs: conan profile detect conan install conanfile.py - - name: Install OpenSSL for MacOS + - name: Install libev for MacOS if: runner.os == 'MacOs' run: | brew install libev diff --git a/pyproject.toml b/pyproject.toml index 8b83c5fb5b..6f4e4a8f6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -129,15 +129,12 @@ tag_regex = '(?P\d*?\.\d*?\.\d*?)-scylla' build-frontend = "build[uv]" environment = { CASS_DRIVER_BUILD_CONCURRENCY = "2", CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST = "yes", CFLAGS = "-g0 -O3" } skip = [ - "cp2*", - "cp36*", - "pp36*", - "cp37*", - "pp37*", "cp38*", "pp38*", "cp39*", "pp39*", + "cp3*t-*", + "pp3*t-*", "*i686", "*musllinux*", ] @@ -149,6 +146,7 @@ manylinux-aarch64-image = "manylinux_2_28" manylinux-pypy_x86_64-image = "manylinux_2_28" manylinux-pypy_aarch64-image = "manylinux_2_28" +enable = ["pypy"] [tool.cibuildwheel.linux] before-build = "rm -rf ~/.pyxbld && rpm --import https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux && yum install -y libffi-devel libev libev-devel openssl openssl-devel" @@ -171,7 +169,3 @@ test-command = [ # TODO: set CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST to yes when https://github.com/scylladb/python-driver/issues/429 is fixed environment = { CASS_DRIVER_BUILD_CONCURRENCY = "2", CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST = "no" } - -[[tool.cibuildwheel.overrides]] -select = "pp*" -test-command = [] From f52edbc09aa5a84dd8c82965f20a412db1988a0f Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Wed, 15 Oct 2025 11:22:27 -0400 Subject: [PATCH 115/298] Add codex and claude files to .gitignore --- .gitignore | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.gitignore b/.gitignore index b96d8702d6..28cf1ba218 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,19 @@ docs/core_graph.rst docs/geo_types.rst docs/graph.rst docs/graph_fluent.rst + +# Codex - AI assistant metadata +.codex/ +.codex-cache/ +.codex-config.json +.codex-settings.json +codex.log +AGENTS.md + +# Claude - AI assistant metadata +.anthropic/ +.claude/ +claude.log +claude_history.json +claude_config.json +CLAUDE.md \ No newline at end of file From 99d430921bf779e9e93a7ca6c4643f1360ea4b96 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 16 Oct 2025 22:11:04 -0400 Subject: [PATCH 116/298] cicd: drop macos-13 runner It is being depricated, since we use it for x86 builds good replacement for it could be `macos-15-intel`. --- .github/workflows/lib-build-and-push.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index f7df5361e4..809f49e29d 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -61,7 +61,7 @@ jobs: was_added=1 elif [[ "${target}" == "macos-x86" ]]; then [ -n "$was_added" ] && echo -n "," >> /tmp/matrix.json - echo -n '{"os":"macos-13", "target": "macos-x86"}' >> /tmp/matrix.json + echo -n '{"os":"macos-15-intel", "target": "macos-x86"}' >> /tmp/matrix.json was_added=1 elif [[ "${target}" == "macos-arm" ]]; then [ -n "$was_added" ] && echo -n "," >> /tmp/matrix.json @@ -134,9 +134,9 @@ jobs: if: runner.os == 'MacOS' run: | ##### Set MACOSX_DEPLOYMENT_TARGET - if [ "${{ matrix.os }}" == "macos-13" ]; then - echo "MACOSX_DEPLOYMENT_TARGET=13.0" >> $GITHUB_ENV; - echo "Enforcing target deployment for 13.0" + if [ "${{ matrix.os }}" == "macos-15-intel" ]; then + echo "MACOSX_DEPLOYMENT_TARGET=15.0" >> $GITHUB_ENV; + echo "Enforcing target deployment for 15.0" elif [ "${{ matrix.os }}" == "macos-14" ]; then echo "MACOSX_DEPLOYMENT_TARGET=14.0" >> $GITHUB_ENV; echo "Enforcing target deployment for 14.0" From c1320dc50d6130d55ce3e56319547d397c69cfd5 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 26 Sep 2025 10:12:40 -0400 Subject: [PATCH 117/298] docs: rename workflows to .yml Currently they are .yaml while rest of the workflows are .yml. Let's set it inline with the rest. --- .github/workflows/{docs-pages.yaml => docs-pages.yml} | 0 .github/workflows/{docs-pr.yaml => docs-pr.yml} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{docs-pages.yaml => docs-pages.yml} (100%) rename .github/workflows/{docs-pr.yaml => docs-pr.yml} (100%) diff --git a/.github/workflows/docs-pages.yaml b/.github/workflows/docs-pages.yml similarity index 100% rename from .github/workflows/docs-pages.yaml rename to .github/workflows/docs-pages.yml diff --git a/.github/workflows/docs-pr.yaml b/.github/workflows/docs-pr.yml similarity index 100% rename from .github/workflows/docs-pr.yaml rename to .github/workflows/docs-pr.yml From e0abebd4ada6cc6b593ed13af92781be5716210a Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 26 Sep 2025 10:17:55 -0400 Subject: [PATCH 118/298] docs: update workflows triggering conditions 1. Trigger docs PR check on push to master and on PR events when it contains relevant changes 2. Include workflow files into triggering conditions --- .github/workflows/docs-pages.yml | 5 +++++ .github/workflows/docs-pr.yml | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index f12124c04c..a828e4f49d 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -9,6 +9,11 @@ on: - 'branch-**' paths: - "docs/**" + - ".github/workflows/docs-pages.yml" + - "cassandra/**" + - "pyproject.toml" + - "setup.py" + - "CHANGELOG.rst" workflow_dispatch: jobs: diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index 55e4b6e423..483838a4f2 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -3,12 +3,24 @@ name: "Docs / Build PR" # see https://sphinx-theme.scylladb.com/stable/deployment/production.html#available-workflows on: - pull_request: + push: branches: - master - - 'branch-**' paths: - "docs/**" + - ".github/workflows/docs-pr.yml" + - "cassandra/**" + - "pyproject.toml" + - "setup.py" + - "CHANGELOG.rst" + pull_request: + paths: + - "docs/**" + - ".github/workflows/docs-pr.yml" + - "cassandra/**" + - "pyproject.toml" + - "setup.py" + - "CHANGELOG.rst" workflow_dispatch: jobs: From bedd7f56ddc176988f8c3628fe4b637b3c4d2ed0 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 26 Sep 2025 10:19:59 -0400 Subject: [PATCH 119/298] docs: format workflow files --- .github/workflows/docs-pages.yml | 5 +++++ .github/workflows/docs-pr.yml | 3 +++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index a828e4f49d..95229b5224 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -26,16 +26,21 @@ jobs: ref: ${{ github.event.repository.default_branch }} persist-credentials: false fetch-depth: 0 + - name: Set up Python uses: actions/setup-python@v6 with: python-version: '3.10' + - name: Set up env run: make -C docs setupenv + - name: Build docs run: make -C docs multiversion + - name: Build redirects run: make -C docs redirects + - name: Deploy docs to GitHub Pages run: ./docs/_utils/deploy.sh env: diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index 483838a4f2..e240051915 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -32,11 +32,14 @@ jobs: with: persist-credentials: false fetch-depth: 0 + - name: Set up Python uses: actions/setup-python@v6 with: python-version: '3.10' + - name: Set up env run: make -C docs setupenv + - name: Build docs run: make -C docs test From 59c1a2abc033dea387904c0a8a35769bfb3f6fe6 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 26 Sep 2025 10:41:28 -0400 Subject: [PATCH 120/298] docs: update dependencies --- docs/poetry.lock | 311 ++++++++++++++------------------------------ docs/pyproject.toml | 25 ++-- 2 files changed, 112 insertions(+), 224 deletions(-) diff --git a/docs/poetry.lock b/docs/poetry.lock index 2e23599d91..03dac9d416 100644 --- a/docs/poetry.lock +++ b/docs/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "aenum" @@ -124,7 +124,6 @@ files = [ [package.dependencies] aiohappyeyeballs = ">=2.5.0" aiosignal = ">=1.4.0" -async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" @@ -148,7 +147,6 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" -typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} [[package]] name = "alabaster" @@ -175,10 +173,8 @@ files = [ ] [package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" -typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] trio = ["trio (>=0.26.1)"] @@ -565,25 +561,6 @@ greenlet = ">=1.0" [package.extras] dev = ["black", "build", "commitizen", "isort", "pip-tools", "pre-commit", "twine"] -[[package]] -name = "exceptiongroup" -version = "1.3.0" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version == \"3.10\"" -files = [ - {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, - {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} - -[package.extras] -test = ["pytest (>=6)"] - [[package]] name = "frozenlist" version = "1.7.0" @@ -698,18 +675,6 @@ files = [ {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, ] -[[package]] -name = "futures" -version = "2.2.0" -description = "Backport of the concurrent.futures package from Python 3.2" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "futures-2.2.0-py2.py3-none-any.whl", hash = "sha256:9fd22b354a4c4755ad8c7d161d93f5026aca4cfe999bd2e53168f14765c02cd6"}, - {file = "futures-2.2.0.tar.gz", hash = "sha256:151c057173474a3a40f897165951c0e33ad04f37de65b6de547ddef107fd0ed3"}, -] - [[package]] name = "geomet" version = "1.1.0" @@ -727,60 +692,60 @@ click = "*" [[package]] name = "gevent" -version = "23.9.1" +version = "25.9.1" description = "Coroutine-based network library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "gevent-23.9.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:a3c5e9b1f766a7a64833334a18539a362fb563f6c4682f9634dea72cbe24f771"}, - {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b101086f109168b23fa3586fccd1133494bdb97f86920a24dc0b23984dc30b69"}, - {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36a549d632c14684bcbbd3014a6ce2666c5f2a500f34d58d32df6c9ea38b6535"}, - {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:272cffdf535978d59c38ed837916dfd2b5d193be1e9e5dcc60a5f4d5025dd98a"}, - {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcb8612787a7f4626aa881ff15ff25439561a429f5b303048f0fca8a1c781c39"}, - {file = "gevent-23.9.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:d57737860bfc332b9b5aa438963986afe90f49645f6e053140cfa0fa1bdae1ae"}, - {file = "gevent-23.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5f3c781c84794926d853d6fb58554dc0dcc800ba25c41d42f6959c344b4db5a6"}, - {file = "gevent-23.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:dbb22a9bbd6a13e925815ce70b940d1578dbe5d4013f20d23e8a11eddf8d14a7"}, - {file = "gevent-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:707904027d7130ff3e59ea387dddceedb133cc742b00b3ffe696d567147a9c9e"}, - {file = "gevent-23.9.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:45792c45d60f6ce3d19651d7fde0bc13e01b56bb4db60d3f32ab7d9ec467374c"}, - {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e24c2af9638d6c989caffc691a039d7c7022a31c0363da367c0d32ceb4a0648"}, - {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e1ead6863e596a8cc2a03e26a7a0981f84b6b3e956101135ff6d02df4d9a6b07"}, - {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65883ac026731ac112184680d1f0f1e39fa6f4389fd1fc0bf46cc1388e2599f9"}, - {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7af500da05363e66f122896012acb6e101a552682f2352b618e541c941a011"}, - {file = "gevent-23.9.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c3e5d2fa532e4d3450595244de8ccf51f5721a05088813c1abd93ad274fe15e7"}, - {file = "gevent-23.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c84d34256c243b0a53d4335ef0bc76c735873986d478c53073861a92566a8d71"}, - {file = "gevent-23.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ada07076b380918829250201df1d016bdafb3acf352f35e5693b59dceee8dd2e"}, - {file = "gevent-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:921dda1c0b84e3d3b1778efa362d61ed29e2b215b90f81d498eb4d8eafcd0b7a"}, - {file = "gevent-23.9.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ed7a048d3e526a5c1d55c44cb3bc06cfdc1947d06d45006cc4cf60dedc628904"}, - {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c1abc6f25f475adc33e5fc2dbcc26a732608ac5375d0d306228738a9ae14d3b"}, - {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4368f341a5f51611411ec3fc62426f52ac3d6d42eaee9ed0f9eebe715c80184e"}, - {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52b4abf28e837f1865a9bdeef58ff6afd07d1d888b70b6804557e7908032e599"}, - {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52e9f12cd1cda96603ce6b113d934f1aafb873e2c13182cf8e86d2c5c41982ea"}, - {file = "gevent-23.9.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:de350fde10efa87ea60d742901e1053eb2127ebd8b59a7d3b90597eb4e586599"}, - {file = "gevent-23.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fde6402c5432b835fbb7698f1c7f2809c8d6b2bd9d047ac1f5a7c1d5aa569303"}, - {file = "gevent-23.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:dd6c32ab977ecf7c7b8c2611ed95fa4aaebd69b74bf08f4b4960ad516861517d"}, - {file = "gevent-23.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:455e5ee8103f722b503fa45dedb04f3ffdec978c1524647f8ba72b4f08490af1"}, - {file = "gevent-23.9.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7ccf0fd378257cb77d91c116e15c99e533374a8153632c48a3ecae7f7f4f09fe"}, - {file = "gevent-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d163d59f1be5a4c4efcdd13c2177baaf24aadf721fdf2e1af9ee54a998d160f5"}, - {file = "gevent-23.9.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7532c17bc6c1cbac265e751b95000961715adef35a25d2b0b1813aa7263fb397"}, - {file = "gevent-23.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:78eebaf5e73ff91d34df48f4e35581ab4c84e22dd5338ef32714264063c57507"}, - {file = "gevent-23.9.1-cp38-cp38-win32.whl", hash = "sha256:f632487c87866094546a74eefbca2c74c1d03638b715b6feb12e80120960185a"}, - {file = "gevent-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:62d121344f7465e3739989ad6b91f53a6ca9110518231553fe5846dbe1b4518f"}, - {file = "gevent-23.9.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:bf456bd6b992eb0e1e869e2fd0caf817f0253e55ca7977fd0e72d0336a8c1c6a"}, - {file = "gevent-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43daf68496c03a35287b8b617f9f91e0e7c0d042aebcc060cadc3f049aadd653"}, - {file = "gevent-23.9.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7c28e38dcde327c217fdafb9d5d17d3e772f636f35df15ffae2d933a5587addd"}, - {file = "gevent-23.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fae8d5b5b8fa2a8f63b39f5447168b02db10c888a3e387ed7af2bd1b8612e543"}, - {file = "gevent-23.9.1-cp39-cp39-win32.whl", hash = "sha256:2c7b5c9912378e5f5ccf180d1fdb1e83f42b71823483066eddbe10ef1a2fcaa2"}, - {file = "gevent-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:a2898b7048771917d85a1d548fd378e8a7b2ca963db8e17c6d90c76b495e0e2b"}, - {file = "gevent-23.9.1.tar.gz", hash = "sha256:72c002235390d46f94938a96920d8856d4ffd9ddf62a303a0d7c118894097e34"}, + {file = "gevent-25.9.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:856b990be5590e44c3a3dc6c8d48a40eaccbb42e99d2b791d11d1e7711a4297e"}, + {file = "gevent-25.9.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fe1599d0b30e6093eb3213551751b24feeb43db79f07e89d98dd2f3330c9063e"}, + {file = "gevent-25.9.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:f0d8b64057b4bf1529b9ef9bd2259495747fba93d1f836c77bfeaacfec373fd0"}, + {file = "gevent-25.9.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b56cbc820e3136ba52cd690bdf77e47a4c239964d5f80dc657c1068e0fe9521c"}, + {file = "gevent-25.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c5fa9ce5122c085983e33e0dc058f81f5264cebe746de5c401654ab96dddfca8"}, + {file = "gevent-25.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:03c74fec58eda4b4edc043311fca8ba4f8744ad1632eb0a41d5ec25413581975"}, + {file = "gevent-25.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a8ae9f895e8651d10b0a8328a61c9c53da11ea51b666388aa99b0ce90f9fdc27"}, + {file = "gevent-25.9.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5aff9e8342dc954adb9c9c524db56c2f3557999463445ba3d9cbe3dada7b7"}, + {file = "gevent-25.9.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1cdf6db28f050ee103441caa8b0448ace545364f775059d5e2de089da975c457"}, + {file = "gevent-25.9.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:812debe235a8295be3b2a63b136c2474241fa5c58af55e6a0f8cfc29d4936235"}, + {file = "gevent-25.9.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b28b61ff9216a3d73fe8f35669eefcafa957f143ac534faf77e8a19eb9e6883a"}, + {file = "gevent-25.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5e4b6278b37373306fc6b1e5f0f1cf56339a1377f67c35972775143d8d7776ff"}, + {file = "gevent-25.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d99f0cb2ce43c2e8305bf75bee61a8bde06619d21b9d0316ea190fc7a0620a56"}, + {file = "gevent-25.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:72152517ecf548e2f838c61b4be76637d99279dbaa7e01b3924df040aa996586"}, + {file = "gevent-25.9.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:46b188248c84ffdec18a686fcac5dbb32365d76912e14fda350db5dc0bfd4f86"}, + {file = "gevent-25.9.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f2b54ea3ca6f0c763281cd3f96010ac7e98c2e267feb1221b5a26e2ca0b9a692"}, + {file = "gevent-25.9.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7a834804ac00ed8a92a69d3826342c677be651b1c3cd66cc35df8bc711057aa2"}, + {file = "gevent-25.9.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:323a27192ec4da6b22a9e51c3d9d896ff20bc53fdc9e45e56eaab76d1c39dd74"}, + {file = "gevent-25.9.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6ea78b39a2c51d47ff0f130f4c755a9a4bbb2dd9721149420ad4712743911a51"}, + {file = "gevent-25.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:dc45cd3e1cc07514a419960af932a62eb8515552ed004e56755e4bf20bad30c5"}, + {file = "gevent-25.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34e01e50c71eaf67e92c186ee0196a039d6e4f4b35670396baed4a2d8f1b347f"}, + {file = "gevent-25.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:4acd6bcd5feabf22c7c5174bd3b9535ee9f088d2bbce789f740ad8d6554b18f3"}, + {file = "gevent-25.9.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:4f84591d13845ee31c13f44bdf6bd6c3dbf385b5af98b2f25ec328213775f2ed"}, + {file = "gevent-25.9.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9cdbb24c276a2d0110ad5c978e49daf620b153719ac8a548ce1250a7eb1b9245"}, + {file = "gevent-25.9.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:88b6c07169468af631dcf0fdd3658f9246d6822cc51461d43f7c44f28b0abb82"}, + {file = "gevent-25.9.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b7bb0e29a7b3e6ca9bed2394aa820244069982c36dc30b70eb1004dd67851a48"}, + {file = "gevent-25.9.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2951bb070c0ee37b632ac9134e4fdaad70d2e660c931bb792983a0837fe5b7d7"}, + {file = "gevent-25.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4e17c2d57e9a42e25f2a73d297b22b60b2470a74be5a515b36c984e1a246d47"}, + {file = "gevent-25.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d94936f8f8b23d9de2251798fcb603b84f083fdf0d7f427183c1828fb64f117"}, + {file = "gevent-25.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:eb51c5f9537b07da673258b4832f6635014fee31690c3f0944d34741b69f92fa"}, + {file = "gevent-25.9.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:1a3fe4ea1c312dbf6b375b416925036fe79a40054e6bf6248ee46526ea628be1"}, + {file = "gevent-25.9.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0adb937f13e5fb90cca2edf66d8d7e99d62a299687400ce2edee3f3504009356"}, + {file = "gevent-25.9.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:427f869a2050a4202d93cf7fd6ab5cffb06d3e9113c10c967b6e2a0d45237cb8"}, + {file = "gevent-25.9.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c049880175e8c93124188f9d926af0a62826a3b81aa6d3074928345f8238279e"}, + {file = "gevent-25.9.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b5a67a0974ad9f24721034d1e008856111e0535f1541499f72a733a73d658d1c"}, + {file = "gevent-25.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1d0f5d8d73f97e24ea8d24d8be0f51e0cf7c54b8021c1fddb580bf239474690f"}, + {file = "gevent-25.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ddd3ff26e5c4240d3fbf5516c2d9d5f2a998ef87cfb73e1429cfaeaaec860fa6"}, + {file = "gevent-25.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:bb63c0d6cb9950cc94036a4995b9cc4667b8915366613449236970f4394f94d7"}, + {file = "gevent-25.9.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f18f80aef6b1f6907219affe15b36677904f7cfeed1f6a6bc198616e507ae2d7"}, + {file = "gevent-25.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b274a53e818124a281540ebb4e7a2c524778f745b7a99b01bdecf0ca3ac0ddb0"}, + {file = "gevent-25.9.1-cp39-cp39-win32.whl", hash = "sha256:c6c91f7e33c7f01237755884316110ee7ea076f5bdb9aa0982b6dc63243c0a38"}, + {file = "gevent-25.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:012a44b0121f3d7c800740ff80351c897e85e76a7e4764690f35c5ad9ec17de5"}, + {file = "gevent-25.9.1.tar.gz", hash = "sha256:adf9cd552de44a4e6754c51ff2e78d9193b7fa6eab123db9578a210e657235dd"}, ] [package.dependencies] -cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} -greenlet = [ - {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""}, - {version = ">=3.0rc3", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\""}, -] +cffi = {version = ">=1.17.1", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} +greenlet = {version = ">=3.2.2", markers = "platform_python_implementation == \"CPython\""} "zope.event" = "*" "zope.interface" = "*" @@ -788,8 +753,8 @@ greenlet = [ dnspython = ["dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\""] docs = ["furo", "repoze.sphinx.autointerface", "sphinx", "sphinxcontrib-programoutput", "zope.schema"] monitor = ["psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\""] -recommended = ["cffi (>=1.12.2) ; platform_python_implementation == \"CPython\"", "dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\"", "psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\""] -test = ["cffi (>=1.12.2) ; platform_python_implementation == \"CPython\"", "coverage (>=5.0) ; sys_platform != \"win32\"", "dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\"", "objgraph", "psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\"", "requests", "setuptools"] +recommended = ["cffi (>=1.17.1) ; platform_python_implementation == \"CPython\"", "dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\"", "psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\""] +test = ["cffi (>=1.17.1) ; platform_python_implementation == \"CPython\"", "coverage (>=5.0) ; sys_platform != \"win32\"", "dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\"", "objgraph", "psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\"", "requests"] [[package]] name = "greenlet" @@ -958,7 +923,6 @@ description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\"" files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -977,31 +941,6 @@ profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] -[[package]] -name = "markdown-it-py" -version = "4.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.10" -groups = ["main"] -markers = "python_version == \"3.10\" or platform_python_implementation != \"CPython\"" -files = [ - {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, - {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "markdown-it-pyrs", "mistletoe (>=1.0,<2.0)", "mistune (>=3.0,<4.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins (>=0.5.0)"] -profiling = ["gprof2dot"] -rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"] - [[package]] name = "markupsafe" version = "3.0.2" @@ -1080,7 +1019,6 @@ description = "Collection of plugins for markdown-it-py" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\"" files = [ {file = "mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f"}, {file = "mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6"}, @@ -1226,9 +1164,6 @@ files = [ {file = "multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} - [[package]] name = "myst-parser" version = "4.0.1" @@ -1236,7 +1171,6 @@ description = "An extended [CommonMark](https://spec.commonmark.org/) compliant optional = false python-versions = ">=3.10" groups = ["main"] -markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\"" files = [ {file = "myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d"}, {file = "myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4"}, @@ -1557,6 +1491,22 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +description = "Manipulate well-formed Roman numerals" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c"}, + {file = "roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d"}, +] + +[package.extras] +lint = ["mypy (==1.15.0)", "pyright (==1.1.394)", "ruff (==0.9.7)"] +test = ["pytest (>=8)"] + [[package]] name = "scales" version = "1.0.9" @@ -1678,18 +1628,18 @@ files = [ [[package]] name = "sphinx" -version = "7.4.7" +version = "8.2.3" description = "Python documentation generator" optional = false -python-versions = ">=3.9" +python-versions = ">=3.11" groups = ["main"] files = [ - {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, - {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, + {file = "sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3"}, + {file = "sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348"}, ] [package.dependencies] -alabaster = ">=0.7.14,<0.8.0" +alabaster = ">=0.7.14" babel = ">=2.13" colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} docutils = ">=0.20,<0.22" @@ -1698,35 +1648,35 @@ Jinja2 = ">=3.1" packaging = ">=23.0" Pygments = ">=2.17" requests = ">=2.30.0" +roman-numerals-py = ">=1.0.0" snowballstemmer = ">=2.2" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" +sphinxcontrib-applehelp = ">=1.0.7" +sphinxcontrib-devhelp = ">=1.0.6" +sphinxcontrib-htmlhelp = ">=2.0.6" +sphinxcontrib-jsmath = ">=1.0.1" +sphinxcontrib-qthelp = ">=1.0.6" sphinxcontrib-serializinghtml = ">=1.1.9" -tomli = {version = ">=2", markers = "python_version < \"3.11\""} [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] -test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] +lint = ["betterproto (==2.0.0b6)", "mypy (==1.15.0)", "pypi-attestations (==0.0.21)", "pyright (==1.1.395)", "pytest (>=8.0)", "ruff (==0.9.9)", "sphinx-lint (>=0.9)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.19.0.20250219)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241128)", "types-requests (==2.32.0.20241016)", "types-urllib3 (==1.26.25.14)"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "pytest-xdist[psutil] (>=3.4)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] [[package]] name = "sphinx-autobuild" -version = "2024.10.3" +version = "2025.8.25" description = "Rebuild Sphinx documentation on changes, with hot reloading in the browser." optional = false -python-versions = ">=3.9" +python-versions = ">=3.11" groups = ["main"] files = [ - {file = "sphinx_autobuild-2024.10.3-py3-none-any.whl", hash = "sha256:158e16c36f9d633e613c9aaf81c19b0fc458ca78b112533b20dafcda430d60fa"}, - {file = "sphinx_autobuild-2024.10.3.tar.gz", hash = "sha256:248150f8f333e825107b6d4b86113ab28fa51750e5f9ae63b59dc339be951fb1"}, + {file = "sphinx_autobuild-2025.8.25-py3-none-any.whl", hash = "sha256:b750ac7d5a18603e4665294323fd20f6dcc0a984117026d1986704fa68f0379a"}, + {file = "sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213"}, ] [package.dependencies] colorama = ">=0.4.6" -sphinx = "*" +Sphinx = "*" starlette = ">=0.35" uvicorn = ">=0.25" watchfiles = ">=0.20" @@ -1862,28 +1812,6 @@ sphinx-last-updated-by-git = "*" [package.extras] dev = ["build", "flake8", "pre-commit", "pytest", "sphinx", "sphinx-last-updated-by-git", "tox"] -[[package]] -name = "sphinx-substitution-extensions" -version = "2025.1.2" -description = "Extensions for Sphinx which allow for substitutions." -optional = false -python-versions = ">=3.10" -groups = ["main"] -markers = "python_version == \"3.10\" or platform_python_implementation != \"CPython\"" -files = [ - {file = "sphinx_substitution_extensions-2025.1.2-py2.py3-none-any.whl", hash = "sha256:ff14f40e4393bd7434a196badb8d47983355d9755af884b902e3023fb456b958"}, - {file = "sphinx_substitution_extensions-2025.1.2.tar.gz", hash = "sha256:53b8d394d5098a09aef36bc687fa310aeb28466319d2c750e996e46400fb2474"}, -] - -[package.dependencies] -beartype = ">=0.18.5" -docutils = ">=0.19" -sphinx = ">=7.3.5" - -[package.extras] -dev = ["actionlint-py (==1.7.5.21)", "check-manifest (==0.50)", "deptry (==0.21.2)", "doc8 (==1.1.2)", "doccmd (==2024.12.26)", "docformatter (==1.7.5)", "interrogate (==1.7.0)", "mypy-strict-kwargs (==2024.12.25)", "mypy[faster-cache] (==1.14.1)", "myst-parser (==4.0.0)", "pre-commit (==4.0.1)", "pyenchant (==3.3.0rc1)", "pylint (==3.3.3)", "pyproject-fmt (==2.5.0)", "pyright (==1.1.391)", "pyroma (==4.2)", "pytest (==8.3.4)", "pytest-cov (==6.0.0)", "ruff (==0.8.4)", "shellcheck-py (==0.10.0.1)", "shfmt-py (==3.7.0.1)", "sphinx-toolbox (==3.8.1)", "sphinx[test] (==8.1.3)", "types-docutils (==0.21.0.20241128)", "vulture (==2.14)", "yamlfix (==1.17.0)"] -release = ["check-wheel-contents (==0.6.1)"] - [[package]] name = "sphinx-substitution-extensions" version = "2025.2.19" @@ -1891,7 +1819,6 @@ description = "Extensions for Sphinx which allow for substitutions." optional = false python-versions = ">=3.11" groups = ["main"] -markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\"" files = [ {file = "sphinx_substitution_extensions-2025.2.19-py2.py3-none-any.whl", hash = "sha256:dfdaa3a925ff5ab450ff89ae08e9989f90f04add362375f5c8e27309573e5343"}, {file = "sphinx_substitution_extensions-2025.2.19.tar.gz", hash = "sha256:ecbb35e7ae210aef4e213a389e5095df503dd1260374640c426d843ad64c8f86"}, @@ -2061,67 +1988,30 @@ files = [ [package.dependencies] anyio = ">=3.6.2,<5" -typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} [package.extras] full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] -[[package]] -name = "tomli" -version = "2.2.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version == \"3.10\"" -files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, -] - [[package]] name = "tornado" -version = "4.5.3" +version = "6.5.2" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false -python-versions = "*" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "tornado-4.5.3-cp35-cp35m-win32.whl", hash = "sha256:92b7ca81e18ba9ec3031a7ee73d4577ac21d41a0c9b775a9182f43301c3b5f8e"}, - {file = "tornado-4.5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:b36298e9f63f18cad97378db2222c0e0ca6a55f6304e605515e05a25483ed51a"}, - {file = "tornado-4.5.3-cp36-cp36m-win32.whl", hash = "sha256:ab587996fe6fb9ce65abfda440f9b61e4f9f2cf921967723540679176915e4c3"}, - {file = "tornado-4.5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:5ef073ac6180038ccf99411fe05ae9aafb675952a2c8db60592d5daf8401f803"}, - {file = "tornado-4.5.3.tar.gz", hash = "sha256:6d14e47eab0e15799cf3cdcc86b0b98279da68522caace2bd7ce644287685f0a"}, + {file = "tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6"}, + {file = "tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef"}, + {file = "tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e"}, + {file = "tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882"}, + {file = "tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108"}, + {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c"}, + {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4"}, + {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04"}, + {file = "tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0"}, + {file = "tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f"}, + {file = "tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af"}, + {file = "tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0"}, ] [[package]] @@ -2187,7 +2077,6 @@ files = [ [package.dependencies] click = ">=7.0" h11 = ">=0.8" -typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] @@ -2579,5 +2468,5 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] lock-version = "2.1" -python-versions = "^3.10" -content-hash = "6aadd8b52593976cfa23d2355dbf15a89a71117e37185eb46b32693955e87219" +python-versions = "^3.13" +content-hash = "d9c72e9c0e672fa64a6680e9db9b861017addd31468fbdb6fb5fa8354a8f7150" diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 51a46007ff..1c6a4d138f 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -6,22 +6,21 @@ authors = ["Python Driver Contributors"] package-mode = false [tool.poetry.dependencies] -eventlet = "^0.40.3" -futures = "2.2.0" -gevent = "^23.9.1" +eventlet = ">=0.40.3,<1.0.0" +gevent = ">=25.9.1,<26.0.0" gremlinpython = "3.7.4" -python = "^3.10" -pygments = "^2.18.0" +python = "^3.13" +pygments = ">=2.19.2,<3.0.0" recommonmark = "0.7.1" -redirects_cli = "~0.1.2" -sphinx-autobuild = "^2024.4.19" -sphinx-sitemap = "^2.6.0" -sphinx-scylladb-theme = "^1.8.1" -sphinx-multiversion-scylla = "^0.3.1" -Sphinx = "^7.3.7" -scales = "^1.0.9" +redirects_cli = "~0.1.3" +sphinx-autobuild = ">=2025.0.0,<2026.0.0" +sphinx-sitemap = ">=2.8.0,<3.0.0" +sphinx-scylladb-theme = ">=1.8.2,<2.0.0" +sphinx-multiversion-scylla = ">=0.3.2,<1.0.0" +Sphinx = ">=8.2.3,<9.0.0" +scales = ">=1.0.9,<2.0.0" six = ">=1.9" -tornado = ">=4.0,<5.0" +tornado = ">=6.5,<7.0" scylla-driver = { path = "../", develop = true } From e63b4b9bf20492cbbcd30ddfc88138a35f89f933 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 26 Sep 2025 10:26:04 -0400 Subject: [PATCH 121/298] docs: switch from poetry to uv uv provides better user experience. As result of switching form poetry to uv environment preparation became seamless, to the point that `setup` step could have been completely dropped. --- .github/workflows/docs-pages.yml | 10 +- .github/workflows/docs-pr.yml | 10 +- docs/Makefile | 44 +- docs/conf.py | 17 +- docs/poetry.lock | 2472 ------------------------------ docs/pyproject.toml | 74 +- docs/uv.lock | 1326 ++++++++++++++++ 7 files changed, 1423 insertions(+), 2530 deletions(-) delete mode 100644 docs/poetry.lock create mode 100644 docs/uv.lock diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index 95229b5224..cea5e395e5 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -27,13 +27,11 @@ jobs: persist-credentials: false fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v6 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: - python-version: '3.10' - - - name: Set up env - run: make -C docs setupenv + working-directory: docs + enable-cache: true - name: Build docs run: make -C docs multiversion diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index e240051915..b7230ad2d3 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -33,13 +33,11 @@ jobs: persist-credentials: false fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v6 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: - python-version: '3.10' - - - name: Set up env - run: make -C docs setupenv + working-directory: docs + enable-cache: true - name: Build docs run: make -C docs test diff --git a/docs/Makefile b/docs/Makefile index b1c54c8199..5c848a8769 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,8 +1,9 @@ # Global variables # You can set these variables from the command line. -POETRY = poetry +SHELL = bash +UV = uv SPHINXOPTS = -j auto -SPHINXBUILD = $(POETRY) run sphinx-build +SPHINXBUILD = $(UV) run --frozen sphinx-build PAPER = BUILDDIR = _build SOURCEDIR = . @@ -17,18 +18,13 @@ TESTSPHINXOPTS = $(ALLSPHINXOPTS) -W --keep-going all: dirhtml # Setup commands -.PHONY: setupenv -setupenv: - pip install -q poetry - sudo apt-get install gcc python3-dev libev4 libev-dev - -.PHONY: setup -setup: - $(POETRY) install +#.PHONY: setupenv +#setupenv: +# uv pip install -r <(uv pip compile pyproject.toml) .PHONY: update update: - $(POETRY) update + $(UV) update # Clean commands .PHONY: pristine @@ -41,58 +37,58 @@ clean: # Generate output commands .PHONY: dirhtml -dirhtml: setup +dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml -singlehtml: setup +singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: epub -epub: setup +epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 -epub3: setup +epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: multiversion -multiversion: setup - $(POETRY) run sphinx-multiversion $(SOURCEDIR) $(BUILDDIR)/dirhtml +multiversion: + $(UV) run --frozen sphinx-multiversion $(SOURCEDIR) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: redirects -redirects: setup - $(POETRY) run redirects-cli fromfile --yaml-file _utils/redirects.yaml --output-dir $(BUILDDIR)/dirhtml +redirects: + $(UV) run --frozen redirects-cli fromfile --yaml-file _utils/redirects.yaml --output-dir $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." # Preview commands .PHONY: preview -preview: setup - $(POETRY) run sphinx-autobuild -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml --port 5500 +preview: + $(UV) run --frozen sphinx-autobuild -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml --port 5500 .PHONY: multiversionpreview multiversionpreview: multiversion - $(POETRY) run python -m http.server 5500 --directory $(BUILDDIR)/dirhtml + $(UV) run --frozen python -m http.server 5500 --directory $(BUILDDIR)/dirhtml # Test commands .PHONY: test -test: setup +test: $(SPHINXBUILD) -b dirhtml $(TESTSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: linkcheck -linkcheck: setup +linkcheck: $(SPHINXBUILD) -b linkcheck $(SOURCEDIR) $(BUILDDIR)/linkcheck diff --git a/docs/conf.py b/docs/conf.py index e505986661..28f025661f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,7 +65,16 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = [ + "_build", + "Thumbs.db", + ".DS_Store", + ".venv", + ".venv/**", + "**/site-packages/**", + "**/*.dist-info/**", + "**/licenses/**", +] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' @@ -134,3 +143,9 @@ # Dictionary of values to pass into the template engine’s context for all pages html_context = {'html_baseurl': html_baseurl} +autodoc_mock_imports = [ + # Asyncore has been removed from python 3.12, we need to mock it until `cassandra/io/asyncorereactor.py` is dropped + "asyncore", + # Since driver is not built, binary modules also not built, so we need to mock them + "cassandra.io.libevwrapper" +] diff --git a/docs/poetry.lock b/docs/poetry.lock deleted file mode 100644 index 03dac9d416..0000000000 --- a/docs/poetry.lock +++ /dev/null @@ -1,2472 +0,0 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. - -[[package]] -name = "aenum" -version = "2.2.6" -description = "Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "aenum-2.2.6-py2-none-any.whl", hash = "sha256:aaebe735508d9cbc72cd6adfb59660a5e676dfbeb6fb24fb090041e7ddb8d3b3"}, - {file = "aenum-2.2.6-py3-none-any.whl", hash = "sha256:f9d20f7302ce3dc3639b3f75c3b3e146f3b22409a6b4513c1f0bd6dbdfcbd8c1"}, - {file = "aenum-2.2.6.tar.gz", hash = "sha256:260225470b49429f5893a195a8b99c73a8d182be42bf90c37c93e7b20e44eaae"}, -] - -[[package]] -name = "aiohappyeyeballs" -version = "2.6.1" -description = "Happy Eyeballs for asyncio" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, -] - -[[package]] -name = "aiohttp" -version = "3.12.15" -description = "Async http client/server framework (asyncio)" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc"}, - {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af"}, - {file = "aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6"}, - {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065"}, - {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1"}, - {file = "aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a"}, - {file = "aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830"}, - {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117"}, - {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe"}, - {file = "aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b"}, - {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7"}, - {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685"}, - {file = "aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b"}, - {file = "aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d"}, - {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7"}, - {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444"}, - {file = "aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545"}, - {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea"}, - {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3"}, - {file = "aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1"}, - {file = "aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34"}, - {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315"}, - {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd"}, - {file = "aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d"}, - {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64"}, - {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51"}, - {file = "aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0"}, - {file = "aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84"}, - {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:691d203c2bdf4f4637792efbbcdcd157ae11e55eaeb5e9c360c1206fb03d4d98"}, - {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e995e1abc4ed2a454c731385bf4082be06f875822adc4c6d9eaadf96e20d406"}, - {file = "aiohttp-3.12.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bd44d5936ab3193c617bfd6c9a7d8d1085a8dc8c3f44d5f1dcf554d17d04cf7d"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46749be6e89cd78d6068cdf7da51dbcfa4321147ab8e4116ee6678d9a056a0cf"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c643f4d75adea39e92c0f01b3fb83d57abdec8c9279b3078b68a3a52b3933b6"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a23918fedc05806966a2438489dcffccbdf83e921a1170773b6178d04ade142"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74bdd8c864b36c3673741023343565d95bfbd778ffe1eb4d412c135a28a8dc89"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a146708808c9b7a988a4af3821379e379e0f0e5e466ca31a73dbdd0325b0263"}, - {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7011a70b56facde58d6d26da4fec3280cc8e2a78c714c96b7a01a87930a9530"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3bdd6e17e16e1dbd3db74d7f989e8af29c4d2e025f9828e6ef45fbdee158ec75"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57d16590a351dfc914670bd72530fd78344b885a00b250e992faea565b7fdc05"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bc9a0f6569ff990e0bbd75506c8d8fe7214c8f6579cca32f0546e54372a3bb54"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:536ad7234747a37e50e7b6794ea868833d5220b49c92806ae2d7e8a9d6b5de02"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f0adb4177fa748072546fb650d9bd7398caaf0e15b370ed3317280b13f4083b0"}, - {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14954a2988feae3987f1eb49c706bff39947605f4b6fa4027c1d75743723eb09"}, - {file = "aiohttp-3.12.15-cp39-cp39-win32.whl", hash = "sha256:b784d6ed757f27574dca1c336f968f4e81130b27595e458e69457e6878251f5d"}, - {file = "aiohttp-3.12.15-cp39-cp39-win_amd64.whl", hash = "sha256:86ceded4e78a992f835209e236617bffae649371c4a50d5e5a3987f237db84b8"}, - {file = "aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2"}, -] - -[package.dependencies] -aiohappyeyeballs = ">=2.5.0" -aiosignal = ">=1.4.0" -attrs = ">=17.3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -propcache = ">=0.2.0" -yarl = ">=1.17.0,<2.0" - -[package.extras] -speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] - -[[package]] -name = "aiosignal" -version = "1.4.0" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, - {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, -] - -[package.dependencies] -frozenlist = ">=1.1.0" - -[[package]] -name = "alabaster" -version = "0.7.16" -description = "A light, configurable Sphinx theme" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, - {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, -] - -[[package]] -name = "anyio" -version = "4.10.0" -description = "High-level concurrency and networking framework on top of asyncio or Trio" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, - {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, -] - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" - -[package.extras] -trio = ["trio (>=0.26.1)"] - -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - -[[package]] -name = "attrs" -version = "25.3.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, - {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, -] - -[package.extras] -benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] - -[[package]] -name = "babel" -version = "2.17.0" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, - {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, -] - -[package.extras] -dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] - -[[package]] -name = "beartype" -version = "0.21.0" -description = "Unbearably fast near-real-time hybrid runtime-static type-checking in pure Python." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "beartype-0.21.0-py3-none-any.whl", hash = "sha256:b6a1bd56c72f31b0a496a36cc55df6e2f475db166ad07fa4acc7e74f4c7f34c0"}, - {file = "beartype-0.21.0.tar.gz", hash = "sha256:f9a5078f5ce87261c2d22851d19b050b64f6a805439e8793aecf01ce660d3244"}, -] - -[package.extras] -dev = ["autoapi (>=0.9.0)", "click", "coverage (>=5.5)", "equinox ; sys_platform == \"linux\"", "jax[cpu] ; sys_platform == \"linux\"", "jaxtyping ; sys_platform == \"linux\"", "langchain", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\"", "numba ; python_version < \"3.13.0\"", "numpy ; sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera", "pydata-sphinx-theme (<=0.7.2)", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "rich-click", "sphinx", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)", "sqlalchemy", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)", "xarray"] -doc-rtd = ["autoapi (>=0.9.0)", "pydata-sphinx-theme (<=0.7.2)", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)"] -test = ["click", "coverage (>=5.5)", "equinox ; sys_platform == \"linux\"", "jax[cpu] ; sys_platform == \"linux\"", "jaxtyping ; sys_platform == \"linux\"", "langchain", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\"", "numba ; python_version < \"3.13.0\"", "numpy ; sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "rich-click", "sphinx", "sqlalchemy", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)", "xarray"] -test-tox = ["click", "equinox ; sys_platform == \"linux\"", "jax[cpu] ; sys_platform == \"linux\"", "jaxtyping ; sys_platform == \"linux\"", "langchain", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\"", "numba ; python_version < \"3.13.0\"", "numpy ; sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "rich-click", "sphinx", "sqlalchemy", "typing-extensions (>=3.10.0.0)", "xarray"] -test-tox-coverage = ["coverage (>=5.5)"] - -[[package]] -name = "beautifulsoup4" -version = "4.13.5" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.7.0" -groups = ["main"] -files = [ - {file = "beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a"}, - {file = "beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695"}, -] - -[package.dependencies] -soupsieve = ">1.2" -typing-extensions = ">=4.0.0" - -[package.extras] -cchardet = ["cchardet"] -chardet = ["chardet"] -charset-normalizer = ["charset-normalizer"] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "certifi" -version = "2025.8.3" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, - {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, -] - -[[package]] -name = "cffi" -version = "2.0.0" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\"" -files = [ - {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, - {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, - {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, - {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, - {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, - {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, - {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, - {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, - {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, - {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, - {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, - {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, - {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, - {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, - {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, - {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, - {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, - {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, - {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, - {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, - {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, - {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, - {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, - {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, - {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, - {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, - {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, - {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, - {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, - {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, - {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, - {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, - {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, - {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, - {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, - {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, - {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, - {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, - {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, - {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, - {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, - {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, -] - -[package.dependencies] -pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} - -[[package]] -name = "charset-normalizer" -version = "3.4.3" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, - {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, - {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, -] - -[[package]] -name = "click" -version = "8.2.1" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, - {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "commonmark" -version = "0.9.1" -description = "Python parser for the CommonMark Markdown spec" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, - {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, -] - -[package.extras] -test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] - -[[package]] -name = "dnspython" -version = "2.8.0" -description = "DNS toolkit" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af"}, - {file = "dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f"}, -] - -[package.extras] -dev = ["black (>=25.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.17.0)", "mypy (>=1.17)", "pylint (>=3)", "pytest (>=8.4)", "pytest-cov (>=6.2.0)", "quart-trio (>=0.12.0)", "sphinx (>=8.2.0)", "sphinx-rtd-theme (>=3.0.0)", "twine (>=6.1.0)", "wheel (>=0.45.0)"] -dnssec = ["cryptography (>=45)"] -doh = ["h2 (>=4.2.0)", "httpcore (>=1.0.0)", "httpx (>=0.28.0)"] -doq = ["aioquic (>=1.2.0)"] -idna = ["idna (>=3.10)"] -trio = ["trio (>=0.30)"] -wmi = ["wmi (>=1.5.1) ; platform_system == \"Windows\""] - -[[package]] -name = "docutils" -version = "0.21.2" -description = "Docutils -- Python Documentation Utilities" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, - {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, -] - -[[package]] -name = "eventlet" -version = "0.40.3" -description = "Highly concurrent networking library" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "eventlet-0.40.3-py3-none-any.whl", hash = "sha256:e681cae6ee956cfb066a966b5c0541e734cc14879bda6058024104790595ac9d"}, - {file = "eventlet-0.40.3.tar.gz", hash = "sha256:290852db0065d78cec17a821b78c8a51cafb820a792796a354592ae4d5fceeb0"}, -] - -[package.dependencies] -dnspython = ">=1.15.0" -greenlet = ">=1.0" - -[package.extras] -dev = ["black", "build", "commitizen", "isort", "pip-tools", "pre-commit", "twine"] - -[[package]] -name = "frozenlist" -version = "1.7.0" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"}, - {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"}, - {file = "frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615"}, - {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd"}, - {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718"}, - {file = "frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e"}, - {file = "frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464"}, - {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a"}, - {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750"}, - {file = "frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86"}, - {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898"}, - {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56"}, - {file = "frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7"}, - {file = "frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d"}, - {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"}, - {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"}, - {file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"}, - {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"}, - {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"}, - {file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"}, - {file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"}, - {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"}, - {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"}, - {file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"}, - {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"}, - {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"}, - {file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"}, - {file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"}, - {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"}, - {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"}, - {file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"}, - {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"}, - {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"}, - {file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"}, - {file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"}, - {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cea3dbd15aea1341ea2de490574a4a37ca080b2ae24e4b4f4b51b9057b4c3630"}, - {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d536ee086b23fecc36c2073c371572374ff50ef4db515e4e503925361c24f71"}, - {file = "frozenlist-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfcebf56f703cb2e346315431699f00db126d158455e513bd14089d992101e44"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974c5336e61d6e7eb1ea5b929cb645e882aadab0095c5a6974a111e6479f8878"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c70db4a0ab5ab20878432c40563573229a7ed9241506181bba12f6b7d0dc41cb"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1137b78384eebaf70560a36b7b229f752fb64d463d38d1304939984d5cb887b6"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e793a9f01b3e8b5c0bc646fb59140ce0efcc580d22a3468d70766091beb81b35"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74739ba8e4e38221d2c5c03d90a7e542cb8ad681915f4ca8f68d04f810ee0a87"}, - {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e63344c4e929b1a01e29bc184bbb5fd82954869033765bfe8d65d09e336a677"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ea2a7369eb76de2217a842f22087913cdf75f63cf1307b9024ab82dfb525938"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:836b42f472a0e006e02499cef9352ce8097f33df43baaba3e0a28a964c26c7d2"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e22b9a99741294b2571667c07d9f8cceec07cb92aae5ccda39ea1b6052ed4319"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a19e85cc503d958abe5218953df722748d87172f71b73cf3c9257a91b999890"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f22dac33bb3ee8fe3e013aa7b91dc12f60d61d05b7fe32191ffa84c3aafe77bd"}, - {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ccec739a99e4ccf664ea0775149f2749b8a6418eb5b8384b4dc0a7d15d304cb"}, - {file = "frozenlist-1.7.0-cp39-cp39-win32.whl", hash = "sha256:b3950f11058310008a87757f3eee16a8e1ca97979833239439586857bc25482e"}, - {file = "frozenlist-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:43a82fce6769c70f2f5a06248b614a7d268080a9d20f7457ef10ecee5af82b63"}, - {file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"}, - {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, -] - -[[package]] -name = "geomet" -version = "1.1.0" -description = "Pure Python conversion library for common geospatial data formats" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "geomet-1.1.0-py3-none-any.whl", hash = "sha256:4372fe4e286a34acc6f2e9308284850bd8c4aa5bc12065e2abbd4995900db12f"}, - {file = "geomet-1.1.0.tar.gz", hash = "sha256:51e92231a0ef6aaa63ac20c443377ba78a303fd2ecd179dc3567de79f3c11605"}, -] - -[package.dependencies] -click = "*" - -[[package]] -name = "gevent" -version = "25.9.1" -description = "Coroutine-based network library" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "gevent-25.9.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:856b990be5590e44c3a3dc6c8d48a40eaccbb42e99d2b791d11d1e7711a4297e"}, - {file = "gevent-25.9.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fe1599d0b30e6093eb3213551751b24feeb43db79f07e89d98dd2f3330c9063e"}, - {file = "gevent-25.9.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:f0d8b64057b4bf1529b9ef9bd2259495747fba93d1f836c77bfeaacfec373fd0"}, - {file = "gevent-25.9.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b56cbc820e3136ba52cd690bdf77e47a4c239964d5f80dc657c1068e0fe9521c"}, - {file = "gevent-25.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c5fa9ce5122c085983e33e0dc058f81f5264cebe746de5c401654ab96dddfca8"}, - {file = "gevent-25.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:03c74fec58eda4b4edc043311fca8ba4f8744ad1632eb0a41d5ec25413581975"}, - {file = "gevent-25.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a8ae9f895e8651d10b0a8328a61c9c53da11ea51b666388aa99b0ce90f9fdc27"}, - {file = "gevent-25.9.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5aff9e8342dc954adb9c9c524db56c2f3557999463445ba3d9cbe3dada7b7"}, - {file = "gevent-25.9.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1cdf6db28f050ee103441caa8b0448ace545364f775059d5e2de089da975c457"}, - {file = "gevent-25.9.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:812debe235a8295be3b2a63b136c2474241fa5c58af55e6a0f8cfc29d4936235"}, - {file = "gevent-25.9.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b28b61ff9216a3d73fe8f35669eefcafa957f143ac534faf77e8a19eb9e6883a"}, - {file = "gevent-25.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5e4b6278b37373306fc6b1e5f0f1cf56339a1377f67c35972775143d8d7776ff"}, - {file = "gevent-25.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d99f0cb2ce43c2e8305bf75bee61a8bde06619d21b9d0316ea190fc7a0620a56"}, - {file = "gevent-25.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:72152517ecf548e2f838c61b4be76637d99279dbaa7e01b3924df040aa996586"}, - {file = "gevent-25.9.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:46b188248c84ffdec18a686fcac5dbb32365d76912e14fda350db5dc0bfd4f86"}, - {file = "gevent-25.9.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f2b54ea3ca6f0c763281cd3f96010ac7e98c2e267feb1221b5a26e2ca0b9a692"}, - {file = "gevent-25.9.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7a834804ac00ed8a92a69d3826342c677be651b1c3cd66cc35df8bc711057aa2"}, - {file = "gevent-25.9.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:323a27192ec4da6b22a9e51c3d9d896ff20bc53fdc9e45e56eaab76d1c39dd74"}, - {file = "gevent-25.9.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6ea78b39a2c51d47ff0f130f4c755a9a4bbb2dd9721149420ad4712743911a51"}, - {file = "gevent-25.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:dc45cd3e1cc07514a419960af932a62eb8515552ed004e56755e4bf20bad30c5"}, - {file = "gevent-25.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34e01e50c71eaf67e92c186ee0196a039d6e4f4b35670396baed4a2d8f1b347f"}, - {file = "gevent-25.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:4acd6bcd5feabf22c7c5174bd3b9535ee9f088d2bbce789f740ad8d6554b18f3"}, - {file = "gevent-25.9.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:4f84591d13845ee31c13f44bdf6bd6c3dbf385b5af98b2f25ec328213775f2ed"}, - {file = "gevent-25.9.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9cdbb24c276a2d0110ad5c978e49daf620b153719ac8a548ce1250a7eb1b9245"}, - {file = "gevent-25.9.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:88b6c07169468af631dcf0fdd3658f9246d6822cc51461d43f7c44f28b0abb82"}, - {file = "gevent-25.9.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b7bb0e29a7b3e6ca9bed2394aa820244069982c36dc30b70eb1004dd67851a48"}, - {file = "gevent-25.9.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2951bb070c0ee37b632ac9134e4fdaad70d2e660c931bb792983a0837fe5b7d7"}, - {file = "gevent-25.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4e17c2d57e9a42e25f2a73d297b22b60b2470a74be5a515b36c984e1a246d47"}, - {file = "gevent-25.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d94936f8f8b23d9de2251798fcb603b84f083fdf0d7f427183c1828fb64f117"}, - {file = "gevent-25.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:eb51c5f9537b07da673258b4832f6635014fee31690c3f0944d34741b69f92fa"}, - {file = "gevent-25.9.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:1a3fe4ea1c312dbf6b375b416925036fe79a40054e6bf6248ee46526ea628be1"}, - {file = "gevent-25.9.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0adb937f13e5fb90cca2edf66d8d7e99d62a299687400ce2edee3f3504009356"}, - {file = "gevent-25.9.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:427f869a2050a4202d93cf7fd6ab5cffb06d3e9113c10c967b6e2a0d45237cb8"}, - {file = "gevent-25.9.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c049880175e8c93124188f9d926af0a62826a3b81aa6d3074928345f8238279e"}, - {file = "gevent-25.9.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b5a67a0974ad9f24721034d1e008856111e0535f1541499f72a733a73d658d1c"}, - {file = "gevent-25.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1d0f5d8d73f97e24ea8d24d8be0f51e0cf7c54b8021c1fddb580bf239474690f"}, - {file = "gevent-25.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ddd3ff26e5c4240d3fbf5516c2d9d5f2a998ef87cfb73e1429cfaeaaec860fa6"}, - {file = "gevent-25.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:bb63c0d6cb9950cc94036a4995b9cc4667b8915366613449236970f4394f94d7"}, - {file = "gevent-25.9.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f18f80aef6b1f6907219affe15b36677904f7cfeed1f6a6bc198616e507ae2d7"}, - {file = "gevent-25.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b274a53e818124a281540ebb4e7a2c524778f745b7a99b01bdecf0ca3ac0ddb0"}, - {file = "gevent-25.9.1-cp39-cp39-win32.whl", hash = "sha256:c6c91f7e33c7f01237755884316110ee7ea076f5bdb9aa0982b6dc63243c0a38"}, - {file = "gevent-25.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:012a44b0121f3d7c800740ff80351c897e85e76a7e4764690f35c5ad9ec17de5"}, - {file = "gevent-25.9.1.tar.gz", hash = "sha256:adf9cd552de44a4e6754c51ff2e78d9193b7fa6eab123db9578a210e657235dd"}, -] - -[package.dependencies] -cffi = {version = ">=1.17.1", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} -greenlet = {version = ">=3.2.2", markers = "platform_python_implementation == \"CPython\""} -"zope.event" = "*" -"zope.interface" = "*" - -[package.extras] -dnspython = ["dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\""] -docs = ["furo", "repoze.sphinx.autointerface", "sphinx", "sphinxcontrib-programoutput", "zope.schema"] -monitor = ["psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\""] -recommended = ["cffi (>=1.17.1) ; platform_python_implementation == \"CPython\"", "dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\"", "psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\""] -test = ["cffi (>=1.17.1) ; platform_python_implementation == \"CPython\"", "coverage (>=5.0) ; sys_platform != \"win32\"", "dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\"", "objgraph", "psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\"", "requests"] - -[[package]] -name = "greenlet" -version = "3.2.4" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, - {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, - {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, - {file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, - {file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, - {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, - {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, - {file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, - {file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, - {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, - {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, - {file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, - {file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, - {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, - {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, - {file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, - {file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, - {file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, - {file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"}, - {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"}, - {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"}, - {file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"}, - {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, - {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil", "setuptools"] - -[[package]] -name = "gremlinpython" -version = "3.7.4" -description = "Gremlin-Python for Apache TinkerPop" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "gremlinpython-3.7.4-py3-none-any.whl", hash = "sha256:b6b336320d0110382b6a3832bc19b4e2bf72e4b3f38dab25fdbedfa1a3167987"}, - {file = "gremlinpython-3.7.4.tar.gz", hash = "sha256:d41579a8ef83c1dce9e51ccff2b5fb496170be0fdb0f491d4124c29e7df9b14d"}, -] - -[package.dependencies] -aenum = ">=1.4.5,<4.0.0" -aiohttp = ">=3.8.0,<4.0.0" -async-timeout = ">=4.0.3,<5.0.0" -isodate = ">=0.6.0,<1.0.0" -nest-asyncio = "*" - -[package.extras] -kerberos = ["kerberos (>=1.3.0,<2.0.0)"] -ujson = ["ujson (>=2.0.0)"] - -[[package]] -name = "h11" -version = "0.16.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, - {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, -] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main"] -files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] - -[[package]] -name = "isodate" -version = "0.7.2" -description = "An ISO 8601 date/time/duration parser and formatter" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15"}, - {file = "isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6"}, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, - {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "3.0.2" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, -] - -[[package]] -name = "mdit-py-plugins" -version = "0.5.0" -description = "Collection of plugins for markdown-it-py" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f"}, - {file = "mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6"}, -] - -[package.dependencies] -markdown-it-py = ">=2.0.0,<5.0.0" - -[package.extras] -code-style = ["pre-commit"] -rtd = ["myst-parser", "sphinx-book-theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "multidict" -version = "6.6.4" -description = "multidict implementation" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, - {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, - {file = "multidict-6.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0af5f9dee472371e36d6ae38bde009bd8ce65ac7335f55dcc240379d7bed1495"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d24f351e4d759f5054b641c81e8291e5d122af0fca5c72454ff77f7cbe492de8"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db6a3810eec08280a172a6cd541ff4a5f6a97b161d93ec94e6c4018917deb6b7"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a1b20a9d56b2d81e2ff52ecc0670d583eaabaa55f402e8d16dd062373dbbe796"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c9854df0eaa610a23494c32a6f44a3a550fb398b6b51a56e8c6b9b3689578db"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4bb7627fd7a968f41905a4d6343b0d63244a0623f006e9ed989fa2b78f4438a0"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caebafea30ed049c57c673d0b36238b1748683be2593965614d7b0e99125c877"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ad887a8250eb47d3ab083d2f98db7f48098d13d42eb7a3b67d8a5c795f224ace"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ed8358ae7d94ffb7c397cecb62cbac9578a83ecefc1eba27b9090ee910e2efb6"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecab51ad2462197a4c000b6d5701fc8585b80eecb90583635d7e327b7b6923eb"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c5c97aa666cf70e667dfa5af945424ba1329af5dd988a437efeb3a09430389fb"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9a950b7cf54099c1209f455ac5970b1ea81410f2af60ed9eb3c3f14f0bfcf987"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:163c7ea522ea9365a8a57832dea7618e6cbdc3cd75f8c627663587459a4e328f"}, - {file = "multidict-6.6.4-cp310-cp310-win32.whl", hash = "sha256:17d2cbbfa6ff20821396b25890f155f40c986f9cfbce5667759696d83504954f"}, - {file = "multidict-6.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce9a40fbe52e57e7edf20113a4eaddfacac0561a0879734e636aa6d4bb5e3fb0"}, - {file = "multidict-6.6.4-cp310-cp310-win_arm64.whl", hash = "sha256:01d0959807a451fe9fdd4da3e139cb5b77f7328baf2140feeaf233e1d777b729"}, - {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c"}, - {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb"}, - {file = "multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f"}, - {file = "multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2"}, - {file = "multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e"}, - {file = "multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf"}, - {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8"}, - {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3"}, - {file = "multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24"}, - {file = "multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793"}, - {file = "multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e"}, - {file = "multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364"}, - {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e"}, - {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657"}, - {file = "multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a"}, - {file = "multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69"}, - {file = "multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf"}, - {file = "multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605"}, - {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb"}, - {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e"}, - {file = "multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92"}, - {file = "multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e"}, - {file = "multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4"}, - {file = "multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad"}, - {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:af7618b591bae552b40dbb6f93f5518328a949dac626ee75927bba1ecdeea9f4"}, - {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b6819f83aef06f560cb15482d619d0e623ce9bf155115150a85ab11b8342a665"}, - {file = "multidict-6.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d09384e75788861e046330308e7af54dd306aaf20eb760eb1d0de26b2bea2cb"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a59c63061f1a07b861c004e53869eb1211ffd1a4acbca330e3322efa6dd02978"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350f6b0fe1ced61e778037fdc7613f4051c8baf64b1ee19371b42a3acdb016a0"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c5cbac6b55ad69cb6aa17ee9343dfbba903118fd530348c330211dc7aa756d1"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:630f70c32b8066ddfd920350bc236225814ad94dfa493fe1910ee17fe4365cbb"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8d4916a81697faec6cb724a273bd5457e4c6c43d82b29f9dc02c5542fd21fc9"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e42332cf8276bb7645d310cdecca93a16920256a5b01bebf747365f86a1675b"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f3be27440f7644ab9a13a6fc86f09cdd90b347c3c5e30c6d6d860de822d7cb53"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:21f216669109e02ef3e2415ede07f4f8987f00de8cdfa0cc0b3440d42534f9f0"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d9890d68c45d1aeac5178ded1d1cccf3bc8d7accf1f976f79bf63099fb16e4bd"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:edfdcae97cdc5d1a89477c436b61f472c4d40971774ac4729c613b4b133163cb"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0b2e886624be5773e69cf32bcb8534aecdeb38943520b240fed3d5596a430f2f"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:be5bf4b3224948032a845d12ab0f69f208293742df96dc14c4ff9b09e508fc17"}, - {file = "multidict-6.6.4-cp39-cp39-win32.whl", hash = "sha256:10a68a9191f284fe9d501fef4efe93226e74df92ce7a24e301371293bd4918ae"}, - {file = "multidict-6.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee25f82f53262f9ac93bd7e58e47ea1bdcc3393cef815847e397cba17e284210"}, - {file = "multidict-6.6.4-cp39-cp39-win_arm64.whl", hash = "sha256:f9867e55590e0855bcec60d4f9a092b69476db64573c9fe17e92b0c50614c16a"}, - {file = "multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c"}, - {file = "multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd"}, -] - -[[package]] -name = "myst-parser" -version = "4.0.1" -description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d"}, - {file = "myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4"}, -] - -[package.dependencies] -docutils = ">=0.19,<0.22" -jinja2 = "*" -markdown-it-py = ">=3.0,<4.0" -mdit-py-plugins = ">=0.4.1,<1.0" -pyyaml = "*" -sphinx = ">=7,<9" - -[package.extras] -code-style = ["pre-commit (>=4.0,<5.0)"] -linkify = ["linkify-it-py (>=2.0,<3.0)"] -rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] -testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pygments (<2.19)", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] -testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] - -[[package]] -name = "nest-asyncio" -version = "1.6.0" -description = "Patch asyncio to allow nested event loops" -optional = false -python-versions = ">=3.5" -groups = ["main"] -files = [ - {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, - {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, -] - -[[package]] -name = "packaging" -version = "25.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, -] - -[[package]] -name = "propcache" -version = "0.3.2" -description = "Accelerated property cache" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"}, - {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"}, - {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"}, - {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"}, - {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"}, - {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"}, - {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"}, - {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"}, - {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"}, - {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"}, - {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"}, - {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"}, - {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"}, - {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"}, - {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"}, - {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"}, - {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"}, - {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"}, - {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"}, - {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"}, - {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"}, - {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"}, - {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"}, - {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"}, - {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"}, - {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"}, - {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"}, - {file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"}, - {file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"}, - {file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"}, - {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"}, - {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"}, -] - -[[package]] -name = "pycparser" -version = "2.23" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and implementation_name != \"PyPy\"" -files = [ - {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, - {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, -] - -[[package]] -name = "pygments" -version = "2.19.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "recommonmark" -version = "0.7.1" -description = "A docutils-compatibility bridge to CommonMark, enabling you to write CommonMark inside of Docutils & Sphinx projects." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"}, - {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, -] - -[package.dependencies] -commonmark = ">=0.8.1" -docutils = ">=0.11" -sphinx = ">=1.3.1" - -[[package]] -name = "redirects-cli" -version = "0.1.3" -description = "Generates static redirections from a YAML file." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "redirects_cli-0.1.3-py3-none-any.whl", hash = "sha256:8a7a548d5f45b98db7d110fd8affbbb44b966cf250e35b5f4c9bd6541622272d"}, - {file = "redirects_cli-0.1.3.tar.gz", hash = "sha256:0cc6f35ae372d087d56bc03cfc639d6e2eac0771454c3c173ac6f3dc233969bc"}, -] - -[package.dependencies] -colorama = ">=0.4" -typer = ">=0.3" - -[package.extras] -test = ["pre-commit", "pytest"] - -[[package]] -name = "requests" -version = "2.32.5" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, - {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rich" -version = "14.1.0" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.8.0" -groups = ["main"] -files = [ - {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, - {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "roman-numerals-py" -version = "3.1.0" -description = "Manipulate well-formed Roman numerals" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c"}, - {file = "roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d"}, -] - -[package.extras] -lint = ["mypy (==1.15.0)", "pyright (==1.1.394)", "ruff (==0.9.7)"] -test = ["pytest (>=8)"] - -[[package]] -name = "scales" -version = "1.0.9" -description = "Stats for Python processes" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "scales-1.0.9.tar.gz", hash = "sha256:8b6930f7d4bf115192290b44c757af5e254e3fcfcb75ff9a51f5c96a404e2753"}, -] - -[package.dependencies] -six = "*" - -[[package]] -name = "scylla-driver" -version = "3.29.4" -description = "Scylla Driver for Apache Cassandra" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [] -develop = true - -[package.dependencies] -geomet = ">=1.1" -pyyaml = ">5.0" - -[package.extras] -cle = ["cryptography (>=35.0)"] -compress-lz4 = ["lz4"] -compress-snappy = ["python-snappy"] -graph = ["gremlinpython (==3.4.6)"] - -[package.source] -type = "directory" -url = ".." - -[[package]] -name = "setuptools" -version = "80.9.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, - {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] - -[[package]] -name = "shellingham" -version = "1.5.4" -description = "Tool to Detect Surrounding Shell" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, - {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, -] - -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "snowballstemmer" -version = "3.0.1" -description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" -groups = ["main"] -files = [ - {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, - {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, -] - -[[package]] -name = "soupsieve" -version = "2.8" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c"}, - {file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"}, -] - -[[package]] -name = "sphinx" -version = "8.2.3" -description = "Python documentation generator" -optional = false -python-versions = ">=3.11" -groups = ["main"] -files = [ - {file = "sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3"}, - {file = "sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348"}, -] - -[package.dependencies] -alabaster = ">=0.7.14" -babel = ">=2.13" -colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} -docutils = ">=0.20,<0.22" -imagesize = ">=1.3" -Jinja2 = ">=3.1" -packaging = ">=23.0" -Pygments = ">=2.17" -requests = ">=2.30.0" -roman-numerals-py = ">=1.0.0" -snowballstemmer = ">=2.2" -sphinxcontrib-applehelp = ">=1.0.7" -sphinxcontrib-devhelp = ">=1.0.6" -sphinxcontrib-htmlhelp = ">=2.0.6" -sphinxcontrib-jsmath = ">=1.0.1" -sphinxcontrib-qthelp = ">=1.0.6" -sphinxcontrib-serializinghtml = ">=1.1.9" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["betterproto (==2.0.0b6)", "mypy (==1.15.0)", "pypi-attestations (==0.0.21)", "pyright (==1.1.395)", "pytest (>=8.0)", "ruff (==0.9.9)", "sphinx-lint (>=0.9)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.19.0.20250219)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241128)", "types-requests (==2.32.0.20241016)", "types-urllib3 (==1.26.25.14)"] -test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "pytest-xdist[psutil] (>=3.4)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] - -[[package]] -name = "sphinx-autobuild" -version = "2025.8.25" -description = "Rebuild Sphinx documentation on changes, with hot reloading in the browser." -optional = false -python-versions = ">=3.11" -groups = ["main"] -files = [ - {file = "sphinx_autobuild-2025.8.25-py3-none-any.whl", hash = "sha256:b750ac7d5a18603e4665294323fd20f6dcc0a984117026d1986704fa68f0379a"}, - {file = "sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213"}, -] - -[package.dependencies] -colorama = ">=0.4.6" -Sphinx = "*" -starlette = ">=0.35" -uvicorn = ">=0.25" -watchfiles = ">=0.20" -websockets = ">=11" - -[package.extras] -test = ["httpx", "pytest (>=6)"] - -[[package]] -name = "sphinx-collapse" -version = "0.1.3" -description = "Collapse extension for Sphinx." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sphinx_collapse-0.1.3-py3-none-any.whl", hash = "sha256:85fadb2ec8769b93fd04276538668fa96239ef60c20c4a9eaa3e480387a6e65b"}, - {file = "sphinx_collapse-0.1.3.tar.gz", hash = "sha256:cae141e6f03ecd52ed246a305a69e1b0d5d05e6cdf3fe803d40d583ad6ad895a"}, -] - -[package.dependencies] -sphinx = ">=3" - -[package.extras] -doc = ["alabaster"] -test = ["pre-commit", "pytest"] - -[[package]] -name = "sphinx-copybutton" -version = "0.5.2" -description = "Add a copy button to each of your code cells." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, - {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, -] - -[package.dependencies] -sphinx = ">=1.8" - -[package.extras] -code-style = ["pre-commit (==2.12.1)"] -rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] - -[[package]] -name = "sphinx-last-updated-by-git" -version = "0.3.8" -description = "Get the \"last updated\" time for each Sphinx page from Git" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sphinx_last_updated_by_git-0.3.8-py3-none-any.whl", hash = "sha256:6382c8285ac1f222483a58569b78c0371af5e55f7fbf9c01e5e8a72d6fdfa499"}, - {file = "sphinx_last_updated_by_git-0.3.8.tar.gz", hash = "sha256:c145011f4609d841805b69a9300099fc02fed8f5bb9e5bcef77d97aea97b7761"}, -] - -[package.dependencies] -sphinx = ">=1.8" - -[[package]] -name = "sphinx-multiversion-scylla" -version = "0.3.2" -description = "Add support for multiple versions to sphinx" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "sphinx_multiversion_scylla-0.3.2.tar.gz", hash = "sha256:f415311273228f4f766c36256503da8e2ce01f9d13423f3fcee3160d6284852b"}, -] - -[package.dependencies] -sphinx = ">=2.1" - -[[package]] -name = "sphinx-notfound-page" -version = "1.1.0" -description = "Sphinx extension to build a 404 page with absolute URLs" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "sphinx_notfound_page-1.1.0-py3-none-any.whl", hash = "sha256:835dc76ff7914577a1f58d80a2c8418fb6138c0932c8da8adce4d9096fbcd389"}, - {file = "sphinx_notfound_page-1.1.0.tar.gz", hash = "sha256:913e1754370bb3db201d9300d458a8b8b5fb22e9246a816643a819a9ea2b8067"}, -] - -[package.dependencies] -sphinx = ">=5" - -[package.extras] -doc = ["sphinx-autoapi", "sphinx-rtd-theme", "sphinx-tabs", "sphinxemoji"] -test = ["tox"] - -[[package]] -name = "sphinx-scylladb-theme" -version = "1.8.8" -description = "A Sphinx Theme for ScyllaDB documentation projects" -optional = false -python-versions = "<4.0,>=3.10" -groups = ["main"] -files = [ - {file = "sphinx_scylladb_theme-1.8.8-py3-none-any.whl", hash = "sha256:9b37f58b745bfc818b6f0869cbcc414a3ad6658023b22a6abbec44da5c09cf80"}, - {file = "sphinx_scylladb_theme-1.8.8.tar.gz", hash = "sha256:15af599424f8b2ddbf14644b267c0bd31bc3fbbd64bca5b97d7b31bdb84d2c3d"}, -] - -[package.dependencies] -beautifulsoup4 = ">=4.12.3,<5.0.0" -pyyaml = ">=6.0.1,<7.0.0" -setuptools = ">=70.1.1,<81.0.0" -sphinx-collapse = ">=0.1.1,<0.2.0" -sphinx-copybutton = ">=0.5.2,<0.6.0" -sphinx-notfound-page = ">=1.0.4,<2.0.0" -Sphinx-Substitution-Extensions = ">=2022.2.16,<2026.0.0" -sphinx-tabs = ">=3.4.5,<4.0.0" -sphinxcontrib-mermaid = ">=1.0.0,<2.0.0" - -[[package]] -name = "sphinx-sitemap" -version = "2.9.0" -description = "Sitemap generator for Sphinx" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "sphinx_sitemap-2.9.0-py3-none-any.whl", hash = "sha256:f1f1d3a9ad012ba17a7ef0b560d303bff2d0db26647567d6e810bcc754466664"}, - {file = "sphinx_sitemap-2.9.0.tar.gz", hash = "sha256:70f97bcdf444e3d68e118355cf82a1f54c4d3c03d651cd17fe87398b26e25e21"}, -] - -[package.dependencies] -sphinx-last-updated-by-git = "*" - -[package.extras] -dev = ["build", "flake8", "pre-commit", "pytest", "sphinx", "sphinx-last-updated-by-git", "tox"] - -[[package]] -name = "sphinx-substitution-extensions" -version = "2025.2.19" -description = "Extensions for Sphinx which allow for substitutions." -optional = false -python-versions = ">=3.11" -groups = ["main"] -files = [ - {file = "sphinx_substitution_extensions-2025.2.19-py2.py3-none-any.whl", hash = "sha256:dfdaa3a925ff5ab450ff89ae08e9989f90f04add362375f5c8e27309573e5343"}, - {file = "sphinx_substitution_extensions-2025.2.19.tar.gz", hash = "sha256:ecbb35e7ae210aef4e213a389e5095df503dd1260374640c426d843ad64c8f86"}, -] - -[package.dependencies] -beartype = ">=0.18.5" -docutils = ">=0.19" -myst-parser = ">=4.0.0" -sphinx = ">=7.3.5" - -[package.extras] -dev = ["actionlint-py (==1.7.7.23)", "check-manifest (==0.50)", "deptry (==0.23.0)", "doc8 (==1.1.2)", "doccmd (==2025.2.19)", "docformatter (==1.7.5)", "interrogate (==1.7.0)", "mypy-strict-kwargs (==2024.12.25)", "mypy[faster-cache] (==1.15.0)", "pre-commit (==4.1.0)", "pyenchant (==3.3.0rc1)", "pylint (==3.3.4)", "pyproject-fmt (==2.5.1)", "pyright (==1.1.394)", "pyroma (==4.2)", "pytest (==8.3.4)", "pytest-cov (==6.0.0)", "ruff (==0.9.6)", "shellcheck-py (==0.10.0.1)", "shfmt-py (==3.7.0.1)", "sphinx-lint (==1.0.0)", "sphinx-toolbox (==3.8.2)", "sphinx[test] (==8.1.3)", "types-docutils (==0.21.0.20241128)", "vulture (==2.14)", "yamlfix (==1.17.0)"] -release = ["check-wheel-contents (==0.6.1)"] - -[[package]] -name = "sphinx-tabs" -version = "3.4.7" -description = "Tabbed views for Sphinx" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sphinx-tabs-3.4.7.tar.gz", hash = "sha256:991ad4a424ff54119799ba1491701aa8130dd43509474aef45a81c42d889784d"}, - {file = "sphinx_tabs-3.4.7-py3-none-any.whl", hash = "sha256:c12d7a36fd413b369e9e9967a0a4015781b71a9c393575419834f19204bd1915"}, -] - -[package.dependencies] -docutils = "*" -pygments = "*" -sphinx = ">=1.8" - -[package.extras] -code-style = ["pre-commit (==2.13.0)"] -testing = ["bs4", "coverage", "pygments", "pytest (>=7.1,<8)", "pytest-cov", "pytest-regressions", "rinohtype"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "2.0.0" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, - {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "2.0.0" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, - {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.1.0" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, - {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["html5lib", "pytest"] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -optional = false -python-versions = ">=3.5" -groups = ["main"] -files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] - -[package.extras] -test = ["flake8", "mypy", "pytest"] - -[[package]] -name = "sphinxcontrib-mermaid" -version = "1.0.0" -description = "Mermaid diagrams in yours Sphinx powered docs" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "sphinxcontrib_mermaid-1.0.0-py3-none-any.whl", hash = "sha256:60b72710ea02087f212028feb09711225fbc2e343a10d34822fe787510e1caa3"}, - {file = "sphinxcontrib_mermaid-1.0.0.tar.gz", hash = "sha256:2e8ab67d3e1e2816663f9347d026a8dee4a858acdd4ad32dd1c808893db88146"}, -] - -[package.dependencies] -pyyaml = "*" -sphinx = "*" - -[package.extras] -test = ["defusedxml", "myst-parser", "pytest", "ruff", "sphinx"] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "2.0.0" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, - {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["defusedxml (>=0.7.1)", "pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "2.0.0" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, - {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "starlette" -version = "0.48.0" -description = "The little ASGI library that shines." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659"}, - {file = "starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46"}, -] - -[package.dependencies] -anyio = ">=3.6.2,<5" - -[package.extras] -full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] - -[[package]] -name = "tornado" -version = "6.5.2" -description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6"}, - {file = "tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef"}, - {file = "tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e"}, - {file = "tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882"}, - {file = "tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108"}, - {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c"}, - {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4"}, - {file = "tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04"}, - {file = "tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0"}, - {file = "tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f"}, - {file = "tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af"}, - {file = "tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0"}, -] - -[[package]] -name = "typer" -version = "0.17.4" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "typer-0.17.4-py3-none-any.whl", hash = "sha256:015534a6edaa450e7007eba705d5c18c3349dcea50a6ad79a5ed530967575824"}, - {file = "typer-0.17.4.tar.gz", hash = "sha256:b77dc07d849312fd2bb5e7f20a7af8985c7ec360c45b051ed5412f64d8dc1580"}, -] - -[package.dependencies] -click = ">=8.0.0" -rich = ">=10.11.0" -shellingham = ">=1.3.0" -typing-extensions = ">=3.7.4.3" - -[[package]] -name = "typing-extensions" -version = "4.15.0" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, -] - -[[package]] -name = "urllib3" -version = "2.5.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "uvicorn" -version = "0.35.0" -description = "The lightning-fast ASGI server." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a"}, - {file = "uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01"}, -] - -[package.dependencies] -click = ">=7.0" -h11 = ">=0.8" - -[package.extras] -standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] - -[[package]] -name = "watchfiles" -version = "1.1.0" -description = "Simple, modern and high performance file watching and code reload in python." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc"}, - {file = "watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df"}, - {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68"}, - {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc"}, - {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97"}, - {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c"}, - {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5"}, - {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9"}, - {file = "watchfiles-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72"}, - {file = "watchfiles-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc"}, - {file = "watchfiles-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587"}, - {file = "watchfiles-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82"}, - {file = "watchfiles-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2"}, - {file = "watchfiles-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c"}, - {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d"}, - {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7"}, - {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c"}, - {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575"}, - {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8"}, - {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f"}, - {file = "watchfiles-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4"}, - {file = "watchfiles-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d"}, - {file = "watchfiles-1.1.0-cp311-cp311-win32.whl", hash = "sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2"}, - {file = "watchfiles-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12"}, - {file = "watchfiles-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a"}, - {file = "watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179"}, - {file = "watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5"}, - {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297"}, - {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0"}, - {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e"}, - {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee"}, - {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd"}, - {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f"}, - {file = "watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4"}, - {file = "watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f"}, - {file = "watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd"}, - {file = "watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47"}, - {file = "watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6"}, - {file = "watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30"}, - {file = "watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a"}, - {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc"}, - {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b"}, - {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895"}, - {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a"}, - {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b"}, - {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c"}, - {file = "watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b"}, - {file = "watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb"}, - {file = "watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9"}, - {file = "watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7"}, - {file = "watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5"}, - {file = "watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1"}, - {file = "watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339"}, - {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633"}, - {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011"}, - {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670"}, - {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf"}, - {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4"}, - {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20"}, - {file = "watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef"}, - {file = "watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb"}, - {file = "watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297"}, - {file = "watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018"}, - {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0"}, - {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12"}, - {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb"}, - {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77"}, - {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92"}, - {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e"}, - {file = "watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b"}, - {file = "watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259"}, - {file = "watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f"}, - {file = "watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e"}, - {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa"}, - {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8"}, - {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f"}, - {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e"}, - {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb"}, - {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147"}, - {file = "watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8"}, - {file = "watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db"}, - {file = "watchfiles-1.1.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:865c8e95713744cf5ae261f3067861e9da5f1370ba91fc536431e29b418676fa"}, - {file = "watchfiles-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42f92befc848bb7a19658f21f3e7bae80d7d005d13891c62c2cd4d4d0abb3433"}, - {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa0cc8365ab29487eb4f9979fd41b22549853389e22d5de3f134a6796e1b05a4"}, - {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90ebb429e933645f3da534c89b29b665e285048973b4d2b6946526888c3eb2c7"}, - {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c588c45da9b08ab3da81d08d7987dae6d2a3badd63acdb3e206a42dbfa7cb76f"}, - {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c55b0f9f68590115c25272b06e63f0824f03d4fc7d6deed43d8ad5660cabdbf"}, - {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd17a1e489f02ce9117b0de3c0b1fab1c3e2eedc82311b299ee6b6faf6c23a29"}, - {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da71945c9ace018d8634822f16cbc2a78323ef6c876b1d34bbf5d5222fd6a72e"}, - {file = "watchfiles-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:51556d5004887045dba3acdd1fdf61dddea2be0a7e18048b5e853dcd37149b86"}, - {file = "watchfiles-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04e4ed5d1cd3eae68c89bcc1a485a109f39f2fd8de05f705e98af6b5f1861f1f"}, - {file = "watchfiles-1.1.0-cp39-cp39-win32.whl", hash = "sha256:c600e85f2ffd9f1035222b1a312aff85fd11ea39baff1d705b9b047aad2ce267"}, - {file = "watchfiles-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3aba215958d88182e8d2acba0fdaf687745180974946609119953c0e112397dc"}, - {file = "watchfiles-1.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5"}, - {file = "watchfiles-1.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d"}, - {file = "watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea"}, - {file = "watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6"}, - {file = "watchfiles-1.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3"}, - {file = "watchfiles-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c"}, - {file = "watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432"}, - {file = "watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792"}, - {file = "watchfiles-1.1.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7b3443f4ec3ba5aa00b0e9fa90cf31d98321cbff8b925a7c7b84161619870bc9"}, - {file = "watchfiles-1.1.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7049e52167fc75fc3cc418fc13d39a8e520cbb60ca08b47f6cedb85e181d2f2a"}, - {file = "watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54062ef956807ba806559b3c3d52105ae1827a0d4ab47b621b31132b6b7e2866"}, - {file = "watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a7bd57a1bb02f9d5c398c0c1675384e7ab1dd39da0ca50b7f09af45fa435277"}, - {file = "watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575"}, -] - -[package.dependencies] -anyio = ">=3.0.0" - -[[package]] -name = "websockets" -version = "15.0.1" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}, - {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}, - {file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"}, - {file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"}, - {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"}, - {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"}, - {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"}, - {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"}, - {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"}, - {file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"}, - {file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"}, - {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"}, - {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"}, - {file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"}, - {file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"}, - {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"}, - {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"}, - {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"}, - {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"}, - {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"}, - {file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"}, - {file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"}, - {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"}, - {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"}, - {file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"}, - {file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"}, - {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"}, - {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"}, - {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"}, - {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"}, - {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"}, - {file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"}, - {file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"}, - {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"}, - {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"}, - {file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"}, - {file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"}, - {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"}, - {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"}, - {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"}, - {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"}, - {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"}, - {file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"}, - {file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"}, - {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5"}, - {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a"}, - {file = "websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b"}, - {file = "websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770"}, - {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb"}, - {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054"}, - {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee"}, - {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed"}, - {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880"}, - {file = "websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411"}, - {file = "websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4"}, - {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"}, - {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"}, - {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"}, - {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"}, - {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"}, - {file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"}, - {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940"}, - {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e"}, - {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9"}, - {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b"}, - {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f"}, - {file = "websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123"}, - {file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"}, - {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}, -] - -[[package]] -name = "yarl" -version = "1.20.1" -description = "Yet another URL library" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"}, - {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"}, - {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"}, - {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"}, - {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"}, - {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"}, - {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"}, - {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"}, - {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"}, - {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"}, - {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"}, - {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"}, - {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"}, - {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"}, - {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"}, - {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"}, - {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"}, - {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"}, - {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"}, - {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"}, - {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"}, - {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"}, - {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"}, - {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"}, - {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"}, - {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"}, - {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"}, - {file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"}, - {file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"}, - {file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"}, - {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"}, - {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -propcache = ">=0.2.1" - -[[package]] -name = "zope-event" -version = "6.0" -description = "Very basic event publishing system" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "zope_event-6.0-py3-none-any.whl", hash = "sha256:6f0922593407cc673e7d8766b492c519f91bdc99f3080fe43dcec0a800d682a3"}, - {file = "zope_event-6.0.tar.gz", hash = "sha256:0ebac894fa7c5f8b7a89141c272133d8c1de6ddc75ea4b1f327f00d1f890df92"}, -] - -[package.dependencies] -setuptools = ">=75.8.2" - -[package.extras] -docs = ["Sphinx"] -test = ["zope.testrunner (>=6.4)"] - -[[package]] -name = "zope-interface" -version = "8.0" -description = "Interfaces for Python" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "zope_interface-8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:daf4d6ba488a0fb560980b575244aa962a75e77b7c86984138b8d52bd4b5465f"}, - {file = "zope_interface-8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0caca2915522451e92c96c2aec404d2687e9c5cb856766940319b3973f62abb8"}, - {file = "zope_interface-8.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a26ae2fe77c58b4df8c39c2b7c3aadedfd44225a1b54a1d74837cd27057b2fc8"}, - {file = "zope_interface-8.0-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:453d2c6668778b8d2215430ed61e04417386e51afb23637ef2e14972b047b700"}, - {file = "zope_interface-8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a2c107cc6dff954be25399cd81ddc390667f79af306802fc0c1de98614348b70"}, - {file = "zope_interface-8.0-cp310-cp310-win_amd64.whl", hash = "sha256:c23af5b4c4e332253d721ec1222c809ad27ceae382ad5b8ff22c4c4fb6eb8ed5"}, - {file = "zope_interface-8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec1da7b9156ae000cea2d19bad83ddb5c50252f9d7b186da276d17768c67a3cb"}, - {file = "zope_interface-8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:160ba50022b342451baf516de3e3a2cd2d8c8dbac216803889a5eefa67083688"}, - {file = "zope_interface-8.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:879bb5bf937cde4acd738264e87f03c7bf7d45478f7c8b9dc417182b13d81f6c"}, - {file = "zope_interface-8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7fb931bf55c66a092c5fbfb82a0ff3cc3221149b185bde36f0afc48acb8dcd92"}, - {file = "zope_interface-8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1858d1e5bb2c5ae766890708184a603eb484bb7454e306e967932a9f3c558b07"}, - {file = "zope_interface-8.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e88c66ebedd1e839082f308b8372a50ef19423e01ee2e09600b80e765a10234"}, - {file = "zope_interface-8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b80447a3a5c7347f4ebf3e50de319c8d2a5dabd7de32f20899ac50fc275b145d"}, - {file = "zope_interface-8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:67047a4470cb2fddb5ba5105b0160a1d1c30ce4b300cf264d0563136adac4eac"}, - {file = "zope_interface-8.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:1bee9c1b42513148f98d3918affd829804a5c992c000c290dc805f25a75a6a3f"}, - {file = "zope_interface-8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:804ebacb2776eb89a57d9b5e9abec86930e0ee784a0005030801ae2f6c04d5d8"}, - {file = "zope_interface-8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c4d9d3982aaa88b177812cd911ceaf5ffee4829e86ab3273c89428f2c0c32cc4"}, - {file = "zope_interface-8.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea1f2e47bc0124a03ee1e5fb31aee5dfde876244bcc552b9e3eb20b041b350d7"}, - {file = "zope_interface-8.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:ee9ecad04269c2da4b1be403a47993981531ffd557064b870eab4094730e5062"}, - {file = "zope_interface-8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a9a8a71c38628af82a9ea1f7be58e5d19360a38067080c8896f6cbabe167e4f8"}, - {file = "zope_interface-8.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c0cc51ebd984945362fd3abdc1e140dbd837c3e3b680942b3fa24fe3aac26ef8"}, - {file = "zope_interface-8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:07405019f635a93b318807cb2ec7b05a5ef30f67cf913d11eb2f156ddbcead0d"}, - {file = "zope_interface-8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:450ab3357799eed6093f3a9f1fa22761b3a9de9ebaf57f416da2c9fb7122cdcb"}, - {file = "zope_interface-8.0-cp313-cp313-win_amd64.whl", hash = "sha256:e38bb30a58887d63b80b01115ab5e8be6158b44d00b67197186385ec7efe44c7"}, - {file = "zope_interface-8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:778458ea69413cf8131a3fcc6f0ea2792d07df605422fb03ad87daca3f8f78ce"}, - {file = "zope_interface-8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b207966f39c2e6fcfe9b68333acb7b19afd3fdda29eccc4643f8d52c180a3185"}, - {file = "zope_interface-8.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:e3cf57f90a760c56c55668f650ba20c3444cde8332820db621c9a1aafc217471"}, - {file = "zope_interface-8.0-cp39-cp39-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5cffe23eb610e32a83283dde5413ab7a17938fa3fbd023ca3e529d724219deb0"}, - {file = "zope_interface-8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4d639d5015c1753031e180b8ef81e72bb7d47b0aca0218694ad1f19b0a6c6b63"}, - {file = "zope_interface-8.0-cp39-cp39-win_amd64.whl", hash = "sha256:dee2d1db1067e8a4b682dde7eb4bff21775412358e142f4f98c9066173f9dacd"}, - {file = "zope_interface-8.0.tar.gz", hash = "sha256:b14d5aac547e635af749ce20bf49a3f5f93b8a854d2a6b1e95d4d5e5dc618f7d"}, -] - -[package.dependencies] -setuptools = "*" - -[package.extras] -docs = ["Sphinx", "furo", "repoze.sphinx.autointerface"] -test = ["coverage[toml]", "zope.event", "zope.testing"] -testing = ["coverage[toml]", "zope.event", "zope.testing"] - -[metadata] -lock-version = "2.1" -python-versions = "^3.13" -content-hash = "d9c72e9c0e672fa64a6680e9db9b861017addd31468fbdb6fb5fa8354a8f7150" diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 1c6a4d138f..49a4d000ae 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -1,29 +1,61 @@ -[tool.poetry] +[project] name = "python-driver-docs" version = "0.1.0" description = "ScyllaDB Python Driver Docs" -authors = ["Python Driver Contributors"] +authors = [{ name = "ScyllaDB" }] package-mode = false +requires-python = ">=3.13,<3.14" -[tool.poetry.dependencies] -eventlet = ">=0.40.3,<1.0.0" -gevent = ">=25.9.1,<26.0.0" -gremlinpython = "3.7.4" -python = "^3.13" -pygments = ">=2.19.2,<3.0.0" -recommonmark = "0.7.1" -redirects_cli = "~0.1.3" -sphinx-autobuild = ">=2025.0.0,<2026.0.0" -sphinx-sitemap = ">=2.8.0,<3.0.0" -sphinx-scylladb-theme = ">=1.8.2,<2.0.0" -sphinx-multiversion-scylla = ">=0.3.2,<1.0.0" -Sphinx = ">=8.2.3,<9.0.0" -scales = ">=1.0.9,<2.0.0" -six = ">=1.9" -tornado = ">=6.5,<7.0" -scylla-driver = { path = "../", develop = true } +dependencies = [ + "eventlet>=0.40.3,<1.0.0", + "gevent>=25.9.1,<26.0.0", + "gremlinpython==3.7.4", + "pygments>=2.19.2,<3.0.0", + "recommonmark==0.7.1", + "redirects_cli~=0.1.3", + "sphinx-autobuild>=2025.0.0,<2026.0.0", + "sphinx-sitemap>=2.8.0,<3.0.0", + "sphinx-scylladb-theme>=1.8.2,<2.0.0", + "sphinx-multiversion-scylla>=0.3.2,<1.0.0", + "sphinx>=8.2.3,<9.0.0", + "scales>=1.0.9,<2.0.0", + "six>=1.9", + "tornado>=6.5,<7.0", +] +[dependency-groups] +# Add any dev-only tools here; example shown +dev = ["hatchling==1.27.0"] + +[tool.uv.sources] +# Keep the driver editable from the parent directory +scylla-driver = { path = "../", editable = true } [build-system] -requires = ["poetry>=1.8.0"] -build-backend = "poetry.masonry.api" +requires = ["hatchling==1.27.0"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] + +# We don't ship a Python package/module; just include the docs tree as data. +# Using 'include' avoids the "Unable to determine which files to ship" error. +include = [ + "**/*.rst", + "**/*.md", + "**/*.txt", + "**/*.py", # e.g., conf.py and any Sphinx helpers + "**/*.yml", + "**/*.yaml", + "**/*.json", + "**/*.css", + "**/*.js", + "**/*.html", + "_static/**", + "_templates/**", +] + +exclude = [ + "**/__pycache__/**", + "**/*.pyc", + ".venv/**", +] \ No newline at end of file diff --git a/docs/uv.lock b/docs/uv.lock new file mode 100644 index 0000000000..9c3371f46a --- /dev/null +++ b/docs/uv.lock @@ -0,0 +1,1326 @@ +version = 1 +revision = 2 +requires-python = ">=3.13, <4" + +[[package]] +name = "aenum" +version = "3.1.16" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/52/6ad8f63ec8da1bf40f96996d25d5b650fdd38f5975f8c813732c47388f18/aenum-3.1.16-py3-none-any.whl", hash = "sha256:9035092855a98e41b66e3d0998bd7b96280e85ceb3a04cc035636138a1943eaf", size = 165627, upload-time = "2025-04-25T03:17:58.89Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.12.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, + { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, + { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, + { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, + { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, + { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, + { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, + { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "anyio" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, +] + +[[package]] +name = "async-timeout" +version = "4.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/d6/21b30a550dafea84b1b8eee21b5e23fa16d010ae006011221f33dcd8d7f8/async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f", size = 8345, upload-time = "2023-08-10T16:35:56.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/fa/e01228c2938de91d47b307831c62ab9e4001e747789d0b05baf779a6488c/async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028", size = 5721, upload-time = "2023-08-10T16:35:55.203Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "beartype" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/f9/21e5a9c731e14f08addd53c71fea2e70794e009de5b98e6a2c3d2f3015d6/beartype-0.21.0.tar.gz", hash = "sha256:f9a5078f5ce87261c2d22851d19b050b64f6a805439e8793aecf01ce660d3244", size = 1437066, upload-time = "2025-05-22T05:09:27.116Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/31/87045d1c66ee10a52486c9d2047bc69f00f2689f69401bb1e998afb4b205/beartype-0.21.0-py3-none-any.whl", hash = "sha256:b6a1bd56c72f31b0a496a36cc55df6e2f475db166ad07fa4acc7e74f4c7f34c0", size = 1191340, upload-time = "2025-05-22T05:09:24.606Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/2e/3e5079847e653b1f6dc647aa24549d68c6addb4c595cc0d902d1b19308ad/beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695", size = 622954, upload-time = "2025-08-24T14:06:13.168Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/eb/f4151e0c7377a6e08a38108609ba5cede57986802757848688aeedd1b9e8/beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a", size = 105113, upload-time = "2025-08-24T14:06:14.884Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "click" +version = "8.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "commonmark" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/48/a60f593447e8f0894ebb7f6e6c1f25dafc5e89c5879fdc9360ae93ff83f0/commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", size = 95764, upload-time = "2019-10-04T15:37:39.817Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/92/dfd892312d822f36c55366118b95d914e5f16de11044a27cf10a7d71bbbf/commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9", size = 51068, upload-time = "2019-10-04T15:37:37.674Z" }, +] + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "eventlet" +version = "0.40.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "greenlet" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/4f/1e5227b23aa77d9ea05056b98cf0bf187cca994991060245002b640f9830/eventlet-0.40.3.tar.gz", hash = "sha256:290852db0065d78cec17a821b78c8a51cafb820a792796a354592ae4d5fceeb0", size = 565741, upload-time = "2025-08-27T09:56:16.085Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/b4/981362608131dc4ee8de9fdca6a38ef19e3da66ab6a13937bd158882db91/eventlet-0.40.3-py3-none-any.whl", hash = "sha256:e681cae6ee956cfb066a966b5c0541e734cc14879bda6058024104790595ac9d", size = 364333, upload-time = "2025-08-27T09:56:10.774Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, + { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, + { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" }, + { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" }, + { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" }, + { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" }, + { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" }, + { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" }, + { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" }, + { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" }, + { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, + { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" }, + { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" }, + { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" }, + { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" }, + { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" }, + { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" }, + { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" }, + { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" }, + { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, +] + +[[package]] +name = "gevent" +version = "25.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'CPython' and sys_platform == 'win32'" }, + { name = "greenlet", marker = "platform_python_implementation == 'CPython'" }, + { name = "zope-event" }, + { name = "zope-interface" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/48/b3ef2673ffb940f980966694e40d6d32560f3ffa284ecaeb5ea3a90a6d3f/gevent-25.9.1.tar.gz", hash = "sha256:adf9cd552de44a4e6754c51ff2e78d9193b7fa6eab123db9578a210e657235dd", size = 5059025, upload-time = "2025-09-17T16:15:34.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/77/b97f086388f87f8ad3e01364f845004aef0123d4430241c7c9b1f9bde742/gevent-25.9.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:4f84591d13845ee31c13f44bdf6bd6c3dbf385b5af98b2f25ec328213775f2ed", size = 2973739, upload-time = "2025-09-17T14:53:30.279Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/9d5f204ead343e5b27bbb2fedaec7cd0009d50696b2266f590ae845d0331/gevent-25.9.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9cdbb24c276a2d0110ad5c978e49daf620b153719ac8a548ce1250a7eb1b9245", size = 1809165, upload-time = "2025-09-17T15:41:27.193Z" }, + { url = "https://files.pythonhosted.org/packages/10/3e/791d1bf1eb47748606d5f2c2aa66571f474d63e0176228b1f1fd7b77ab37/gevent-25.9.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:88b6c07169468af631dcf0fdd3658f9246d6822cc51461d43f7c44f28b0abb82", size = 1890638, upload-time = "2025-09-17T15:49:02.45Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5c/9ad0229b2b4d81249ca41e4f91dd8057deaa0da6d4fbe40bf13cdc5f7a47/gevent-25.9.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b7bb0e29a7b3e6ca9bed2394aa820244069982c36dc30b70eb1004dd67851a48", size = 1857118, upload-time = "2025-09-17T15:49:22.125Z" }, + { url = "https://files.pythonhosted.org/packages/49/2a/3010ed6c44179a3a5c5c152e6de43a30ff8bc2c8de3115ad8733533a018f/gevent-25.9.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2951bb070c0ee37b632ac9134e4fdaad70d2e660c931bb792983a0837fe5b7d7", size = 2111598, upload-time = "2025-09-17T15:15:15.226Z" }, + { url = "https://files.pythonhosted.org/packages/08/75/6bbe57c19a7aa4527cc0f9afcdf5a5f2aed2603b08aadbccb5bf7f607ff4/gevent-25.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4e17c2d57e9a42e25f2a73d297b22b60b2470a74be5a515b36c984e1a246d47", size = 1829059, upload-time = "2025-09-17T15:52:42.596Z" }, + { url = "https://files.pythonhosted.org/packages/06/6e/19a9bee9092be45679cb69e4dd2e0bf5f897b7140b4b39c57cc123d24829/gevent-25.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d94936f8f8b23d9de2251798fcb603b84f083fdf0d7f427183c1828fb64f117", size = 2173529, upload-time = "2025-09-17T15:24:13.897Z" }, + { url = "https://files.pythonhosted.org/packages/ca/4f/50de9afd879440e25737e63f5ba6ee764b75a3abe17376496ab57f432546/gevent-25.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:eb51c5f9537b07da673258b4832f6635014fee31690c3f0944d34741b69f92fa", size = 1681518, upload-time = "2025-09-17T19:39:47.488Z" }, + { url = "https://files.pythonhosted.org/packages/15/1a/948f8167b2cdce573cf01cec07afc64d0456dc134b07900b26ac7018b37e/gevent-25.9.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:1a3fe4ea1c312dbf6b375b416925036fe79a40054e6bf6248ee46526ea628be1", size = 2982934, upload-time = "2025-09-17T14:54:11.302Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ec/726b146d1d3aad82e03d2e1e1507048ab6072f906e83f97f40667866e582/gevent-25.9.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0adb937f13e5fb90cca2edf66d8d7e99d62a299687400ce2edee3f3504009356", size = 1813982, upload-time = "2025-09-17T15:41:28.506Z" }, + { url = "https://files.pythonhosted.org/packages/35/5d/5f83f17162301662bd1ce702f8a736a8a8cac7b7a35e1d8b9866938d1f9d/gevent-25.9.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:427f869a2050a4202d93cf7fd6ab5cffb06d3e9113c10c967b6e2a0d45237cb8", size = 1894902, upload-time = "2025-09-17T15:49:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/83/cd/cf5e74e353f60dab357829069ffc300a7bb414c761f52cf8c0c6e9728b8d/gevent-25.9.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c049880175e8c93124188f9d926af0a62826a3b81aa6d3074928345f8238279e", size = 1861792, upload-time = "2025-09-17T15:49:23.279Z" }, + { url = "https://files.pythonhosted.org/packages/dd/65/b9a4526d4a4edce26fe4b3b993914ec9dc64baabad625a3101e51adb17f3/gevent-25.9.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b5a67a0974ad9f24721034d1e008856111e0535f1541499f72a733a73d658d1c", size = 2113215, upload-time = "2025-09-17T15:15:16.34Z" }, + { url = "https://files.pythonhosted.org/packages/e5/be/7d35731dfaf8370795b606e515d964a0967e129db76ea7873f552045dd39/gevent-25.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1d0f5d8d73f97e24ea8d24d8be0f51e0cf7c54b8021c1fddb580bf239474690f", size = 1833449, upload-time = "2025-09-17T15:52:43.75Z" }, + { url = "https://files.pythonhosted.org/packages/65/58/7bc52544ea5e63af88c4a26c90776feb42551b7555a1c89c20069c168a3f/gevent-25.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ddd3ff26e5c4240d3fbf5516c2d9d5f2a998ef87cfb73e1429cfaeaaec860fa6", size = 2176034, upload-time = "2025-09-17T15:24:15.676Z" }, + { url = "https://files.pythonhosted.org/packages/c2/69/a7c4ba2ffbc7c7dbf6d8b4f5d0f0a421f7815d229f4909854266c445a3d4/gevent-25.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:bb63c0d6cb9950cc94036a4995b9cc4667b8915366613449236970f4394f94d7", size = 1703019, upload-time = "2025-09-17T19:30:55.272Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, +] + +[[package]] +name = "gremlinpython" +version = "3.7.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aenum" }, + { name = "aiohttp" }, + { name = "async-timeout" }, + { name = "isodate" }, + { name = "nest-asyncio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/86/a0b20fed6bb699054f19e693b40570c27d06432469d50be677a156b65fcb/gremlinpython-3.7.4.tar.gz", hash = "sha256:d41579a8ef83c1dce9e51ccff2b5fb496170be0fdb0f491d4124c29e7df9b14d", size = 52639, upload-time = "2025-08-08T16:55:23.376Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/84/7a268ae9d5ae4a64a701fa099497b7531820d42f6c19dfec39dcdb238bf7/gremlinpython-3.7.4-py3-none-any.whl", hash = "sha256:b6b336320d0110382b6a3832bc19b4e2bf72e4b3f38dab25fdbedfa1a3167987", size = 78522, upload-time = "2025-08-08T16:55:22.246Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hatchling" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pathspec" }, + { name = "pluggy" }, + { name = "trove-classifiers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/8a/cc1debe3514da292094f1c3a700e4ca25442489731ef7c0814358816bb03/hatchling-1.27.0.tar.gz", hash = "sha256:971c296d9819abb3811112fc52c7a9751c8d381898f36533bb16f9791e941fd6", size = 54983, upload-time = "2024-12-15T17:08:11.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/e7/ae38d7a6dfba0533684e0b2136817d667588ae3ec984c1a4e5df5eb88482/hatchling-1.27.0-py3-none-any.whl", hash = "sha256:d3a2f3567c4f926ea39849cdf924c7e99e6686c9c8e288ae1037c8fa2a5d937b", size = 75794, upload-time = "2024-12-15T17:08:10.364Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + +[[package]] +name = "isodate" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "multidict" +version = "6.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/5d/e1db626f64f60008320aab00fbe4f23fc3300d75892a3381275b3d284580/multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e", size = 75848, upload-time = "2025-08-11T12:07:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657", size = 45060, upload-time = "2025-08-11T12:07:21.163Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da", size = 43269, upload-time = "2025-08-11T12:07:22.392Z" }, + { url = "https://files.pythonhosted.org/packages/dc/31/d54eb0c62516776f36fe67f84a732f97e0b0e12f98d5685bebcc6d396910/multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa", size = 237158, upload-time = "2025-08-11T12:07:23.636Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1c/8a10c1c25b23156e63b12165a929d8eb49a6ed769fdbefb06e6f07c1e50d/multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f", size = 257076, upload-time = "2025-08-11T12:07:25.049Z" }, + { url = "https://files.pythonhosted.org/packages/ad/86/90e20b5771d6805a119e483fd3d1e8393e745a11511aebca41f0da38c3e2/multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0", size = 240694, upload-time = "2025-08-11T12:07:26.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/49/484d3e6b535bc0555b52a0a26ba86e4d8d03fd5587d4936dc59ba7583221/multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879", size = 266350, upload-time = "2025-08-11T12:07:27.94Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b4/aa4c5c379b11895083d50021e229e90c408d7d875471cb3abf721e4670d6/multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a", size = 267250, upload-time = "2025-08-11T12:07:29.303Z" }, + { url = "https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f", size = 254900, upload-time = "2025-08-11T12:07:30.764Z" }, + { url = "https://files.pythonhosted.org/packages/17/38/58b27fed927c07035abc02befacab42491e7388ca105e087e6e0215ead64/multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5", size = 252355, upload-time = "2025-08-11T12:07:32.205Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a1/dad75d23a90c29c02b5d6f3d7c10ab36c3197613be5d07ec49c7791e186c/multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438", size = 250061, upload-time = "2025-08-11T12:07:33.623Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1a/ac2216b61c7f116edab6dc3378cca6c70dc019c9a457ff0d754067c58b20/multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e", size = 249675, upload-time = "2025-08-11T12:07:34.958Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/1916af833b800d13883e452e8e0977c065c4ee3ab7a26941fbfdebc11895/multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7", size = 261247, upload-time = "2025-08-11T12:07:36.588Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/d1f84fe08ac44a5fc7391cbc20a7cedc433ea616b266284413fd86062f8c/multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812", size = 257960, upload-time = "2025-08-11T12:07:39.735Z" }, + { url = "https://files.pythonhosted.org/packages/13/b5/29ec78057d377b195ac2c5248c773703a6b602e132a763e20ec0457e7440/multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a", size = 250078, upload-time = "2025-08-11T12:07:41.525Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0e/7e79d38f70a872cae32e29b0d77024bef7834b0afb406ddae6558d9e2414/multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69", size = 41708, upload-time = "2025-08-11T12:07:43.405Z" }, + { url = "https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf", size = 45912, upload-time = "2025-08-11T12:07:45.082Z" }, + { url = "https://files.pythonhosted.org/packages/c7/87/3bac136181e271e29170d8d71929cdeddeb77f3e8b6a0c08da3a8e9da114/multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605", size = 43076, upload-time = "2025-08-11T12:07:46.746Z" }, + { url = "https://files.pythonhosted.org/packages/64/94/0a8e63e36c049b571c9ae41ee301ada29c3fee9643d9c2548d7d558a1d99/multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb", size = 82812, upload-time = "2025-08-11T12:07:48.402Z" }, + { url = "https://files.pythonhosted.org/packages/25/1a/be8e369dfcd260d2070a67e65dd3990dd635cbd735b98da31e00ea84cd4e/multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e", size = 48313, upload-time = "2025-08-11T12:07:49.679Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/dd4ade298674b2f9a7b06a32c94ffbc0497354df8285f27317c66433ce3b/multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f", size = 46777, upload-time = "2025-08-11T12:07:51.318Z" }, + { url = "https://files.pythonhosted.org/packages/89/db/98aa28bc7e071bfba611ac2ae803c24e96dd3a452b4118c587d3d872c64c/multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773", size = 229321, upload-time = "2025-08-11T12:07:52.965Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bc/01ddda2a73dd9d167bd85d0e8ef4293836a8f82b786c63fb1a429bc3e678/multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e", size = 249954, upload-time = "2025-08-11T12:07:54.423Z" }, + { url = "https://files.pythonhosted.org/packages/06/78/6b7c0f020f9aa0acf66d0ab4eb9f08375bac9a50ff5e3edb1c4ccd59eafc/multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0", size = 228612, upload-time = "2025-08-11T12:07:55.914Z" }, + { url = "https://files.pythonhosted.org/packages/00/44/3faa416f89b2d5d76e9d447296a81521e1c832ad6e40b92f990697b43192/multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395", size = 257528, upload-time = "2025-08-11T12:07:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/05/5f/77c03b89af0fcb16f018f668207768191fb9dcfb5e3361a5e706a11db2c9/multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45", size = 256329, upload-time = "2025-08-11T12:07:58.844Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e9/ed750a2a9afb4f8dc6f13dc5b67b514832101b95714f1211cd42e0aafc26/multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb", size = 247928, upload-time = "2025-08-11T12:08:01.037Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b5/e0571bc13cda277db7e6e8a532791d4403dacc9850006cb66d2556e649c0/multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5", size = 245228, upload-time = "2025-08-11T12:08:02.96Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a3/69a84b0eccb9824491f06368f5b86e72e4af54c3067c37c39099b6687109/multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141", size = 235869, upload-time = "2025-08-11T12:08:04.746Z" }, + { url = "https://files.pythonhosted.org/packages/a9/9d/28802e8f9121a6a0804fa009debf4e753d0a59969ea9f70be5f5fdfcb18f/multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d", size = 243446, upload-time = "2025-08-11T12:08:06.332Z" }, + { url = "https://files.pythonhosted.org/packages/38/ea/6c98add069b4878c1d66428a5f5149ddb6d32b1f9836a826ac764b9940be/multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d", size = 252299, upload-time = "2025-08-11T12:08:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/3a/09/8fe02d204473e14c0af3affd50af9078839dfca1742f025cca765435d6b4/multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0", size = 246926, upload-time = "2025-08-11T12:08:09.467Z" }, + { url = "https://files.pythonhosted.org/packages/37/3d/7b1e10d774a6df5175ecd3c92bff069e77bed9ec2a927fdd4ff5fe182f67/multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92", size = 243383, upload-time = "2025-08-11T12:08:10.981Z" }, + { url = "https://files.pythonhosted.org/packages/50/b0/a6fae46071b645ae98786ab738447de1ef53742eaad949f27e960864bb49/multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e", size = 47775, upload-time = "2025-08-11T12:08:12.439Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0a/2436550b1520091af0600dff547913cb2d66fbac27a8c33bc1b1bccd8d98/multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4", size = 53100, upload-time = "2025-08-11T12:08:13.823Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/43ac51faff934086db9c072a94d327d71b7d8b40cd5dcb47311330929ef0/multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad", size = 45501, upload-time = "2025-08-11T12:08:15.173Z" }, + { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, +] + +[[package]] +name = "myst-parser" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985, upload-time = "2025-02-12T10:53:03.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579, upload-time = "2025-02-12T10:53:02.078Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "propcache" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, + { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, + { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, + { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, + { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, + { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, + { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, + { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, + { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "python-driver-docs" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "eventlet" }, + { name = "gevent" }, + { name = "gremlinpython" }, + { name = "pygments" }, + { name = "recommonmark" }, + { name = "redirects-cli" }, + { name = "scales" }, + { name = "six" }, + { name = "sphinx" }, + { name = "sphinx-autobuild" }, + { name = "sphinx-multiversion-scylla" }, + { name = "sphinx-scylladb-theme" }, + { name = "sphinx-sitemap" }, + { name = "tornado" }, +] + +[package.dev-dependencies] +dev = [ + { name = "hatchling" }, +] + +[package.metadata] +requires-dist = [ + { name = "eventlet", specifier = ">=0.40.3,<1.0.0" }, + { name = "gevent", specifier = ">=25.9.1,<26.0.0" }, + { name = "gremlinpython", specifier = "==3.7.4" }, + { name = "pygments", specifier = ">=2.19.2,<3.0.0" }, + { name = "recommonmark", specifier = "==0.7.1" }, + { name = "redirects-cli", specifier = "~=0.1.3" }, + { name = "scales", specifier = ">=1.0.9,<2.0.0" }, + { name = "six", specifier = ">=1.9" }, + { name = "sphinx", specifier = ">=8.2.3,<9.0.0" }, + { name = "sphinx-autobuild", specifier = ">=2025.0.0,<2026.0.0" }, + { name = "sphinx-multiversion-scylla", specifier = ">=0.3.2,<1.0.0" }, + { name = "sphinx-scylladb-theme", specifier = ">=1.8.2,<2.0.0" }, + { name = "sphinx-sitemap", specifier = ">=2.8.0,<3.0.0" }, + { name = "tornado", specifier = ">=6.5,<7.0" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "hatchling", specifier = "==1.27.0" }] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "recommonmark" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "commonmark" }, + { name = "docutils" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/00/3dd2bdc4184b0ce754b5b446325abf45c2e0a347e022292ddc44670f628c/recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67", size = 34444, upload-time = "2020-12-17T19:24:56.523Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/77/ed589c75db5d02a77a1d5d2d9abc63f29676467d396c64277f98b50b79c2/recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f", size = 10214, upload-time = "2020-12-17T19:24:55.137Z" }, +] + +[[package]] +name = "redirects-cli" +version = "0.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/3e/942a3d5322f05aa75c903de1bdc101800cc0627e4c6c371768ef9070fa28/redirects_cli-0.1.3.tar.gz", hash = "sha256:0cc6f35ae372d087d56bc03cfc639d6e2eac0771454c3c173ac6f3dc233969bc", size = 4404, upload-time = "2022-11-29T19:11:20.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/a4/829d6901e0c2c492d0d46190aadf3f4b9c6db6594b4e14a814f844014b28/redirects_cli-0.1.3-py3-none-any.whl", hash = "sha256:8a7a548d5f45b98db7d110fd8affbbb44b966cf250e35b5f4c9bd6541622272d", size = 4655, upload-time = "2022-11-29T19:11:18.898Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rich" +version = "14.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, +] + +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, +] + +[[package]] +name = "scales" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/85/b4a3933f227889b536a76c7ed5a0708ae5f63fe20f81d09a725228349e81/scales-1.0.9.tar.gz", hash = "sha256:8b6930f7d4bf115192290b44c757af5e254e3fcfcb75ff9a51f5c96a404e2753", size = 21889, upload-time = "2015-02-28T18:49:39.538Z" } + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, +] + +[[package]] +name = "sphinx" +version = "8.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals-py" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, +] + +[[package]] +name = "sphinx-autobuild" +version = "2025.8.25" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "sphinx" }, + { name = "starlette" }, + { name = "uvicorn" }, + { name = "watchfiles" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/3c/a59a3a453d4133777f7ed2e83c80b7dc817d43c74b74298ca0af869662ad/sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213", size = 15200, upload-time = "2025-08-25T18:44:55.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/20/56411b52f917696995f5ad27d2ea7e9492c84a043c5b49a3a3173573cd93/sphinx_autobuild-2025.8.25-py3-none-any.whl", hash = "sha256:b750ac7d5a18603e4665294323fd20f6dcc0a984117026d1986704fa68f0379a", size = 12535, upload-time = "2025-08-25T18:44:54.164Z" }, +] + +[[package]] +name = "sphinx-collapse" +version = "0.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/02/183559e508906f7282d4dd6ccbf443efddaa3114b7f6fab425949b37a003/sphinx_collapse-0.1.3.tar.gz", hash = "sha256:cae141e6f03ecd52ed246a305a69e1b0d5d05e6cdf3fe803d40d583ad6ad895a", size = 18540, upload-time = "2024-02-22T15:24:38.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/2f/5889082a6a535aa8613a327308582914517082967583ad45586b7d61c145/sphinx_collapse-0.1.3-py3-none-any.whl", hash = "sha256:85fadb2ec8769b93fd04276538668fa96239ef60c20c4a9eaa3e480387a6e65b", size = 4688, upload-time = "2024-02-22T15:24:29.365Z" }, +] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload-time = "2023-04-14T08:10:22.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, +] + +[[package]] +name = "sphinx-last-updated-by-git" +version = "0.3.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/fd/de1685b6dab173dff31da24e0d3b29f02873fc24a1cdbb7678721ddc8581/sphinx_last_updated_by_git-0.3.8.tar.gz", hash = "sha256:c145011f4609d841805b69a9300099fc02fed8f5bb9e5bcef77d97aea97b7761", size = 10785, upload-time = "2024-08-11T07:15:54.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/fb/e496f16fa11fbe2dbdd0b5e306ede153dfed050aae4766fc89d500720dc7/sphinx_last_updated_by_git-0.3.8-py3-none-any.whl", hash = "sha256:6382c8285ac1f222483a58569b78c0371af5e55f7fbf9c01e5e8a72d6fdfa499", size = 8580, upload-time = "2024-08-11T07:15:53.244Z" }, +] + +[[package]] +name = "sphinx-multiversion-scylla" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/48/aaa77dc9fedb1671a1729b731f4b359d594bc6af689e230027a6e299d661/sphinx_multiversion_scylla-0.3.2.tar.gz", hash = "sha256:f415311273228f4f766c36256503da8e2ce01f9d13423f3fcee3160d6284852b", size = 11442, upload-time = "2024-08-02T13:06:26.313Z" } + +[[package]] +name = "sphinx-notfound-page" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/b2/67603444a8ee97b4a8ea71b0a9d6bab1727ed65e362c87e02f818ee57b8a/sphinx_notfound_page-1.1.0.tar.gz", hash = "sha256:913e1754370bb3db201d9300d458a8b8b5fb22e9246a816643a819a9ea2b8067", size = 7392, upload-time = "2025-01-28T18:45:02.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/d4/019fe439c840a7966012bbb95ccbdd81c5c10271749706793b43beb05145/sphinx_notfound_page-1.1.0-py3-none-any.whl", hash = "sha256:835dc76ff7914577a1f58d80a2c8418fb6138c0932c8da8adce4d9096fbcd389", size = 8167, upload-time = "2025-01-28T18:45:00.465Z" }, +] + +[[package]] +name = "sphinx-scylladb-theme" +version = "1.8.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "pyyaml" }, + { name = "setuptools" }, + { name = "sphinx-collapse" }, + { name = "sphinx-copybutton" }, + { name = "sphinx-notfound-page" }, + { name = "sphinx-substitution-extensions" }, + { name = "sphinx-tabs" }, + { name = "sphinxcontrib-mermaid" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/83/a1cd84f0d0015dab4f9cb09488b89d12f19941098c595406068c23f5e2fa/sphinx_scylladb_theme-1.8.8.tar.gz", hash = "sha256:15af599424f8b2ddbf14644b267c0bd31bc3fbbd64bca5b97d7b31bdb84d2c3d", size = 1617725, upload-time = "2025-09-02T18:59:44.533Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/22/3f1aa6399045b93362786c2255068c7aef70385d4471d214dbec8fecbeba/sphinx_scylladb_theme-1.8.8-py3-none-any.whl", hash = "sha256:9b37f58b745bfc818b6f0869cbcc414a3ad6658023b22a6abbec44da5c09cf80", size = 1659430, upload-time = "2025-09-02T18:59:42.733Z" }, +] + +[[package]] +name = "sphinx-sitemap" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx-last-updated-by-git" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/0e/e249fdd17c0530c8260191f0020556862b243fa718cf0e6b28d956eafb2c/sphinx_sitemap-2.8.0.tar.gz", hash = "sha256:749d7184a0c7b73d486a232b54b5c1b38a0e2d6f18cf19fb1b033b8162b44a82", size = 6829, upload-time = "2025-08-12T04:54:24.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ab/3bc6f9ee09b5fd5ae2e5ae1febf41a2ed2bdde452a41c06e78fec0296b5c/sphinx_sitemap-2.8.0-py3-none-any.whl", hash = "sha256:332042cd5b9385f61ec2861dfd550d9bccbdfcff86f6b68c7072cf40c9f16363", size = 6167, upload-time = "2025-08-12T04:54:23.479Z" }, +] + +[[package]] +name = "sphinx-substitution-extensions" +version = "2025.6.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype" }, + { name = "docutils" }, + { name = "myst-parser" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/92/86cfb27705791652a54d42f35409a5128fc29395fbe38039222fe15116ff/sphinx_substitution_extensions-2025.6.6.tar.gz", hash = "sha256:241ddb9f88962422a8b436243d2601f9d94616e94b520ce1a6a528d9a3b7804a", size = 22109, upload-time = "2025-06-06T20:53:19.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/11/d81677eb3dcd9b130cf97169ebf762170ade3fa676395329d59f9cf1540a/sphinx_substitution_extensions-2025.6.6-py2.py3-none-any.whl", hash = "sha256:96206ba7a2cdf43237edec81f87770a5683a91a1de3bf93f7a22309509ecbfd8", size = 7949, upload-time = "2025-06-06T20:53:18.341Z" }, +] + +[[package]] +name = "sphinx-tabs" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "pygments" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/53/a9a91995cb365e589f413b77fc75f1c0e9b4ac61bfa8da52a779ad855cc0/sphinx-tabs-3.4.7.tar.gz", hash = "sha256:991ad4a424ff54119799ba1491701aa8130dd43509474aef45a81c42d889784d", size = 15891, upload-time = "2024-10-08T13:37:27.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/c6/f47505b564b918a3ba60c1e99232d4942c4a7e44ecaae603e829e3d05dae/sphinx_tabs-3.4.7-py3-none-any.whl", hash = "sha256:c12d7a36fd413b369e9e9967a0a4015781b71a9c393575419834f19204bd1915", size = 9727, upload-time = "2024-10-08T13:37:26.192Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-mermaid" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/69/bf039237ad260073e8c02f820b3e00dc34f3a2de20aff7861e6b19d2f8c5/sphinxcontrib_mermaid-1.0.0.tar.gz", hash = "sha256:2e8ab67d3e1e2816663f9347d026a8dee4a858acdd4ad32dd1c808893db88146", size = 15153, upload-time = "2024-10-12T16:33:03.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/c8/784b9ac6ea08aa594c1a4becbd0dbe77186785362e31fd633b8c6ae0197a/sphinxcontrib_mermaid-1.0.0-py3-none-any.whl", hash = "sha256:60b72710ea02087f212028feb09711225fbc2e343a10d34822fe787510e1caa3", size = 9597, upload-time = "2024-10-12T16:33:02.303Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "starlette" +version = "0.48.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949, upload-time = "2025-09-13T08:41:05.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" }, + { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" }, + { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" }, + { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" }, +] + +[[package]] +name = "trove-classifiers" +version = "2025.9.11.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/9a/778622bc06632529817c3c524c82749a112603ae2bbcf72ee3eb33a2c4f1/trove_classifiers-2025.9.11.17.tar.gz", hash = "sha256:931ca9841a5e9c9408bc2ae67b50d28acf85bef56219b56860876dd1f2d024dd", size = 16975, upload-time = "2025-09-11T17:07:50.97Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl", hash = "sha256:5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd", size = 14158, upload-time = "2025-09-11T17:07:49.886Z" }, +] + +[[package]] +name = "typer" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/ca/950278884e2ca20547ff3eb109478c6baf6b8cf219318e6bc4f666fad8e8/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca", size = 104755, upload-time = "2025-09-23T09:47:48.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9", size = 46748, upload-time = "2025-09-23T09:47:46.777Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/57/1616c8274c3442d802621abf5deb230771c7a0fec9414cb6763900eb3868/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13", size = 80367, upload-time = "2025-09-23T13:33:47.486Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/cd/584a2ceb5532af99dd09e50919e3615ba99aa127e9850eafe5f31ddfdb9a/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c", size = 67976, upload-time = "2025-09-23T13:33:45.842Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" }, + { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" }, + { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" }, + { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" }, + { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" }, + { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" }, + { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" }, + { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" }, + { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" }, + { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" }, + { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" }, + { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" }, + { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" }, + { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" }, + { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, + { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, + { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" }, + { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" }, + { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" }, + { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" }, + { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" }, + { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" }, + { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" }, + { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" }, + { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" }, + { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" }, + { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, + { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, + { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, + { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, + { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, + { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, + { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, + { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, + { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, + { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, + { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, + { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, + { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, + { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +] + +[[package]] +name = "zope-event" +version = "6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/d8/9c8b0c6bb1db09725395618f68d3b8a08089fca0aed28437500caaf713ee/zope_event-6.0.tar.gz", hash = "sha256:0ebac894fa7c5f8b7a89141c272133d8c1de6ddc75ea4b1f327f00d1f890df92", size = 18731, upload-time = "2025-09-12T07:10:13.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/b5/1abb5a8b443314c978617bf46d5d9ad648bdf21058074e817d7efbb257db/zope_event-6.0-py3-none-any.whl", hash = "sha256:6f0922593407cc673e7d8766b492c519f91bdc99f3080fe43dcec0a800d682a3", size = 6409, upload-time = "2025-09-12T07:10:12.316Z" }, +] + +[[package]] +name = "zope-interface" +version = "8.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/3a/7fcf02178b8fad0a51e67e32765cd039ae505d054d744d76b8c2bbcba5ba/zope_interface-8.0.1.tar.gz", hash = "sha256:eba5610d042c3704a48222f7f7c6ab5b243ed26f917e2bc69379456b115e02d1", size = 253746, upload-time = "2025-09-25T05:55:51.285Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/dc/3c12fca01c910c793d636ffe9c0984e0646abaf804e44552070228ed0ede/zope_interface-8.0.1-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:c7cc027fc5c61c5d69e5080c30b66382f454f43dc379c463a38e78a9c6bab71a", size = 208992, upload-time = "2025-09-25T05:58:40.712Z" }, + { url = "https://files.pythonhosted.org/packages/46/71/6127b7282a3e380ca927ab2b40778a9c97935a4a57a2656dadc312db5f30/zope_interface-8.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fcf9097ff3003b7662299f1c25145e15260ec2a27f9a9e69461a585d79ca8552", size = 209051, upload-time = "2025-09-25T05:58:42.182Z" }, + { url = "https://files.pythonhosted.org/packages/56/86/4387a9f951ee18b0e41fda77da77d59c33e59f04660578e2bad688703e64/zope_interface-8.0.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6d965347dd1fb9e9a53aa852d4ded46b41ca670d517fd54e733a6b6a4d0561c2", size = 259223, upload-time = "2025-09-25T05:58:23.191Z" }, + { url = "https://files.pythonhosted.org/packages/61/08/ce60a114466abc067c68ed41e2550c655f551468ae17b4b17ea360090146/zope_interface-8.0.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9a3b8bb77a4b89427a87d1e9eb969ab05e38e6b4a338a9de10f6df23c33ec3c2", size = 264690, upload-time = "2025-09-25T05:58:15.052Z" }, + { url = "https://files.pythonhosted.org/packages/36/9a/62a9ba3a919594605a07c34eee3068659bbd648e2fa0c4a86d876810b674/zope_interface-8.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:87e6b089002c43231fb9afec89268391bcc7a3b66e76e269ffde19a8112fb8d5", size = 264201, upload-time = "2025-09-25T06:26:27.797Z" }, + { url = "https://files.pythonhosted.org/packages/da/06/8fe88bd7edef60566d21ef5caca1034e10f6b87441ea85de4bbf9ea74768/zope_interface-8.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:64a43f5280aa770cbafd0307cb3d1ff430e2a1001774e8ceb40787abe4bb6658", size = 212273, upload-time = "2025-09-25T06:00:25.398Z" }, +] From 9bb16416085ac7d93c13d45e3faa275b7168b51c Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 26 Sep 2025 10:32:33 -0400 Subject: [PATCH 122/298] cicd: don't test workflow on docs changes --- .github/workflows/integration-tests.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 0572e1d58e..d2bd01703d 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -5,6 +5,18 @@ on: branches: - master - 'branch-**' + paths-ignore: + - docs/* + - examples/* + - scripts/* + - .gitignore + - '*.rst' + - '*.ini' + - LICENSE + - .github/dependabot.yml + - .github/pull_request_template.md + - "*.md" + - .github/workflows/docs-* pull_request: paths-ignore: - docs/* @@ -16,6 +28,8 @@ on: - LICENSE - .github/dependabot.yml - .github/pull_request_template.md + - "*.md" + - .github/workflows/docs-* - workflow_dispatch: jobs: From e851a56b9500fe3e410c31f3004ce4c778638a40 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 26 Sep 2025 12:24:40 -0400 Subject: [PATCH 123/298] docs: set ubuntu-latest runner --- .github/workflows/docs-pages.yml | 2 +- .github/workflows/docs-pr.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index cea5e395e5..bcced995ef 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -18,7 +18,7 @@ on: jobs: release: - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v5 diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index b7230ad2d3..ee2f5c4b30 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -25,7 +25,7 @@ on: jobs: build: - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v5 From ac91d601b8932de3bb556be19416b0f77dfcfcc6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 19 Oct 2025 21:03:25 +0000 Subject: [PATCH 124/298] chore(deps): update astral-sh/setup-uv action to v7 --- .github/workflows/docs-pages.yml | 2 +- .github/workflows/docs-pr.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index bcced995ef..dde17cfa53 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -28,7 +28,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: working-directory: docs enable-cache: true diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index ee2f5c4b30..2ee44bcf74 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -34,7 +34,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: working-directory: docs enable-cache: true From c431bb5791315139090691d4cd49b072536463b5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:56:53 +0000 Subject: [PATCH 125/298] chore(deps): update github artifact actions --- .github/workflows/build-push.yml | 2 +- .github/workflows/lib-build-and-push.yml | 6 +++--- .github/workflows/publish-manually.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 1ac2fda9bb..f76c6e44cd 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -23,7 +23,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: path: dist merge-multiple: true diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index 809f49e29d..102a9f68a4 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -153,7 +153,7 @@ jobs: run: | GITHUB_WORKFLOW_REF="scylladb/python-driver/.github/workflows/lib-build-and-push.yml@refs/heads/master" CIBW_BUILD="cp3*" cibuildwheel --archs aarch64 --output-dir wheelhouse - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: wheels-${{ matrix.target }}-${{ matrix.os }} path: ./wheelhouse/*.whl @@ -172,7 +172,7 @@ jobs: - name: Build sdist run: uv build --sdist - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: source-dist path: dist/*.tar.gz @@ -185,7 +185,7 @@ jobs: id-token: write steps: - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: path: dist merge-multiple: true diff --git a/.github/workflows/publish-manually.yml b/.github/workflows/publish-manually.yml index 3bc260e820..444ec42bc7 100644 --- a/.github/workflows/publish-manually.yml +++ b/.github/workflows/publish-manually.yml @@ -53,7 +53,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: path: dist merge-multiple: true From b3c63c00faa021cf17730a8079f22422ecee7af0 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 4 Nov 2025 05:01:44 -0400 Subject: [PATCH 126/298] policies: fix dc aware and rack aware policies initialization In cases when nodes are listed not in a proper groups of dc (for DCAwareRoundRobinPolicy) and dc+rack (for RackAwareRoundRobinPolicy). Policy register only nodes from the last group or dc+rack. So, policy knows only last group of nodes, rest of the nodes considered to be non-existent, until node is restarted, which can be days. --- cassandra/policies.py | 7 +++---- tests/unit/test_policies.py | 6 +++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cassandra/policies.py b/cassandra/policies.py index cb83238e87..efef0bb2a5 100644 --- a/cassandra/policies.py +++ b/cassandra/policies.py @@ -14,7 +14,6 @@ import random from collections import namedtuple -from functools import lru_cache from itertools import islice, cycle, groupby, repeat import logging from random import randint, shuffle @@ -254,7 +253,7 @@ def _dc(self, host): def populate(self, cluster, hosts): for dc, dc_hosts in groupby(hosts, lambda h: self._dc(h)): - self._dc_live_hosts[dc] = tuple(set(dc_hosts)) + self._dc_live_hosts[dc] = tuple({*dc_hosts, *self._dc_live_hosts.get(dc, [])}) if not self.local_dc: self._endpoints = [ @@ -374,9 +373,9 @@ def _dc(self, host): def populate(self, cluster, hosts): for (dc, rack), rack_hosts in groupby(hosts, lambda host: (self._dc(host), self._rack(host))): - self._live_hosts[(dc, rack)] = tuple(set(rack_hosts)) + self._live_hosts[(dc, rack)] = tuple({*rack_hosts, *self._live_hosts.get((dc, rack), [])}) for dc, dc_hosts in groupby(hosts, lambda host: self._dc(host)): - self._dc_live_hosts[dc] = tuple(set(dc_hosts)) + self._dc_live_hosts[dc] = tuple({*dc_hosts, *self._dc_live_hosts.get(dc, [])}) self._position = randint(0, len(hosts) - 1) if hosts else 0 diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index c98511ab34..15970aadfa 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import random import unittest from itertools import islice, cycle @@ -199,6 +199,8 @@ def test_no_remote(self, policy_specialization, constructor_args): h.set_location_info("dc1", "rack1") hosts.append(h) + random.shuffle(hosts) + policy = policy_specialization(*constructor_args) policy.populate(None, hosts) qplan = list(policy.make_query_plan()) @@ -213,6 +215,8 @@ def test_with_remotes(self, policy_specialization, constructor_args): for h in hosts[4:]: h.set_location_info("dc2", "rack1") + random.shuffle(hosts) + local_rack_hosts = set(h for h in hosts if h.datacenter == "dc1" and h.rack == "rack1") local_hosts = set(h for h in hosts if h.datacenter == "dc1" and h.rack != "rack1") remote_hosts = set(h for h in hosts if h.datacenter != "dc1") From bc0317baaccfe51ea8b9b824081e080ae8da355a Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 4 Nov 2025 08:13:09 -0400 Subject: [PATCH 127/298] token-aware-policy: drop _tablets_routing_v1 flag This flag was introduced to check if server supports tablets. As result driver needs to sync it to policy when control connection is established. Which is an unwanted problem. Let's relay on presence of tablets for given table instead, which will not require any syncing. --- cassandra/cluster.py | 8 --- cassandra/policies.py | 9 ++-- cassandra/tablets.py | 3 ++ tests/integration/standard/test_tablets.py | 32 +++++++++++- tests/unit/test_policies.py | 57 +++++++++++++++++----- 5 files changed, 81 insertions(+), 28 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 14c5cb4bd9..5822a23aa9 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -1734,14 +1734,6 @@ def connect(self, keyspace=None, wait_for_all_pools=False): self.shutdown() raise - # Update the information about tablet support after connection handshake. - self.load_balancing_policy._tablets_routing_v1 = self.control_connection._tablets_routing_v1 - child_policy = self.load_balancing_policy.child_policy if hasattr(self.load_balancing_policy, 'child_policy') else None - while child_policy is not None: - if hasattr(child_policy, '_tablet_routing_v1'): - child_policy._tablet_routing_v1 = self.control_connection._tablets_routing_v1 - child_policy = child_policy.child_policy if hasattr(child_policy, 'child_policy') else None - self.profile_manager.check_supported() # todo: rename this method if self.idle_heartbeat_interval: diff --git a/cassandra/policies.py b/cassandra/policies.py index efef0bb2a5..a679bff877 100644 --- a/cassandra/policies.py +++ b/cassandra/policies.py @@ -476,7 +476,6 @@ class TokenAwarePolicy(LoadBalancingPolicy): _child_policy = None _cluster_metadata = None - _tablets_routing_v1 = False shuffle_replicas = False """ Yield local replicas in a random order. @@ -488,7 +487,6 @@ def __init__(self, child_policy, shuffle_replicas=False): def populate(self, cluster, hosts): self._cluster_metadata = cluster.metadata - self._tablets_routing_v1 = cluster.control_connection._tablets_routing_v1 self._child_policy.populate(cluster, hosts) def check_supported(self): @@ -513,17 +511,16 @@ def make_query_plan(self, working_keyspace=None, query=None): return replicas = [] - if self._tablets_routing_v1: + if self._cluster_metadata._tablets.table_has_tablets(keyspace, query.table): tablet = self._cluster_metadata._tablets.get_tablet_for_key( - keyspace, query.table, self._cluster_metadata.token_map.token_class.from_key(query.routing_key)) + keyspace, query.table, self._cluster_metadata.token_map.token_class.from_key(query.routing_key)) if tablet is not None: replicas_mapped = set(map(lambda r: r[0], tablet.replicas)) child_plan = child.make_query_plan(keyspace, query) replicas = [host for host in child_plan if host.host_id in replicas_mapped] - - if not replicas: + else: replicas = self._cluster_metadata.get_replicas(keyspace, query.routing_key) if self.shuffle_replicas: diff --git a/cassandra/tablets.py b/cassandra/tablets.py index 457ee93ca4..dca26ab0df 100644 --- a/cassandra/tablets.py +++ b/cassandra/tablets.py @@ -49,6 +49,9 @@ def __init__(self, tablets): self._tablets = tablets self._lock = Lock() + def table_has_tablets(self, keyspace, table) -> bool: + return bool(self._tablets.get((keyspace, table), [])) + def get_tablet_for_key(self, keyspace, table, t): tablet = self._tablets.get((keyspace, table), []) if not tablet: diff --git a/tests/integration/standard/test_tablets.py b/tests/integration/standard/test_tablets.py index 0216f7843a..d9439e5c2c 100644 --- a/tests/integration/standard/test_tablets.py +++ b/tests/integration/standard/test_tablets.py @@ -2,7 +2,7 @@ import pytest -from cassandra.cluster import Cluster +from cassandra.cluster import Cluster, EXEC_PROFILE_DEFAULT, ExecutionProfile from cassandra.policies import ConstantReconnectionPolicy, RoundRobinPolicy, TokenAwarePolicy from tests.integration import PROTOCOL_VERSION, use_cluster, get_cluster @@ -163,6 +163,36 @@ def test_tablets_shard_awareness(self): self.query_data_shard_select(self.session) self.query_data_shard_insert(self.session) + def test_tablets_lbp_in_profile(self): + cluster = Cluster(contact_points=["127.0.0.1", "127.0.0.2", "127.0.0.3"], protocol_version=PROTOCOL_VERSION, + execution_profiles={ + EXEC_PROFILE_DEFAULT: ExecutionProfile( + load_balancing_policy=TokenAwarePolicy(RoundRobinPolicy()), + )}, + reconnection_policy=ConstantReconnectionPolicy(1)) + session = cluster.connect() + try: + self.query_data_host_select(self.session) + self.query_data_host_insert(self.session) + finally: + session.shutdown() + cluster.shutdown() + + def test_tablets_shard_awareness_lbp_in_profile(self): + cluster = Cluster(contact_points=["127.0.0.1", "127.0.0.2", "127.0.0.3"], protocol_version=PROTOCOL_VERSION, + execution_profiles={ + EXEC_PROFILE_DEFAULT: ExecutionProfile( + load_balancing_policy=TokenAwarePolicy(RoundRobinPolicy()), + )}, + reconnection_policy=ConstantReconnectionPolicy(1)) + session = cluster.connect() + try: + self.query_data_shard_select(self.session) + self.query_data_shard_insert(self.session) + finally: + session.shutdown() + cluster.shutdown() + def test_tablets_invalidation_drop_ks_while_reconnecting(self): def recreate_while_reconnecting(_): # Kill control connection diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index 15970aadfa..e65a89bca7 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -36,6 +36,7 @@ from cassandra.connection import DefaultEndPoint, UnixSocketEndPoint from cassandra.pool import Host from cassandra.query import Statement +from cassandra.tablets import Tablets, Tablet class LoadBalancingPolicyTest(unittest.TestCase): @@ -582,7 +583,8 @@ class TokenAwarePolicyTest(unittest.TestCase): def test_wrap_round_robin(self): cluster = Mock(spec=Cluster) cluster.metadata = Mock(spec=Metadata) - cluster.control_connection._tablets_routing_v1 = False + cluster.metadata._tablets = Mock(spec=Tablets) + cluster.metadata._tablets.table_has_tablets.return_value = [] hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy) for i in range(4)] for host in hosts: host.set_up() @@ -614,7 +616,8 @@ def get_replicas(keyspace, packed_key): def test_wrap_dc_aware(self): cluster = Mock(spec=Cluster) cluster.metadata = Mock(spec=Metadata) - cluster.control_connection._tablets_routing_v1 = False + cluster.metadata._tablets = Mock(spec=Tablets) + cluster.metadata._tablets.table_has_tablets.return_value = [] hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy) for i in range(4)] for host in hosts: host.set_up() @@ -744,9 +747,10 @@ def test_statement_keyspace(self): cluster = Mock(spec=Cluster) cluster.metadata = Mock(spec=Metadata) - cluster.control_connection._tablets_routing_v1 = False + cluster.metadata._tablets = Mock(spec=Tablets) replicas = hosts[2:] cluster.metadata.get_replicas.return_value = replicas + cluster.metadata._tablets.table_has_tablets.return_value = [] child_policy = Mock() child_policy.make_query_plan.return_value = hosts @@ -803,7 +807,8 @@ def test_shuffles_if_given_keyspace_and_routing_key(self): @test_category policy """ - self._assert_shuffle(keyspace='keyspace', routing_key='routing_key') + self._assert_shuffle(cluster=self._prepare_cluster_with_vnodes(), keyspace='keyspace', routing_key='routing_key') + self._assert_shuffle(cluster=self._prepare_cluster_with_tablets(), keyspace='keyspace', routing_key='routing_key') def test_no_shuffle_if_given_no_keyspace(self): """ @@ -814,7 +819,8 @@ def test_no_shuffle_if_given_no_keyspace(self): @test_category policy """ - self._assert_shuffle(keyspace=None, routing_key='routing_key') + self._assert_shuffle(cluster=self._prepare_cluster_with_vnodes(), keyspace=None, routing_key='routing_key') + self._assert_shuffle(cluster=self._prepare_cluster_with_tablets(), keyspace=None, routing_key='routing_key') def test_no_shuffle_if_given_no_routing_key(self): """ @@ -825,20 +831,38 @@ def test_no_shuffle_if_given_no_routing_key(self): @test_category policy """ - self._assert_shuffle(keyspace='keyspace', routing_key=None) + self._assert_shuffle(cluster=self._prepare_cluster_with_vnodes(), keyspace='keyspace', routing_key=None) + self._assert_shuffle(cluster=self._prepare_cluster_with_tablets(), keyspace='keyspace', routing_key=None) - @patch('cassandra.policies.shuffle') - def _assert_shuffle(self, patched_shuffle, keyspace, routing_key): + def _prepare_cluster_with_vnodes(self): hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy) for i in range(4)] for host in hosts: host.set_up() + cluster = Mock(spec=Cluster) + cluster.metadata = Mock(spec=Metadata) + cluster.metadata._tablets = Mock(spec=Tablets) + cluster.metadata.all_hosts.return_value = hosts + cluster.metadata.get_replicas.return_value = hosts[2:] + cluster.metadata._tablets.table_has_tablets.return_value = False + return cluster + def _prepare_cluster_with_tablets(self): + hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy) for i in range(4)] + for host in hosts: + host.set_up() cluster = Mock(spec=Cluster) cluster.metadata = Mock(spec=Metadata) - cluster.control_connection._tablets_routing_v1 = False - replicas = hosts[2:] - cluster.metadata.get_replicas.return_value = replicas + cluster.metadata._tablets = Mock(spec=Tablets) + cluster.metadata.all_hosts.return_value = hosts + cluster.metadata.get_replicas.return_value = hosts[2:] + cluster.metadata._tablets.table_has_tablets.return_value = True + cluster.metadata._tablets.get_tablet_for_key.return_value = Tablet(replicas=[(h.host_id, 0) for h in hosts[2:]]) + return cluster + @patch('cassandra.policies.shuffle') + def _assert_shuffle(self, patched_shuffle, cluster, keyspace, routing_key): + hosts = cluster.metadata.all_hosts() + replicas = cluster.metadata.get_replicas() child_policy = Mock() child_policy.make_query_plan.return_value = hosts child_policy.distance.return_value = HostDistance.LOCAL @@ -846,6 +870,8 @@ def _assert_shuffle(self, patched_shuffle, keyspace, routing_key): policy = TokenAwarePolicy(child_policy, shuffle_replicas=True) policy.populate(cluster, hosts) + is_tablets = cluster.metadata._tablets.table_has_tablets() + cluster.metadata.get_replicas.reset_mock() child_policy.make_query_plan.reset_mock() query = Statement(routing_key=routing_key) @@ -858,7 +884,11 @@ def _assert_shuffle(self, patched_shuffle, keyspace, routing_key): else: assert set(replicas) == set(qplan[:2]) assert hosts[:2] == qplan[2:] - child_policy.make_query_plan.assert_called_once_with(keyspace, query) + if is_tablets: + child_policy.make_query_plan.assert_called_with(keyspace, query) + assert child_policy.make_query_plan.call_count == 2 + else: + child_policy.make_query_plan.assert_called_once_with(keyspace, query) assert patched_shuffle.call_count == 1 @@ -1538,7 +1568,6 @@ def test_query_plan_deferred_to_child(self): def test_wrap_token_aware(self): cluster = Mock(spec=Cluster) - cluster.control_connection._tablets_routing_v1 = False hosts = [Host(DefaultEndPoint("127.0.0.{}".format(i)), SimpleConvictionPolicy) for i in range(1, 6)] for host in hosts: host.set_up() @@ -1547,6 +1576,8 @@ def get_replicas(keyspace, packed_key): return hosts[:2] cluster.metadata.get_replicas.side_effect = get_replicas + cluster.metadata._tablets = Mock(spec=Tablets) + cluster.metadata._tablets.table_has_tablets.return_value = [] child_policy = TokenAwarePolicy(RoundRobinPolicy()) From 2f271be12a975a538650890e0f665cb5db813c77 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 3 Nov 2025 09:42:59 -0400 Subject: [PATCH 128/298] Update TokenAwarePolicy.make_query_plan to schedule to replicas first End result should be like this: 1. Replicas from the same RACK (if rack is specified) 2. Replicas from the same DC, but Remote RACK (if rack is specified) 3. Replicas from the same DC (if rack is not specified) 3. Replicas from the remote DC 5. Non-replicas in the same order --- cassandra/policies.py | 18 +++++---- tests/unit/test_policies.py | 73 ++++++++++++++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 14 deletions(-) diff --git a/cassandra/policies.py b/cassandra/policies.py index a679bff877..d681980d77 100644 --- a/cassandra/policies.py +++ b/cassandra/policies.py @@ -526,14 +526,16 @@ def make_query_plan(self, working_keyspace=None, query=None): if self.shuffle_replicas: shuffle(replicas) - for replica in replicas: - if replica.is_up and child.distance(replica) in [HostDistance.LOCAL, HostDistance.LOCAL_RACK]: - yield replica - - for host in child.make_query_plan(keyspace, query): - # skip if we've already listed this host - if host not in replicas or child.distance(host) == HostDistance.REMOTE: - yield host + def yield_in_order(hosts): + for distance in [HostDistance.LOCAL_RACK, HostDistance.LOCAL, HostDistance.REMOTE]: + for replica in hosts: + if replica.is_up and child.distance(replica) == distance: + yield replica + + # yield replicas: local_rack, local, remote + yield from yield_in_order(replicas) + # yield rest of the cluster: local_rack, local, remote + yield from yield_in_order([host for host in child.make_query_plan(keyspace, query) if host not in replicas]) def on_up(self, *args, **kwargs): return self._child_policy.on_up(*args, **kwargs) diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index e65a89bca7..084b2c3137 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -636,7 +636,7 @@ def get_replicas(keyspace, packed_key): cluster.metadata.get_replicas.side_effect = get_replicas - policy = TokenAwarePolicy(DCAwareRoundRobinPolicy("dc1", used_hosts_per_remote_dc=1)) + policy = TokenAwarePolicy(DCAwareRoundRobinPolicy("dc1", used_hosts_per_remote_dc=2)) policy.populate(cluster, hosts) for i in range(4): @@ -648,14 +648,75 @@ def get_replicas(keyspace, packed_key): assert qplan[0] in replicas assert qplan[0].datacenter == "dc1" - # then the local non-replica - assert qplan[1] not in replicas + # then the replica from remote DC + assert qplan[1] in replicas + assert qplan[1].datacenter == "dc2" + + # then non-replica from local DC + assert qplan[2] not in replicas + assert qplan[2].datacenter == "dc1" + + # and only then non-replica from remote DC + assert qplan[3] not in replicas + assert qplan[3].datacenter == "dc2" + + assert 4 == len(qplan) + + def test_wrap_rack_aware(self): + cluster = Mock(spec=Cluster) + cluster.metadata = Mock(spec=Metadata) + cluster.metadata._tablets = Mock(spec=Tablets) + cluster.metadata._tablets.table_has_tablets.return_value = [] + hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy) for i in range(8)] + for host in hosts: + host.set_up() + hosts[0].set_location_info("dc1", "rack1") + hosts[1].set_location_info("dc1", "rack2") + hosts[2].set_location_info("dc2", "rack1") + hosts[3].set_location_info("dc2", "rack2") + hosts[4].set_location_info("dc1", "rack1") + hosts[5].set_location_info("dc1", "rack2") + hosts[6].set_location_info("dc2", "rack1") + hosts[7].set_location_info("dc2", "rack2") + + def get_replicas(keyspace, packed_key): + index = struct.unpack('>i', packed_key)[0] + # return one node from each DC + if index % 2 == 0: + return [hosts[0], hosts[1], hosts[2], hosts[3]] + else: + return [hosts[4], hosts[5], hosts[6], hosts[7]] + + cluster.metadata.get_replicas.side_effect = get_replicas + + policy = TokenAwarePolicy(RackAwareRoundRobinPolicy("dc1", "rack1", used_hosts_per_remote_dc=4)) + policy.populate(cluster, hosts) + + for i in range(4): + query = Statement(routing_key=struct.pack('>i', i), keyspace='keyspace_name') + qplan = list(policy.make_query_plan(None, query)) + replicas = get_replicas(None, struct.pack('>i', i)) + + print(qplan) + print(replicas) + + # first should be replica from local rack local dc + assert qplan[0] in replicas + assert qplan[0].datacenter == "dc1" + assert qplan[0].rack == "rack1" + + # second should be replica from remote rack local dc + assert qplan[1] in replicas assert qplan[1].datacenter == "dc1" + assert qplan[1].rack == "rack2" - # then one of the remotes (used_hosts_per_remote_dc is 1, so we - # shouldn't see two remotes) + # third and forth should be replica from the remote dcs + assert qplan[2] in replicas assert qplan[2].datacenter == "dc2" - assert 3 == len(qplan) + assert qplan[3] in replicas + assert qplan[3].datacenter == "dc2" + + assert 8 == len(qplan) class FakeCluster: def __init__(self): From f3812e8da7e223a88d3bc16cfec162b72fae7ab3 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Wed, 5 Nov 2025 13:50:52 -0400 Subject: [PATCH 129/298] Release 3.29.5: changelog, version and documentation --- CHANGELOG.rst | 15 +++++++++++++++ cassandra/__init__.py | 2 +- docs/installation.rst | 4 ++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 29db4ddf97..de57c7b126 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,18 @@ +3.29.5 +====== +November 5, 2025 + +Bug Fixes +--------- +* Update TokenAwarePolicy.make_query_plan to schedule to replicas first (#548) +* Drop _tablets_routing_v1 flag from token-aware policy (#547) +* Fix dc aware and rack aware policies initialization (#546) +* Fix Cluster.metadata_request_timeout and default it from control_connection_timeout (#539) + +Others +------ +* Drop python 3.9 support (#564) + 3.29.4 ====== August 16, 2025 diff --git a/cassandra/__init__.py b/cassandra/__init__.py index 68dd5246bf..633e96d895 100644 --- a/cassandra/__init__.py +++ b/cassandra/__init__.py @@ -23,7 +23,7 @@ def emit(self, record): logging.getLogger('cassandra').addHandler(NullHandler()) -__version_info__ = (3, 29, 4) +__version_info__ = (3, 29, 5) __version__ = '.'.join(map(str, __version_info__)) diff --git a/docs/installation.rst b/docs/installation.rst index 41cd374c7d..b2f6035f11 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -26,7 +26,7 @@ To check if the installation was successful, you can run:: python -c 'import cassandra; print(cassandra.__version__)' -It should print something like "3.29.4". +It should print something like "3.29.5". (*Optional*) Compression Support -------------------------------- @@ -208,7 +208,7 @@ through `Homebrew `_. For example, on Mac OS X:: $ brew install libev -The libev extension can now be built for Windows as of Python driver version 3.29.4. You can +The libev extension can now be built for Windows as of Python driver version 3.29.5. You can install libev using any Windows package manager. For example, to install using `vcpkg `_: $ vcpkg install libev From 27ca050c7ecc72008eded5d2ba9efaeac16071fb Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 6 Nov 2025 07:51:10 -0400 Subject: [PATCH 130/298] docs: update tags to publish --- docs/conf.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 28f025661f..1f3a54659c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,14 +10,30 @@ # -- Global variables # Build documentation for the following tags and branches -TAGS = ['3.21.0-scylla', '3.22.3-scylla', '3.24.8-scylla', '3.25.4-scylla', '3.25.11-scylla', '3.26.9-scylla', '3.28.0-scylla', '3.28.1-scylla', '3.28.2-scylla'] +TAGS = [ + '3.21.0-scylla', + '3.22.3-scylla', + '3.24.8-scylla', + '3.25.4-scylla', + '3.25.11-scylla', + '3.26.9-scylla', + '3.28.0-scylla', + '3.28.1-scylla', + '3.28.2-scylla', + '3.29.0-scylla', + '3.29.1-scylla', + '3.29.2-scylla', + '3.29.3-scylla', + '3.29.4-scylla', + '3.29.5-scylla', +] BRANCHES = ['master'] # Set the latest version. -LATEST_VERSION = '3.28.2-scylla' +LATEST_VERSION = '3.29.5-scylla' # Set which versions are not released yet. UNSTABLE_VERSIONS = ['master'] # Set which versions are deprecated -DEPRECATED_VERSIONS = ['3.21.0-scylla', '3.22.3-scylla', '3.24.8-scylla', '3.25.4-scylla', '3.25.11-scylla', '3.26.9-scylla', '3.28.1-scylla'] +DEPRECATED_VERSIONS = ['3.21.0-scylla', '3.22.3-scylla', '3.24.8-scylla', '3.25.4-scylla', '3.25.11-scylla', '3.26.9-scylla', '3.28.1-scylla', '3.29.1-scylla'] # -- General configuration From 52255563e1d857eb619fd3d3ddb435a0f376ac04 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 6 Nov 2025 10:16:53 -0400 Subject: [PATCH 131/298] unit: fix test_successful_wait_for_connection failure This test can fail with: ``` > elif connection in self._trash: ^^^^^^^^^^^^^^^^^^^^^^^^^ E TypeError: __hash__ method should return an integer not 'MagicMock' ``` --- tests/unit/test_host_connection_pool.py | 22 ++++++++++++---------- tests/unit/util.py | 7 +++++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/tests/unit/test_host_connection_pool.py b/tests/unit/test_host_connection_pool.py index 56f6d942e9..e7b930a990 100644 --- a/tests/unit/test_host_connection_pool.py +++ b/tests/unit/test_host_connection_pool.py @@ -29,6 +29,8 @@ from cassandra.policies import HostDistance, SimpleConvictionPolicy import pytest +from tests.unit.util import HashableMock + LOGGER = logging.getLogger(__name__) @@ -38,13 +40,13 @@ class _PoolTests(unittest.TestCase): uses_single_connection = None def make_session(self): - session = NonCallableMagicMock(spec=Session, keyspace='foobarkeyspace') + session = NonCallableMagicMock(spec=Session, keyspace='foobarkeyspace', _trash=[]) return session def test_borrow_and_return(self): host = Mock(spec=Host, address='ip1') session = self.make_session() - conn = NonCallableMagicMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=False, max_request_id=100) + conn = HashableMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=False, max_request_id=100) session.cluster.connection_factory.return_value = conn pool = self.PoolImpl(host, HostDistance.LOCAL, session) @@ -63,7 +65,7 @@ def test_borrow_and_return(self): def test_failed_wait_for_connection(self): host = Mock(spec=Host, address='ip1') session = self.make_session() - conn = NonCallableMagicMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=False, max_request_id=100) + conn = HashableMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=False, max_request_id=100) session.cluster.connection_factory.return_value = conn pool = self.PoolImpl(host, HostDistance.LOCAL, session) @@ -82,7 +84,7 @@ def test_failed_wait_for_connection(self): def test_successful_wait_for_connection(self): host = Mock(spec=Host, address='ip1') session = self.make_session() - conn = NonCallableMagicMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=False, max_request_id=100, + conn = HashableMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=False, max_request_id=100, lock=Lock()) session.cluster.connection_factory.return_value = conn @@ -107,7 +109,7 @@ def get_second_conn(): def test_spawn_when_at_max(self): host = Mock(spec=Host, address='ip1') session = self.make_session() - conn = NonCallableMagicMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=False, max_request_id=100) + conn = HashableMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=False, max_request_id=100) conn.max_request_id = 100 session.cluster.connection_factory.return_value = conn @@ -131,7 +133,7 @@ def test_spawn_when_at_max(self): def test_return_defunct_connection(self): host = Mock(spec=Host, address='ip1') session = self.make_session() - conn = NonCallableMagicMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=False, + conn = HashableMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=False, max_request_id=100, signaled_error=False) session.cluster.connection_factory.return_value = conn @@ -151,7 +153,7 @@ def test_return_defunct_connection(self): def test_return_defunct_connection_on_down_host(self): host = Mock(spec=Host, address='ip1') session = self.make_session() - conn = NonCallableMagicMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=False, + conn = HashableMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=False, max_request_id=100, signaled_error=False, orphaned_threshold_reached=False) session.cluster.connection_factory.return_value = conn @@ -180,7 +182,7 @@ def test_return_defunct_connection_on_down_host(self): def test_return_closed_connection(self): host = Mock(spec=Host, address='ip1') session = self.make_session() - conn = NonCallableMagicMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=True, max_request_id=100, + conn = HashableMock(spec=Connection, in_flight=0, is_defunct=False, is_closed=True, max_request_id=100, signaled_error=False, orphaned_threshold_reached=False) session.cluster.connection_factory.return_value = conn @@ -247,7 +249,7 @@ def submit(self, fn, *args, **kwargs): return self.cluster.executor.submit(fn, *args, **kwargs) def mock_connection_factory(self, *args, **kwargs): - connection = MagicMock() + connection = HashableMock() connection.is_shutdown = False connection.is_defunct = False connection.is_closed = False @@ -267,7 +269,7 @@ def executor_init(self, *args): LOGGER.info("Testing fast shutdown %d / 20 times", attempt_num + 1) host = MagicMock() host.endpoint = "1.2.3.4" - session = MockSession() + session = self.make_session() pool = HostConnection(host=host, host_distance=HostDistance.REMOTE, session=session) LOGGER.info("Initialized pool %s", pool) diff --git a/tests/unit/util.py b/tests/unit/util.py index 603eb4d9b5..042f07fb99 100644 --- a/tests/unit/util.py +++ b/tests/unit/util.py @@ -9,6 +9,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from unittest.mock import NonCallableMagicMock def check_sequence_consistency(ordered_sequence, equal=False): @@ -28,3 +29,9 @@ def _check_order_consistency(smaller, bigger, equal=False): assert smaller != bigger assert smaller < bigger assert bigger > smaller + + +class HashableMock(NonCallableMagicMock): + + def __hash__(self): + return id(self) \ No newline at end of file From 78f554236f0a7f1a6290e1616b1788feab6d20fe Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 13 Nov 2025 14:04:36 -0400 Subject: [PATCH 132/298] compression: better handle configuration problems 1. Throw an exception on cluster initialization when concrete compression is configured, but library is not available 2. Log an error only once at cluster initialization if compression is True, but no library is available. 3. Throw an exception on cluster initialization if compression is something else but string and bool --- cassandra/cluster.py | 26 ++++++++++++++++++++++---- cassandra/connection.py | 15 ++++++++------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 5822a23aa9..8370dde9d1 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -29,7 +29,7 @@ from itertools import groupby, count, chain import json import logging -from typing import Optional +from typing import Optional, Union from warnings import warn from random import random import re @@ -51,7 +51,7 @@ from cassandra.connection import (ConnectionException, ConnectionShutdown, ConnectionHeartbeat, ProtocolVersionUnsupported, EndPoint, DefaultEndPoint, DefaultEndPointFactory, - SniEndPointFactory, ConnectionBusy) + SniEndPointFactory, ConnectionBusy, locally_supported_compressions) from cassandra.cqltypes import UserType import cassandra.cqltypes as types from cassandra.encoder import Encoder @@ -686,7 +686,7 @@ class Cluster(object): Used for testing new protocol features incrementally before the new version is complete. """ - compression = True + compression: Union[bool, str] = True """ Controls compression for communications between the driver and Cassandra. If left as the default of :const:`True`, either lz4 or snappy compression @@ -1173,7 +1173,7 @@ def token_metadata_enabled(self, enabled): def __init__(self, contact_points=_NOT_SET, port=9042, - compression=True, + compression: Union[bool, str] = True, auth_provider=None, load_balancing_policy=None, reconnection_policy=None, @@ -1302,6 +1302,24 @@ def __init__(self, self._resolve_hostnames() + if isinstance(compression, bool): + if compression and not locally_supported_compressions: + log.error( + "Compression is enabled, but no compression libraries are available. " + "Disabling compression, consider installing one of the Python packages: lz4 and/or python-snappy." + ) + compression = False + elif isinstance(compression, str): + if not locally_supported_compressions.get(compression): + raise ValueError( + "Compression '%s' was requested, but it is not available. " + "Consider installing the corresponding Python package." % compression + ) + else: + raise TypeError( + "The 'compression' option must be either a string (e.g., 'lz4' or 'snappy') " + "or a boolean (True to enable any available compression, False to disable it)." + ) self.compression = compression if protocol_version is not _NOT_SET: diff --git a/cassandra/connection.py b/cassandra/connection.py index 39baeea884..9ac02c9776 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -28,7 +28,7 @@ import weakref import random import itertools -from typing import Optional +from typing import Optional, Union from cassandra.application_info import ApplicationInfoBase from cassandra.protocol_features import ProtocolFeatures @@ -679,7 +679,7 @@ class Connection(object): protocol_version = ProtocolVersion.MAX_SUPPORTED keyspace = None - compression = True + compression: Union[bool, str] = True _compression_type = None compressor = None decompressor = None @@ -760,7 +760,7 @@ def _iobuf(self): return self._io_buffer.io_buffer def __init__(self, host='127.0.0.1', port=9042, authenticator=None, - ssl_options=None, sockopts=None, compression=True, + ssl_options=None, sockopts=None, compression: Union[bool, str] = True, cql_version=None, protocol_version=ProtocolVersion.MAX_SUPPORTED, is_control_connection=False, user_type_map=None, connect_timeout=None, allow_beta_protocol_version=False, no_compact=False, ssl_context=None, owning_pool=None, shard_id=None, total_shards=None, @@ -1383,10 +1383,11 @@ def _handle_options_response(self, options_response): overlap = (set(locally_supported_compressions.keys()) & set(remote_supported_compressions)) if len(overlap) == 0: - log.error("No available compression types supported on both ends." - " locally supported: %r. remotely supported: %r", - locally_supported_compressions.keys(), - remote_supported_compressions) + if locally_supported_compressions: + log.error("No available compression types supported on both ends." + " locally supported: %r. remotely supported: %r", + locally_supported_compressions.keys(), + remote_supported_compressions) else: compression_type = None if isinstance(self.compression, str): From dfa6a2ac8ae53a1a88fac97377013d08d8038e3e Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 16 Nov 2025 09:28:06 -0400 Subject: [PATCH 133/298] tests: drop `sure` package This package colides with twisted implementation, which ends up in the following error: ``` File "/home/runner/work/python-driver/python-driver/.venv/lib/python3.11/site-packages/sure/__init__.py", line 1084, in _new_dir patched = [x for x in old_dir(obj[0]) if isinstance(getattr(obj[0], x, None), AssertionBuilder)] File "/home/runner/work/python-driver/python-driver/.venv/lib/python3.11/site-packages/sure/__init__.py", line 1084, in patched = [x for x in old_dir(obj[0]) if isinstance(getattr(obj[0], x, None), AssertionBuilder)] File "/home/runner/work/python-driver/python-driver/.venv/lib/python3.11/site-packages/zope/interface/ro.py", line 224, in __get__ for k in dir(klass): File "/home/runner/work/python-driver/python-driver/.venv/lib/python3.11/site-packages/sure/__init__.py", line 1084, in _new_dir patched = [x for x in old_dir(obj[0]) if isinstance(getattr(obj[0], x, None), AssertionBuilder)] builtins.RecursionError: maximum recursion depth exceeded while calling a Python object ``` --- pyproject.toml | 1 - tests/integration/cqlengine/test_timestamp.py | 46 ++++++++----------- .../integration/standard/test_use_keyspace.py | 2 +- tests/unit/io/test_eventletreactor.py | 4 +- tests/unit/test_shard_aware.py | 2 +- 5 files changed, 23 insertions(+), 32 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6f4e4a8f6e..9a3cd40b5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,6 @@ dev = [ "pytest", "PyYAML", "pytz", - "sure", "scales", "pure-sasl", "twisted[tls]", diff --git a/tests/integration/cqlengine/test_timestamp.py b/tests/integration/cqlengine/test_timestamp.py index 1b0e3d6b9d..7b10ad578f 100644 --- a/tests/integration/cqlengine/test_timestamp.py +++ b/tests/integration/cqlengine/test_timestamp.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import re from datetime import timedelta, datetime from unittest import mock -import sure from uuid import uuid4 from cassandra.cqlengine import columns @@ -47,7 +47,7 @@ def test_batch_is_included(self): with BatchQuery(timestamp=timedelta(seconds=30)) as b: TestTimestampModel.batch(b).create(count=1) - "USING TIMESTAMP".should.be.within(m.call_args[0][0].query_string) + assert "USING TIMESTAMP" in m.call_args[0][0].query_string class CreateWithTimestampTest(BaseTimestampTest): @@ -58,37 +58,34 @@ def test_batch(self): TestTimestampModel.timestamp(timedelta(seconds=10)).batch(b).create(count=1) query = m.call_args[0][0].query_string - - query.should.match(r"INSERT.*USING TIMESTAMP") - query.should_not.match(r"TIMESTAMP.*INSERT") + assert re.search(r"INSERT.*USING TIMESTAMP", query) + assert not re.search(r"TIMESTAMP.*INSERT", query) def test_timestamp_not_included_on_normal_create(self): with mock.patch.object(self.session, "execute") as m: TestTimestampModel.create(count=2) - "USING TIMESTAMP".shouldnt.be.within(m.call_args[0][0].query_string) + assert "USING TIMESTAMP" not in m.call_args[0][0].query_string def test_timestamp_is_set_on_model_queryset(self): delta = timedelta(seconds=30) tmp = TestTimestampModel.timestamp(delta) - tmp._timestamp.should.equal(delta) + assert tmp._timestamp == delta def test_non_batch_syntax_integration(self): tmp = TestTimestampModel.timestamp(timedelta(seconds=30)).create(count=1) - tmp.should.be.ok + assert tmp def test_non_batch_syntax_with_tll_integration(self): tmp = TestTimestampModel.timestamp(timedelta(seconds=30)).ttl(30).create(count=1) - tmp.should.be.ok + assert tmp def test_non_batch_syntax_unit(self): with mock.patch.object(self.session, "execute") as m: TestTimestampModel.timestamp(timedelta(seconds=30)).create(count=1) - query = m.call_args[0][0].query_string - - "USING TIMESTAMP".should.be.within(query) + assert "USING TIMESTAMP" in m.call_args[0][0].query_string def test_non_batch_syntax_with_ttl_unit(self): @@ -96,9 +93,7 @@ def test_non_batch_syntax_with_ttl_unit(self): TestTimestampModel.timestamp(timedelta(seconds=30)).ttl(30).create( count=1) - query = m.call_args[0][0].query_string - - query.should.match(r"USING TTL \d* AND TIMESTAMP") + assert re.search(r"USING TTL \d* AND TIMESTAMP", m.call_args[0][0].query_string) class UpdateWithTimestampTest(BaseTimestampTest): @@ -112,15 +107,14 @@ def test_instance_update_includes_timestamp_in_query(self): with mock.patch.object(self.session, "execute") as m: self.instance.timestamp(timedelta(seconds=30)).update(count=2) - "USING TIMESTAMP".should.be.within(m.call_args[0][0].query_string) + assert "USING TIMESTAMP" in m.call_args[0][0].query_string def test_instance_update_in_batch(self): with mock.patch.object(self.session, "execute") as m: with BatchQuery() as b: self.instance.batch(b).timestamp(timedelta(seconds=30)).update(count=2) - query = m.call_args[0][0].query_string - "USING TIMESTAMP".should.be.within(query) + assert "USING TIMESTAMP" in m.call_args[0][0].query_string class DeleteWithTimestampTest(BaseTimestampTest): @@ -132,7 +126,7 @@ def test_non_batch(self): uid = uuid4() tmp = TestTimestampModel.create(id=uid, count=1) - TestTimestampModel.get(id=uid).should.be.ok + assert TestTimestampModel.get(id=uid) tmp.timestamp(timedelta(seconds=5)).delete() @@ -146,15 +140,15 @@ def test_non_batch(self): # calling .timestamp sets the TS on the model tmp.timestamp(timedelta(seconds=5)) - tmp._timestamp.should.be.ok + assert tmp._timestamp # calling save clears the set timestamp tmp.save() - tmp._timestamp.shouldnt.be.ok + assert not tmp._timestamp tmp.timestamp(timedelta(seconds=5)) tmp.update() - tmp._timestamp.shouldnt.be.ok + assert not tmp._timestamp def test_blind_delete(self): """ @@ -163,7 +157,7 @@ def test_blind_delete(self): uid = uuid4() tmp = TestTimestampModel.create(id=uid, count=1) - TestTimestampModel.get(id=uid).should.be.ok + assert TestTimestampModel.get(id=uid) TestTimestampModel.objects(id=uid).timestamp(timedelta(seconds=5)).delete() @@ -182,7 +176,7 @@ def test_blind_delete_with_datetime(self): uid = uuid4() tmp = TestTimestampModel.create(id=uid, count=1) - TestTimestampModel.get(id=uid).should.be.ok + assert TestTimestampModel.get(id=uid) plus_five_seconds = datetime.now() + timedelta(seconds=5) @@ -200,11 +194,9 @@ def test_delete_in_the_past(self): uid = uuid4() tmp = TestTimestampModel.create(id=uid, count=1) - TestTimestampModel.get(id=uid).should.be.ok + assert TestTimestampModel.get(id=uid) # delete the in past, should not affect the object created above TestTimestampModel.objects(id=uid).timestamp(timedelta(seconds=-60)).delete() TestTimestampModel.get(id=uid) - - diff --git a/tests/integration/standard/test_use_keyspace.py b/tests/integration/standard/test_use_keyspace.py index 42cf03a553..72ddbf9ef4 100644 --- a/tests/integration/standard/test_use_keyspace.py +++ b/tests/integration/standard/test_use_keyspace.py @@ -7,7 +7,7 @@ except ImportError: import unittest # noqa -from mock import patch +from unittest.mock import patch from cassandra.connection import Connection from cassandra.cluster import Cluster diff --git a/tests/unit/io/test_eventletreactor.py b/tests/unit/io/test_eventletreactor.py index e49f2459c1..d3962196a4 100644 --- a/tests/unit/io/test_eventletreactor.py +++ b/tests/unit/io/test_eventletreactor.py @@ -14,12 +14,12 @@ import unittest -from mock import patch + +from unittest.mock import patch from tests.unit.io.utils import TimerTestMixin from tests import notpypy, EVENT_LOOP_MANAGER -from unittest.mock import patch try: from eventlet import monkey_patch diff --git a/tests/unit/test_shard_aware.py b/tests/unit/test_shard_aware.py index 8b34eb2578..0d13a243ab 100644 --- a/tests/unit/test_shard_aware.py +++ b/tests/unit/test_shard_aware.py @@ -18,7 +18,7 @@ import unittest # noqa import logging -from mock import MagicMock +from unittest.mock import MagicMock from concurrent.futures import ThreadPoolExecutor from cassandra.cluster import ShardAwareOptions From c5efea9e132d9bda9db4daf8b27139827603d65e Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 14 Nov 2025 15:52:01 -0400 Subject: [PATCH 134/298] Remove serverless tests Serverless got removed from the cloud and not going to return back. Let's drop tests for it. --- .../integration/standard/test_scylla_cloud.py | 129 ------------------ 1 file changed, 129 deletions(-) delete mode 100644 tests/integration/standard/test_scylla_cloud.py diff --git a/tests/integration/standard/test_scylla_cloud.py b/tests/integration/standard/test_scylla_cloud.py deleted file mode 100644 index 5679d959bb..0000000000 --- a/tests/integration/standard/test_scylla_cloud.py +++ /dev/null @@ -1,129 +0,0 @@ -import json -import logging -import os.path -from unittest import TestCase -from ccmlib.utils.ssl_utils import generate_ssl_stores -from ccmlib.utils.sni_proxy import refresh_certs, start_sni_proxy, create_cloud_config, NodeInfo - -from cassandra import DependencyException -from cassandra.policies import TokenAwarePolicy, RoundRobinPolicy, ConstantReconnectionPolicy -from tests.integration import use_cluster, PROTOCOL_VERSION -from cassandra.cluster import Cluster, TwistedConnection - - -supported_connection_classes = [TwistedConnection] - -try: - from cassandra.io.libevreactor import LibevConnection - supported_connection_classes += [LibevConnection] -except DependencyException: - pass - - -try: - from cassandra.io.asyncorereactor import AsyncoreConnection - supported_connection_classes += [AsyncoreConnection] -except DependencyException: - pass - -#from cassandra.io.geventreactor import GeventConnection -#from cassandra.io.eventletreactor import EventletConnection -#from cassandra.io.asyncioreactor import AsyncioConnection - -# need to run them with specific configuration like `gevent.monkey.patch_all()` or under async functions -# unsupported_connection_classes = [GeventConnection, AsyncioConnection, EventletConnection] -LOGGER = logging.getLogger(__name__) - - -def get_cluster_info(cluster, port=9142): - session = Cluster( - contact_points=list(map(lambda node: node.address(), cluster.nodelist())), protocol_version=PROTOCOL_VERSION, - load_balancing_policy=TokenAwarePolicy(RoundRobinPolicy()), - reconnection_policy=ConstantReconnectionPolicy(5) - ).connect() - - nodes_info = [] - - for row in session.execute('select host_id, broadcast_address, data_center from system.local'): - if row[0] and row[1]: - nodes_info.append(NodeInfo(address=row[1], - port=port, - host_id=row[0], - data_center=row[2])) - - for row in session.execute('select host_id, broadcast_address, data_center from system.local'): - nodes_info.append(NodeInfo(address=row[1], - port=port, - host_id=row[0], - data_center=row[2])) - - return nodes_info - - -class ScyllaCloudConfigTests(TestCase): - def start_cluster_with_proxy(self): - ccm_cluster = self.ccm_cluster - generate_ssl_stores(ccm_cluster.get_path()) - ssl_port = 9142 - sni_port = 443 - ccm_cluster.set_configuration_options(dict( - client_encryption_options= - dict(require_client_auth=True, - truststore=os.path.join(ccm_cluster.get_path(), 'ccm_node.cer'), - certificate=os.path.join(ccm_cluster.get_path(), 'ccm_node.pem'), - keyfile=os.path.join(ccm_cluster.get_path(), 'ccm_node.key'), - enabled=True), - native_transport_port_ssl=ssl_port)) - - ccm_cluster._update_config() - - ccm_cluster.start(wait_for_binary_proto=True, wait_other_notice=True) - - nodes_info = get_cluster_info(ccm_cluster, port=ssl_port) - refresh_certs(ccm_cluster, nodes_info) - - docker_id, listen_address, listen_port = \ - start_sni_proxy(ccm_cluster.get_path(), nodes_info=nodes_info, listen_port=sni_port) - ccm_cluster.sni_proxy_docker_ids = [docker_id] - ccm_cluster.sni_proxy_listen_port = listen_port - ccm_cluster._update_config() - - config_data_yaml, config_path_yaml = create_cloud_config(ccm_cluster.get_path(), - port=listen_port, address=listen_address, - nodes_info=nodes_info) - return config_data_yaml, config_path_yaml - - def test_1_node_cluster(self): - self.ccm_cluster = use_cluster("sni_proxy", [1], start=False) - config_data_yaml, config_path_yaml = self.start_cluster_with_proxy() - - for config in [config_path_yaml, config_data_yaml]: - for connection_class in supported_connection_classes: - logging.warning('testing with class: %s', connection_class.__name__) - cluster = Cluster(scylla_cloud=config, connection_class=connection_class) - try: - with cluster.connect() as session: - res = session.execute("SELECT * FROM system.local WHERE key='local'") - assert res.all() - - assert len(cluster.metadata._hosts) == 1 - assert len(cluster.metadata._host_id_by_endpoint) == 1 - finally: - cluster.shutdown() - - def test_3_node_cluster(self): - self.ccm_cluster = use_cluster("sni_proxy", [3], start=False) - config_data_yaml, config_path_yaml = self.start_cluster_with_proxy() - - for config in [config_path_yaml, config_data_yaml]: - for connection_class in supported_connection_classes: - logging.warning('testing with class: %s', connection_class.__name__) - cluster = Cluster(scylla_cloud=config, connection_class=connection_class) - try: - with cluster.connect() as session: - res = session.execute("SELECT * FROM system.local WHERE key='local'") - assert res.all() - assert len(cluster.metadata._hosts) == 3 - assert len(cluster.metadata._host_id_by_endpoint) == 3 - finally: - cluster.shutdown() From 77e2e264fdaabaa094aeb005d8d23656fd3edccb Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 14 Nov 2025 15:57:57 -0400 Subject: [PATCH 135/298] Remove serverless feature Drop ScyllaCloud/serverless feature, it was dropped from cloud and not comming back. --- cassandra/cluster.py | 19 +----- cassandra/scylla/cloud.py | 139 -------------------------------------- 2 files changed, 1 insertion(+), 157 deletions(-) delete mode 100644 cassandra/scylla/cloud.py diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 8370dde9d1..a184d40940 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -95,7 +95,6 @@ GraphSON3Serializer) from cassandra.datastax.graph.query import _request_timeout_key, _GraphSONContextRowFactory from cassandra.datastax import cloud as dscloud -from cassandra.scylla.cloud import CloudConfiguration from cassandra.application_info import ApplicationInfoBase try: @@ -1238,23 +1237,7 @@ def __init__(self, self.connection_class = connection_class if scylla_cloud is not None: - if contact_points is not _NOT_SET or ssl_context or ssl_options: - raise ValueError("contact_points, ssl_context, and ssl_options " - "cannot be specified with a scylla cloud configuration") - if shard_aware_options and not shard_aware_options.disable_shardaware_port: - raise ValueError("shard_aware_options.disable_shardaware_port=False " - "cannot be specified with a scylla cloud configuration") - uses_twisted = TwistedConnection and issubclass(self.connection_class, TwistedConnection) - uses_eventlet = EventletConnection and issubclass(self.connection_class, EventletConnection) - - scylla_cloud_config = CloudConfiguration.create(scylla_cloud, pyopenssl=uses_twisted or uses_eventlet, - endpoint_factory=endpoint_factory) - ssl_context = scylla_cloud_config.ssl_context - endpoint_factory = scylla_cloud_config.endpoint_factory - contact_points = scylla_cloud_config.contact_points - ssl_options = scylla_cloud_config.ssl_options - auth_provider = scylla_cloud_config.auth_provider - shard_aware_options = ShardAwareOptions(shard_aware_options, disable_shardaware_port=True) + raise NotImplementedError("scylla_cloud was removed and not supported anymore") if cloud is not None: self.cloud = cloud diff --git a/cassandra/scylla/cloud.py b/cassandra/scylla/cloud.py deleted file mode 100644 index c3298b199a..0000000000 --- a/cassandra/scylla/cloud.py +++ /dev/null @@ -1,139 +0,0 @@ -# Copyright ScyllaDB, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -import ssl -import tempfile -import base64 -from ssl import SSLContext -from contextlib import contextmanager -from itertools import islice - -import yaml - -from cassandra.connection import SniEndPointFactory -from cassandra.auth import AuthProvider, PlainTextAuthProvider - - -@contextmanager -def file_or_memory(path=None, data=None): - # since we can't read keys/cert from memory yet - # see https://github.com/python/cpython/pull/2449 which isn't accepted and PEP-543 that was withdrawn - # so we use temporary file to load the key - if data: - with tempfile.NamedTemporaryFile(mode="wb") as f: - d = base64.b64decode(data) - f.write(d) - if not d.endswith(b"\n"): - f.write(b"\n") - - f.flush() - yield f.name - - if path: - yield path - - -def nth(iterable, n, default=None): - "Returns the nth item or a default value" - return next(islice(iterable, n, None), default) - - -class CloudConfiguration: - endpoint_factory: SniEndPointFactory - contact_points: list - auth_provider: AuthProvider = None - ssl_options: dict - ssl_context: SSLContext - skip_tls_verify: bool - - def __init__(self, configuration_file, pyopenssl=False, endpoint_factory=None): - cloud_config = yaml.safe_load(open(configuration_file)) - - self.current_context = cloud_config['contexts'][cloud_config['currentContext']] - self.data_centers = cloud_config['datacenters'] - self.current_data_center = self.data_centers[self.current_context['datacenterName']] - self.auth_info = cloud_config['authInfos'][self.current_context['authInfoName']] - self.ssl_options = {} - self.skip_tls_verify = self.current_data_center.get('insecureSkipTlsVerify', False) - self.ssl_context = self.create_pyopenssl_context() if pyopenssl else self.create_ssl_context() - - proxy_address, port, node_domain = self.get_server(self.current_data_center) - - if not endpoint_factory: - endpoint_factory = SniEndPointFactory(proxy_address, port=int(port), node_domain=node_domain) - else: - assert isinstance(endpoint_factory, SniEndPointFactory) - self.endpoint_factory = endpoint_factory - - username, password = self.auth_info.get('username'), self.auth_info.get('password') - if username and password: - self.auth_provider = PlainTextAuthProvider(username, password) - - @property - def contact_points(self): - _contact_points = [] - for data_center in self.data_centers.values(): - _, _, node_domain = self.get_server(data_center) - _contact_points.append(self.endpoint_factory.create_from_sni(node_domain)) - return _contact_points - - def get_server(self, data_center): - address = data_center.get('server') - address = address.split(":") - port = nth(address, 1, default=9142) - address = nth(address, 0) - node_domain = data_center.get('nodeDomain') - assert address and port and node_domain, "server or nodeDomain are missing" - return address, port, node_domain - - def create_ssl_context(self): - ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT) - ssl_context.verify_mode = ssl.CERT_NONE if self.skip_tls_verify else ssl.CERT_REQUIRED - for data_center in self.data_centers.values(): - with file_or_memory(path=data_center.get('certificateAuthorityPath'), - data=data_center.get('certificateAuthorityData')) as cafile: - ssl_context.load_verify_locations(cadata=open(cafile).read()) - with file_or_memory(path=self.auth_info.get('clientCertificatePath'), - data=self.auth_info.get('clientCertificateData')) as certfile, \ - file_or_memory(path=self.auth_info.get('clientKeyPath'), data=self.auth_info.get('clientKeyData')) as keyfile: - ssl_context.load_cert_chain(keyfile=keyfile, - certfile=certfile) - - return ssl_context - - def create_pyopenssl_context(self): - try: - from OpenSSL import SSL - except ImportError as e: - raise ImportError( - "PyOpenSSL must be installed to connect to scylla-cloud with the Eventlet or Twisted event loops") \ - .with_traceback(e.__traceback__) - ssl_context = SSL.Context(SSL.TLS_METHOD) - ssl_context.set_verify(SSL.VERIFY_PEER, callback=lambda _1, _2, _3, _4, ok: True if self.skip_tls_verify else ok) - for data_center in self.data_centers.values(): - with file_or_memory(path=data_center.get('certificateAuthorityPath'), - data=data_center.get('certificateAuthorityData')) as cafile: - ssl_context.load_verify_locations(cafile) - with file_or_memory(path=self.auth_info.get('clientCertificatePath'), - data=self.auth_info.get('clientCertificateData')) as certfile, \ - file_or_memory(path=self.auth_info.get('clientKeyPath'), data=self.auth_info.get('clientKeyData')) as keyfile: - ssl_context.use_privatekey_file(keyfile) - ssl_context.use_certificate_file(certfile) - - return ssl_context - - @classmethod - def create(cls, configuration_file, pyopenssl=False, endpoint_factory=None): - return cls(configuration_file, pyopenssl=pyopenssl, endpoint_factory=endpoint_factory) From 6eaff9ccb366e35bfaa767f45abcb52186fca95c Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 14 Nov 2025 08:56:25 -0400 Subject: [PATCH 136/298] test: temporary lock pytest on 9.0.0 or earlier There is a bug in pytest that makes one partuclar xfail test to fail on cicd: ``` FAILED tests/integration/standard/test_application_info.py::ApplicationInfoTest::test_create_session_and_check_system_views_clients - [XPASS(strict)] #scylladb/scylla-enterprise#5467 - not released yet ``` Unlock it when it get fixed --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9a3cd40b5a..1558b2832c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ compress-snappy = ['python-snappy'] [dependency-groups] dev = [ - "pytest", + "pytest~=8.0", "PyYAML", "pytz", "scales", From 2509b1c4d0649398edaf65a110d3743d70f355f3 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 17 Nov 2025 23:54:54 -0400 Subject: [PATCH 137/298] Fix dict handling in pool and metrics There are certain rules you need to following in the cases when dicts are modified and read in parallel. Otherwise you end up with `RuntimeError: dictionary changed size during iteration`. Rules are the following: 1. Any iteration over items or keys, needs to be done over a snapshot, i.e. `list()` or `set()` 2. Avoid unnecessary iterations like `len(d.keys())`, you can replace them with `len(d)` This commit fixes code to match these rules in the `pool.py` and `metrics.py` --- cassandra/metrics.py | 4 ++-- cassandra/pool.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cassandra/metrics.py b/cassandra/metrics.py index 223b0c7c6e..abfc863b55 100644 --- a/cassandra/metrics.py +++ b/cassandra/metrics.py @@ -134,9 +134,9 @@ def __init__(self, cluster_proxy): scales.Stat('known_hosts', lambda: len(cluster_proxy.metadata.all_hosts())), scales.Stat('connected_to', - lambda: len(set(chain.from_iterable(s._pools.keys() for s in cluster_proxy.sessions)))), + lambda: len(set(chain.from_iterable(list(s._pools.keys()) for s in cluster_proxy.sessions)))), scales.Stat('open_connections', - lambda: sum(sum(p.open_count for p in s._pools.values()) for s in cluster_proxy.sessions))) + lambda: sum(sum(p.open_count for p in list(s._pools.values())) for s in cluster_proxy.sessions))) # TODO, to be removed in 4.0 # /cassandra contains the metrics of the first cluster registered diff --git a/cassandra/pool.py b/cassandra/pool.py index 70abb6eccc..b8a8ef7493 100644 --- a/cassandra/pool.py +++ b/cassandra/pool.py @@ -492,7 +492,7 @@ def _get_connection_for_routing_key(self, routing_key=None, keyspace=None, table "Connection to shard_id=%i reached orphaned stream limit, replacing on host %s (%s/%i)", shard_id, self.host, - len(self._connections.keys()), + len(self._connections), self.host.sharding_info.shards_count ) elif shard_id not in self._connecting: @@ -503,7 +503,7 @@ def _get_connection_for_routing_key(self, routing_key=None, keyspace=None, table "Trying to connect to missing shard_id=%i on host %s (%s/%i)", shard_id, self.host, - len(self._connections.keys()), + len(self._connections), self.host.sharding_info.shards_count ) @@ -607,7 +607,7 @@ def _replace(self, connection): log.debug("Replacing connection (%s) to %s", id(connection), self.host) try: - if connection.features.shard_id in self._connections.keys(): + if connection.features.shard_id in self._connections: del self._connections[connection.features.shard_id] if self.host.sharding_info and not self._session.cluster.shard_aware_options.disable: self._connecting.add(connection.features.shard_id) @@ -759,7 +759,7 @@ def _open_connection_to_missing_shard(self, shard_id): with self._lock: is_shutdown = self.is_shutdown if not is_shutdown: - if conn.features.shard_id in self._connections.keys(): + if conn.features.shard_id in self._connections: # Move the current connection to the trash and use the new one from now on old_conn = self._connections[conn.features.shard_id] log.debug( @@ -804,7 +804,7 @@ def _open_connection_to_missing_shard(self, shard_id): num_missing_or_needing_replacement = self.num_missing_or_needing_replacement log.debug( "Connected to %s/%i shards on host %s (%i missing or needs replacement)", - len(self._connections.keys()), + len(self._connections), self.host.sharding_info.shards_count, self.host, num_missing_or_needing_replacement @@ -816,7 +816,7 @@ def _open_connection_to_missing_shard(self, shard_id): len(self._excess_connections) ) self._close_excess_connections() - elif self.host.sharding_info.shards_count == len(self._connections.keys()) and self.num_missing_or_needing_replacement == 0: + elif self.host.sharding_info.shards_count == len(self._connections) and self.num_missing_or_needing_replacement == 0: log.debug( "All shards are already covered, closing newly opened excess connection %s for host %s", id(self), @@ -917,7 +917,7 @@ def get_state(self): @property def num_missing_or_needing_replacement(self): return self.host.sharding_info.shards_count \ - - sum(1 for c in self._connections.values() if not c.orphaned_threshold_reached) + - sum(1 for c in list(self._connections.values()) if not c.orphaned_threshold_reached) @property def open_count(self): From 3385e6fcaea90b4b7ddfe97bb862226f8c0312fa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:07:01 +0000 Subject: [PATCH 138/298] chore(deps): update actions/checkout action to v6 --- .github/workflows/docs-pages.yml | 2 +- .github/workflows/docs-pr.yml | 2 +- .github/workflows/integration-tests.yml | 2 +- .github/workflows/lib-build-and-push.yml | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index dde17cfa53..f017201c7c 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ github.event.repository.default_branch }} persist-credentials: false diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index 2ee44bcf74..b5651c8159 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: persist-credentials: false fetch-depth: 0 diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index d2bd01703d..b9e9b3956e 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -52,7 +52,7 @@ jobs: event_loop_manager: "asyncore" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK ${{ matrix.java-version }} uses: actions/setup-java@v5 diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index 102a9f68a4..e41a06d78e 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -83,11 +83,11 @@ jobs: include: ${{ fromJson(needs.prepare-matrix.outputs.matrix) }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Checkout tag ${{ inputs.target_tag }} if: inputs.target_tag != '' - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ inputs.target_tag }} @@ -162,7 +162,7 @@ jobs: name: Build source distribution runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Install uv uses: astral-sh/setup-uv@v7 From 5309326526031c0584faf4bc964e5dfb901f82fa Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 16 Oct 2025 12:42:37 -0400 Subject: [PATCH 139/298] Add python 3.14 support 1. Add integration tests to CICD 2. Update documentation and pyproject.toml --- .github/workflows/integration-tests.yml | 14 +++++++++++--- README.rst | 2 +- docs/index.rst | 2 +- docs/installation.rst | 2 +- pyproject.toml | 2 ++ 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index b9e9b3956e..210c2d4e2b 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -43,13 +43,17 @@ jobs: fail-fast: false matrix: java-version: [8] - python-version: ["3.11", "3.12", "3.13"] + python-version: ["3.11", "3.12", "3.13", "3.14", "3.14t"] event_loop_manager: ["libev", "asyncio", "asyncore"] exclude: - python-version: "3.12" event_loop_manager: "asyncore" - python-version: "3.13" event_loop_manager: "asyncore" + - python-version: "3.14" + event_loop_manager: "asyncore" + - python-version: "3.14t" + event_loop_manager: "asyncore" steps: - uses: actions/checkout@v6 @@ -81,7 +85,11 @@ jobs: uv run ccm remove - name: Test with pytest + env: + EVENT_LOOP_MANAGER: ${{ matrix.event_loop_manager }} + PROTOCOL_VERSION: 4 run: | - export EVENT_LOOP_MANAGER=${{ matrix.event_loop_manager }} - export PROTOCOL_VERSION=4 + if [[ "${{ matrix.python-version }}" =~ t$ ]]; then + export PYTHON_GIL=0 + fi uv run pytest tests/integration/standard/ tests/integration/cqlengine/ diff --git a/README.rst b/README.rst index e71af2f47b..84ceb443a3 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ Scylla Enterprise (2018.1.x+) using exclusively Cassandra's binary protocol and .. image:: https://github.com/scylladb/python-driver/actions/workflows/integration-tests.yml/badge.svg?branch=master :target: https://github.com/scylladb/python-driver/actions/workflows/integration-tests.yml?query=event%3Apush+branch%3Amaster -The driver supports Python versions 3.10-3.13. +The driver supports Python versions 3.10-3.14. .. **Note:** This driver does not support big-endian systems. diff --git a/docs/index.rst b/docs/index.rst index 3192a17102..cd137917d9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,7 +4,7 @@ A Python client driver for `Scylla `_. This driver works exclusively with the Cassandra Query Language v3 (CQL3) and Cassandra's native protocol. -The driver supports Python 3.10-3.13. +The driver supports Python 3.10-3.14. This driver is open source under the `Apache v2 License `_. diff --git a/docs/installation.rst b/docs/installation.rst index b2f6035f11..904e1fa2b0 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -3,7 +3,7 @@ Installation Supported Platforms ------------------- -Python versions 3.10-3.13 are supported. Both CPython (the standard Python +Python versions 3.10-3.14 are supported. Both CPython (the standard Python implementation) and `PyPy `_ are supported and tested. Linux, OSX, and Windows are supported. diff --git a/pyproject.toml b/pyproject.toml index 1558b2832c..f0dffa23c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,8 @@ classifiers = [ 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', + 'Programming Language :: Python :: Free Threading :: 2 - Beta', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', From 77e0492b608860b36cbfd866ee15a1df2e4bf12b Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Thu, 13 Nov 2025 11:48:13 +0100 Subject: [PATCH 140/298] Perform LWT metadata protocol handshake --- cassandra/lwt_info.py | 21 +++++++++++++++++++++ cassandra/protocol_features.py | 27 +++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 cassandra/lwt_info.py diff --git a/cassandra/lwt_info.py b/cassandra/lwt_info.py new file mode 100644 index 0000000000..45750dbcec --- /dev/null +++ b/cassandra/lwt_info.py @@ -0,0 +1,21 @@ +# Copyright 2020 ScyllaDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class _LwtInfo: + """ + Holds LWT-related information parsed from the server's supported features. + """ + + def __init__(self, lwt_meta_bit_mask): + self.lwt_meta_bit_mask = lwt_meta_bit_mask diff --git a/cassandra/protocol_features.py b/cassandra/protocol_features.py index 4eb7019f84..877998be7d 100644 --- a/cassandra/protocol_features.py +++ b/cassandra/protocol_features.py @@ -1,10 +1,13 @@ import logging from cassandra.shard_info import _ShardingInfo +from cassandra.lwt_info import _LwtInfo log = logging.getLogger(__name__) +LWT_ADD_METADATA_MARK = "SCYLLA_LWT_ADD_METADATA_MARK" +LWT_OPTIMIZATION_META_BIT_MASK = "LWT_OPTIMIZATION_META_BIT_MASK" RATE_LIMIT_ERROR_EXTENSION = "SCYLLA_RATE_LIMIT_ERROR" TABLETS_ROUTING_V1 = "TABLETS_ROUTING_V1" @@ -13,19 +16,22 @@ class ProtocolFeatures(object): shard_id = 0 sharding_info = None tablets_routing_v1 = False + lwt_info = None - def __init__(self, rate_limit_error=None, shard_id=0, sharding_info=None, tablets_routing_v1=False): + def __init__(self, rate_limit_error=None, shard_id=0, sharding_info=None, tablets_routing_v1=False, lwt_info=None): self.rate_limit_error = rate_limit_error self.shard_id = shard_id self.sharding_info = sharding_info self.tablets_routing_v1 = tablets_routing_v1 + self.lwt_info = lwt_info @staticmethod def parse_from_supported(supported): rate_limit_error = ProtocolFeatures.maybe_parse_rate_limit_error(supported) shard_id, sharding_info = ProtocolFeatures.parse_sharding_info(supported) tablets_routing_v1 = ProtocolFeatures.parse_tablets_info(supported) - return ProtocolFeatures(rate_limit_error, shard_id, sharding_info, tablets_routing_v1) + lwt_info = ProtocolFeatures.parse_lwt_info(supported) + return ProtocolFeatures(rate_limit_error, shard_id, sharding_info, tablets_routing_v1, lwt_info) @staticmethod def maybe_parse_rate_limit_error(supported): @@ -49,6 +55,8 @@ def add_startup_options(self, options): options[RATE_LIMIT_ERROR_EXTENSION] = "" if self.tablets_routing_v1: options[TABLETS_ROUTING_V1] = "" + if self.lwt_info is not None: + options[LWT_ADD_METADATA_MARK] = str(self.lwt_info.lwt_meta_bit_mask) @staticmethod def parse_sharding_info(options): @@ -72,3 +80,18 @@ def parse_sharding_info(options): @staticmethod def parse_tablets_info(options): return TABLETS_ROUTING_V1 in options + + @staticmethod + def parse_lwt_info(options): + value_list = options.get(LWT_ADD_METADATA_MARK, [None]) + for value in value_list: + if value is None or not value.startswith(LWT_OPTIMIZATION_META_BIT_MASK + "="): + continue + try: + lwt_meta_bit_mask = int(value[len(LWT_OPTIMIZATION_META_BIT_MASK + "="):]) + return _LwtInfo(lwt_meta_bit_mask) + except Exception as e: + log.exception(f"Error while parsing {LWT_ADD_METADATA_MARK}: {e}") + return None + + return None From 5c31bb62a01b7fa761b29c01831dca9ef94f7e0e Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Thu, 13 Nov 2025 13:26:54 +0100 Subject: [PATCH 141/298] Parse LWT flags when creating prepared statement --- cassandra/cluster.py | 2 +- cassandra/lwt_info.py | 3 +++ cassandra/protocol.py | 14 ++++++++------ cassandra/query.py | 19 +++++++++++++++---- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index a184d40940..fcf4a0e440 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -3110,7 +3110,7 @@ def prepare(self, query, custom_payload=None, keyspace=None): prepared_keyspace = keyspace if keyspace else None prepared_statement = PreparedStatement.from_message( response.query_id, response.bind_metadata, response.pk_indexes, self.cluster.metadata, query, prepared_keyspace, - self._protocol_version, response.column_metadata, response.result_metadata_id, self.cluster.column_encryption_policy) + self._protocol_version, response.column_metadata, response.result_metadata_id, response.is_lwt, self.cluster.column_encryption_policy) prepared_statement.custom_payload = future.custom_payload self.cluster.add_prepared(response.query_id, prepared_statement) diff --git a/cassandra/lwt_info.py b/cassandra/lwt_info.py index 45750dbcec..d64c08bbcf 100644 --- a/cassandra/lwt_info.py +++ b/cassandra/lwt_info.py @@ -19,3 +19,6 @@ class _LwtInfo: def __init__(self, lwt_meta_bit_mask): self.lwt_meta_bit_mask = lwt_meta_bit_mask + + def get_lwt_flag(self, flags): + return (flags & self.lwt_meta_bit_mask) == self.lwt_meta_bit_mask diff --git a/cassandra/protocol.py b/cassandra/protocol.py index d8716f4eeb..c151d44361 100644 --- a/cassandra/protocol.py +++ b/cassandra/protocol.py @@ -686,11 +686,12 @@ class ResultMessage(_MessageType): bind_metadata = None pk_indexes = None schema_change_event = None + is_lwt = False def __init__(self, kind): self.kind = kind - def recv(self, f, protocol_version, user_type_map, result_metadata, column_encryption_policy): + def recv(self, f, protocol_version, protocol_features, user_type_map, result_metadata, column_encryption_policy): if self.kind == RESULT_KIND_VOID: return elif self.kind == RESULT_KIND_ROWS: @@ -698,7 +699,7 @@ def recv(self, f, protocol_version, user_type_map, result_metadata, column_encry elif self.kind == RESULT_KIND_SET_KEYSPACE: self.new_keyspace = read_string(f) elif self.kind == RESULT_KIND_PREPARED: - self.recv_results_prepared(f, protocol_version, user_type_map) + self.recv_results_prepared(f, protocol_version, protocol_features, user_type_map) elif self.kind == RESULT_KIND_SCHEMA_CHANGE: self.recv_results_schema_change(f, protocol_version) else: @@ -708,7 +709,7 @@ def recv(self, f, protocol_version, user_type_map, result_metadata, column_encry def recv_body(cls, f, protocol_version, protocol_features, user_type_map, result_metadata, column_encryption_policy): kind = read_int(f) msg = cls(kind) - msg.recv(f, protocol_version, user_type_map, result_metadata, column_encryption_policy) + msg.recv(f, protocol_version, protocol_features, user_type_map, result_metadata, column_encryption_policy) return msg def recv_results_rows(self, f, protocol_version, user_type_map, result_metadata, column_encryption_policy): @@ -741,13 +742,13 @@ def decode_row(row): col_md[3].cql_parameterized_type(), str(e))) - def recv_results_prepared(self, f, protocol_version, user_type_map): + def recv_results_prepared(self, f, protocol_version, protocol_features, user_type_map): self.query_id = read_binary_string(f) if ProtocolVersion.uses_prepared_metadata(protocol_version): self.result_metadata_id = read_binary_string(f) else: self.result_metadata_id = None - self.recv_prepared_metadata(f, protocol_version, user_type_map) + self.recv_prepared_metadata(f, protocol_version, protocol_features, user_type_map) def recv_results_metadata(self, f, user_type_map): flags = read_int(f) @@ -785,8 +786,9 @@ def recv_results_metadata(self, f, user_type_map): self.column_metadata = column_metadata - def recv_prepared_metadata(self, f, protocol_version, user_type_map): + def recv_prepared_metadata(self, f, protocol_version, protocol_features, user_type_map): flags = read_int(f) + self.is_lwt = protocol_features.lwt_info.get_lwt_flag(flags) if protocol_features.lwt_info is not None else False colcount = read_int(f) pk_indexes = None if protocol_version >= 4: diff --git a/cassandra/query.py b/cassandra/query.py index f3922849ab..246c0565ba 100644 --- a/cassandra/query.py +++ b/cassandra/query.py @@ -345,6 +345,9 @@ def _set_serial_consistency_level(self, serial_consistency_level): def _del_serial_consistency_level(self): self._serial_consistency_level = None + def is_lwt(self): + return False + serial_consistency_level = property( _get_serial_consistency_level, _set_serial_consistency_level, @@ -454,10 +457,11 @@ class PreparedStatement(object): routing_key_indexes = None _routing_key_index_set = None serial_consistency_level = None # TODO never used? + _is_lwt = False def __init__(self, column_metadata, query_id, routing_key_indexes, query, keyspace, protocol_version, result_metadata, result_metadata_id, - column_encryption_policy=None): + is_lwt=False, column_encryption_policy=None): self.column_metadata = column_metadata self.query_id = query_id self.routing_key_indexes = routing_key_indexes @@ -468,15 +472,16 @@ def __init__(self, column_metadata, query_id, routing_key_indexes, query, self.result_metadata_id = result_metadata_id self.column_encryption_policy = column_encryption_policy self.is_idempotent = False + self._is_lwt = is_lwt @classmethod def from_message(cls, query_id, column_metadata, pk_indexes, cluster_metadata, query, prepared_keyspace, protocol_version, result_metadata, - result_metadata_id, column_encryption_policy=None): + result_metadata_id, is_lwt, column_encryption_policy=None): if not column_metadata: return PreparedStatement(column_metadata, query_id, None, query, prepared_keyspace, protocol_version, result_metadata, - result_metadata_id, column_encryption_policy) + result_metadata_id, is_lwt, column_encryption_policy) if pk_indexes: routing_key_indexes = pk_indexes @@ -502,7 +507,7 @@ def from_message(cls, query_id, column_metadata, pk_indexes, cluster_metadata, return PreparedStatement(column_metadata, query_id, routing_key_indexes, query, prepared_keyspace, protocol_version, result_metadata, - result_metadata_id, column_encryption_policy) + result_metadata_id, is_lwt, column_encryption_policy) def bind(self, values): """ @@ -517,6 +522,9 @@ def is_routing_key_index(self, i): self._routing_key_index_set = set(self.routing_key_indexes) if self.routing_key_indexes else set() return i in self._routing_key_index_set + def is_lwt(self): + return self._is_lwt + def __str__(self): consistency = ConsistencyLevel.value_to_name.get(self.consistency_level, 'Not Set') return (u'' % @@ -682,6 +690,9 @@ def routing_key(self): return self._routing_key + def is_lwt(self): + return self.prepared_statement.is_lwt() + def __str__(self): consistency = ConsistencyLevel.value_to_name.get(self.consistency_level, 'Not Set') return (u'' % From f568aeb66fa231a72760c83a33f28064097ddcd5 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Thu, 13 Nov 2025 14:04:09 +0100 Subject: [PATCH 142/298] Add tests for lwt --- .../standard/test_prepared_statements.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/integration/standard/test_prepared_statements.py b/tests/integration/standard/test_prepared_statements.py index 68a704cd77..e6f86d835b 100644 --- a/tests/integration/standard/test_prepared_statements.py +++ b/tests/integration/standard/test_prepared_statements.py @@ -406,6 +406,29 @@ def test_raise_error_on_prepared_statement_execution_dropped_table(self): with pytest.raises(InvalidRequest): self.session.execute(prepared, [0]) + def test_recognize_lwt_query(self): + self.session.execute("CREATE TABLE IF NOT EXISTS preparedtests.bound_statement_test (a int PRIMARY KEY, b int)") + # Prepare a non-LWT statement + statementNonLWT = self.session.prepare("UPDATE preparedtests.bound_statement_test SET b = ? WHERE a = ?") + # Prepare an LWT statement + statementLWT = self.session.prepare("UPDATE preparedtests.bound_statement_test SET b = ? WHERE a = ? IF b = ?") + + boundNonLWT = statementNonLWT.bind((3, 1)) + boundLWT = statementLWT.bind((3, 1, 5)) + + # Check LWT detection + assert not boundNonLWT.is_lwt() + assert boundLWT.is_lwt() + + self.session.execute("CREATE TABLE IF NOT EXISTS preparedtests.prepared_statement_test (a int PRIMARY KEY, b int)") + # Prepare a non-LWT statement + statementNonLWT = self.session.prepare("UPDATE preparedtests.prepared_statement_test SET b = ? WHERE a = ?") + # Prepare an LWT statement + statementLWT = self.session.prepare("UPDATE preparedtests.prepared_statement_test SET b = ? WHERE a = ? IF b = ?") + # Check LWT detection + assert not statementNonLWT.is_lwt() + assert statementLWT.is_lwt() + @unittest.skipIf((CASSANDRA_VERSION >= Version('3.11.12') and CASSANDRA_VERSION < Version('4.0')) or \ CASSANDRA_VERSION >= Version('4.0.2'), "Fixed server-side in Cassandra 3.11.12, 4.0.2") From 75970592758c07cd28e2bcadb77f2d10726a8285 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Thu, 13 Nov 2025 21:23:13 +0100 Subject: [PATCH 143/298] Don not shuffle replicas for LWT queries --- cassandra/policies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cassandra/policies.py b/cassandra/policies.py index d681980d77..8869a9ce30 100644 --- a/cassandra/policies.py +++ b/cassandra/policies.py @@ -523,7 +523,7 @@ def make_query_plan(self, working_keyspace=None, query=None): else: replicas = self._cluster_metadata.get_replicas(keyspace, query.routing_key) - if self.shuffle_replicas: + if self.shuffle_replicas and not query.is_lwt(): shuffle(replicas) def yield_in_order(hosts): From f8d75086a5b7f7102dabbabb5c3aa6c93e682cdd Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 25 Nov 2025 17:26:52 -0400 Subject: [PATCH 144/298] Make xfail_scylla_version_lt accept any ent_scylla_version version It turns out that scylla can get new features in the minor version. So, let's ease requirement to ent_scylla_versio and allow any version there. --- tests/integration/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index 7a8845750e..edac685d12 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -688,7 +688,7 @@ def xfail_scylla_version_lt(reason, oss_scylla_version, ent_scylla_version, *arg It is used to mark tests that are going to fail on certain scylla versions. :param reason: message to fail test with :param oss_scylla_version: str, oss version from which test supposed to succeed - :param ent_scylla_version: str, enterprise version from which test supposed to succeed. It should end with `.1.1` + :param ent_scylla_version: str, enterprise version from which test supposed to succeed """ if not reason.startswith("scylladb/scylladb#"): raise ValueError('reason should start with scylladb/scylladb# to reference issue in scylla repo') @@ -696,9 +696,6 @@ def xfail_scylla_version_lt(reason, oss_scylla_version, ent_scylla_version, *arg if not isinstance(ent_scylla_version, str): raise ValueError('ent_scylla_version should be a str') - if not ent_scylla_version.endswith("1.1"): - raise ValueError('ent_scylla_version should end with "1.1"') - if SCYLLA_VERSION is None: return pytest.mark.skipif(False, reason="It is just a NoOP Decor, should not skip anything") From 390e1652a3d6d7123066e454fc1eb048136dd590 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 25 Nov 2025 16:46:05 -0400 Subject: [PATCH 145/298] Add support of CONNECTION_METADATA_CHANGE event This event was introduced into the core recently, it is sent whenever `system.conneciton_metadata` table is updated. --- cassandra/protocol.py | 11 ++- .../standard/test_control_connection.py | 72 ++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/cassandra/protocol.py b/cassandra/protocol.py index c151d44361..8fee0db101 100644 --- a/cassandra/protocol.py +++ b/cassandra/protocol.py @@ -956,7 +956,8 @@ def send_body(self, f, protocol_version): known_event_types = frozenset(( 'TOPOLOGY_CHANGE', 'STATUS_CHANGE', - 'SCHEMA_CHANGE' + 'SCHEMA_CHANGE', + 'CONNECTION_METADATA_CHANGE' )) @@ -987,6 +988,14 @@ def recv_body(cls, f, protocol_version, *args): return cls(event_type=event_type, event_args=read_method(f, protocol_version)) raise NotSupportedError('Unknown event type %r' % event_type) + @classmethod + def recv_connection_metadata_change(cls, f, protocol_version): + # "UPDATE_NODES" + change_type = read_string(f) + connection_ids = read_stringlist(f) + host_ids = read_stringlist(f) + return dict(change_type=change_type, connection_ids=connection_ids, host_ids=host_ids) + @classmethod def recv_topology_change(cls, f, protocol_version): # "NEW_NODE" or "REMOVED_NODE" diff --git a/tests/integration/standard/test_control_connection.py b/tests/integration/standard/test_control_connection.py index 32cc468e64..350c6a5ffe 100644 --- a/tests/integration/standard/test_control_connection.py +++ b/tests/integration/standard/test_control_connection.py @@ -14,13 +14,17 @@ # # # +from threading import Event + from cassandra import InvalidRequest import unittest +import requests from cassandra.protocol import ConfigurationException -from tests.integration import use_singledc, PROTOCOL_VERSION, TestCluster, greaterthanorequalcass40 +from tests.integration import use_singledc, PROTOCOL_VERSION, TestCluster, greaterthanorequalcass40, \ + xfail_scylla_version_lt from tests.integration.datatype_utils import update_datatypes @@ -127,3 +131,69 @@ def test_control_connection_port_discovery(self): for host in hosts: assert 9042 == host.broadcast_rpc_port assert 7000 == host.broadcast_port + + @xfail_scylla_version_lt(reason='scylladb/scylladb#26992 - system.connection_metadata is not yet supported', + oss_scylla_version="7.0", ent_scylla_version="2025.4.0") + def test_connection_metadata_change_event(self): + cluster = TestCluster() + + # Establish control connection + cluster.connect() + + flag = Event() + + connection_ids = ["anytext", "11510f50-f906-4844-8c74-49ddab9ac6a9"] + host_ids = ["1a13fa42-c45b-410f-8ba5-58b42ada9c12", "aa13fa42-c45b-410f-8ba5-58b42ada9c12"] + got_connection_ids = [] + got_host_ids = [] + + def on_event(event): + nonlocal got_connection_ids + nonlocal got_host_ids + try: + assert event.get("change_type") == "UPDATE_NODES" + got_connection_ids = event.get("connection_ids") + got_host_ids = event.get("host_ids") + finally: + flag.set() + + cluster.control_connection._connection.register_watchers({"CONNECTION_METADATA_CHANGE": on_event}) + + try: + payload = [ + { + "connection_id": connection_ids[0], # Should be a UUID if API requires that + "host_id": host_ids[0], + "address": "localhost", + "port": 9042, + "tls_port": 0, + "alternator_port": 0, + "alternator_https_port": 0, + "rack": "string", + "datacenter": "string" + }, + { + "connection_id": connection_ids[1], + "host_id": host_ids[1], + "address": "localhost", + "port": 9042, + "tls_port": 0, + "alternator_port": 0, + "alternator_https_port": 0, + "rack": "string", + "datacenter": "string" + } + ] + response = requests.post( + "http://" + cluster.contact_points[0] + ":10000/v2/connection-metadata", + json=payload, + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + }) + assert response.status_code == 200 + assert flag.wait(20), "Schema change event was not received after registering watchers" + assert got_connection_ids == connection_ids + assert got_host_ids == host_ids + finally: + cluster.shutdown() From 01eb2e83053f72a2485770d949bb96d0a9988f86 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Wed, 26 Nov 2025 11:52:50 -0400 Subject: [PATCH 146/298] Add support of LWT flag for Batch statements --- cassandra/query.py | 10 ++++++++ tests/unit/test_query.py | 49 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/cassandra/query.py b/cassandra/query.py index 246c0565ba..6c6878fdb4 100644 --- a/cassandra/query.py +++ b/cassandra/query.py @@ -761,6 +761,7 @@ class BatchStatement(Statement): _statements_and_parameters = None _session = None + _is_lwt = False def __init__(self, batch_type=BatchType.LOGGED, retry_policy=None, consistency_level=None, serial_consistency_level=None, @@ -845,6 +846,8 @@ def add(self, statement, parameters=None): query_id = statement.query_id bound_statement = statement.bind(() if parameters is None else parameters) self._update_state(bound_statement) + if statement.is_lwt(): + self._is_lwt = True self._add_statement_and_params(True, query_id, bound_statement.values) elif isinstance(statement, BoundStatement): if parameters: @@ -852,6 +855,8 @@ def add(self, statement, parameters=None): "Parameters cannot be passed with a BoundStatement " "to BatchStatement.add()") self._update_state(statement) + if statement.is_lwt(): + self._is_lwt = True self._add_statement_and_params(True, statement.prepared_statement.query_id, statement.values) else: # it must be a SimpleStatement @@ -860,6 +865,8 @@ def add(self, statement, parameters=None): encoder = Encoder() if self._session is None else self._session.encoder query_string = bind_params(query_string, parameters, encoder) self._update_state(statement) + if statement.is_lwt(): + self._is_lwt = True self._add_statement_and_params(False, query_string, ()) return self @@ -893,6 +900,9 @@ def _update_state(self, statement): self._maybe_set_routing_attributes(statement) self._update_custom_payload(statement) + def is_lwt(self): + return self._is_lwt + def __len__(self): return len(self._statements_and_parameters) diff --git a/tests/unit/test_query.py b/tests/unit/test_query.py index 29c800b99c..6b0ebe690e 100644 --- a/tests/unit/test_query.py +++ b/tests/unit/test_query.py @@ -14,7 +14,7 @@ import unittest -from cassandra.query import BatchStatement, SimpleStatement +from cassandra.query import BatchStatement, PreparedStatement, SimpleStatement class BatchStatementTest(unittest.TestCase): @@ -68,3 +68,50 @@ def test_len(self): batch.add_all(statements=['%s'] * n, parameters=[(i,) for i in range(n)]) assert len(batch) == n + + def _make_prepared_statement(self, is_lwt=False): + return PreparedStatement( + column_metadata=[], + query_id=b"query-id", + routing_key_indexes=[], + query="INSERT INTO test.table (id) VALUES (1)", + keyspace=None, + protocol_version=4, + result_metadata=[], + result_metadata_id=None, + is_lwt=is_lwt, + ) + + def test_is_lwt_false_for_non_lwt_statements(self): + batch = BatchStatement() + batch.add(self._make_prepared_statement(is_lwt=False)) + batch.add(self._make_prepared_statement(is_lwt=False).bind(())) + batch.add(SimpleStatement("INSERT INTO test.table (id) VALUES (3)")) + batch.add("INSERT INTO test.table (id) VALUES (4)") + assert batch.is_lwt() is False + + def test_is_lwt_propagates_from_statements(self): + batch = BatchStatement() + batch.add(self._make_prepared_statement(is_lwt=False)) + assert batch.is_lwt() is False + + batch.add(self._make_prepared_statement(is_lwt=True)) + assert batch.is_lwt() is True + + bound_lwt = self._make_prepared_statement(is_lwt=True).bind(()) + batch_with_bound = BatchStatement() + batch_with_bound.add(bound_lwt) + assert batch_with_bound.is_lwt() is True + + class LwtSimpleStatement(SimpleStatement): + def __init__(self): + super(LwtSimpleStatement, self).__init__( + "INSERT INTO test.table (id) VALUES (2) IF NOT EXISTS" + ) + + def is_lwt(self): + return True + + batch_with_simple = BatchStatement() + batch_with_simple.add(LwtSimpleStatement()) + assert batch_with_simple.is_lwt() is True From 2ad9ba6051620d4e2fa30703e622726776c3fa42 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sat, 31 May 2025 00:03:52 -0400 Subject: [PATCH 147/298] TokenAwarePolicy: enable shuffling by default Shuffling provides better performance with no downsides --- cassandra/policies.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cassandra/policies.py b/cassandra/policies.py index 8869a9ce30..bcfd797706 100644 --- a/cassandra/policies.py +++ b/cassandra/policies.py @@ -476,12 +476,12 @@ class TokenAwarePolicy(LoadBalancingPolicy): _child_policy = None _cluster_metadata = None - shuffle_replicas = False + shuffle_replicas = True """ Yield local replicas in a random order. """ - def __init__(self, child_policy, shuffle_replicas=False): + def __init__(self, child_policy, shuffle_replicas=True): self._child_policy = child_policy self.shuffle_replicas = shuffle_replicas From f3c80923caf4eaa215cd5d87b390df88b8781b8a Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 27 Nov 2025 09:38:19 -0400 Subject: [PATCH 148/298] Fix tests to match new shuffle_replicas default --- tests/unit/advanced/test_insights.py | 8 ++++---- tests/unit/test_policies.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/advanced/test_insights.py b/tests/unit/advanced/test_insights.py index 6646e6746f..ec9b918866 100644 --- a/tests/unit/advanced/test_insights.py +++ b/tests/unit/advanced/test_insights.py @@ -126,7 +126,7 @@ def test_execution_profile(self): 'options': {'local_dc': '', 'used_hosts_per_remote_dc': 0}, 'type': 'DCAwareRoundRobinPolicy'}, - 'shuffle_replicas': False}, + 'shuffle_replicas': True}, 'type': 'TokenAwarePolicy'}, 'readTimeout': 10.0, 'retry': {'namespace': 'cassandra.policies', 'options': {}, 'type': 'RetryPolicy'}, @@ -145,7 +145,7 @@ def test_graph_execution_profile(self): 'options': {'local_dc': '', 'used_hosts_per_remote_dc': 0}, 'type': 'DCAwareRoundRobinPolicy'}, - 'shuffle_replicas': False}, + 'shuffle_replicas': True}, 'type': 'TokenAwarePolicy'}, 'readTimeout': 30.0, 'retry': {'namespace': 'cassandra.policies', 'options': {}, 'type': 'NeverRetryPolicy'}, @@ -167,7 +167,7 @@ def test_graph_analytics_execution_profile(self): 'options': {'local_dc': '', 'used_hosts_per_remote_dc': 0}, 'type': 'DCAwareRoundRobinPolicy'}, - 'shuffle_replicas': False}, + 'shuffle_replicas': True}, 'type': 'TokenAwarePolicy'}}, 'type': 'DefaultLoadBalancingPolicy'}, 'readTimeout': 604800.0, @@ -195,7 +195,7 @@ def test_token_aware_policy(self): 'options': {'child_policy': {'namespace': 'cassandra.policies', 'options': {}, 'type': 'LoadBalancingPolicy'}, - 'shuffle_replicas': False}, + 'shuffle_replicas': True}, 'type': 'TokenAwarePolicy'} def test_whitelist_round_robin_policy(self): diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index 084b2c3137..e15705c8f7 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -604,14 +604,14 @@ def get_replicas(keyspace, packed_key): replicas = get_replicas(None, struct.pack('>i', i)) other = set(h for h in hosts if h not in replicas) - assert replicas == qplan[:2] + assert sorted(replicas) == sorted(qplan[:2]) assert other == set(qplan[2:]) # Should use the secondary policy for i in range(4): qplan = list(policy.make_query_plan()) - assert set(qplan) == set(hosts) + assert sorted(set(qplan)) == sorted(set(hosts)) def test_wrap_dc_aware(self): cluster = Mock(spec=Cluster) From c71835aeb0240a0852837581b4579730e0cbebc4 Mon Sep 17 00:00:00 2001 From: David Garcia Date: Mon, 17 Nov 2025 08:42:48 +0000 Subject: [PATCH 149/298] docs: style Python API reference docs --- docs/api/cassandra.rst | 6 ++++-- docs/api/cassandra/auth.rst | 6 ++++-- docs/api/cassandra/cluster.rst | 6 ++++-- docs/api/cassandra/concurrent.rst | 6 ++++-- docs/api/cassandra/connection.rst | 6 ++++-- docs/api/cassandra/cqlengine/columns.rst | 6 ++++-- docs/api/cassandra/cqlengine/connection.rst | 6 ++++-- docs/api/cassandra/cqlengine/management.rst | 6 ++++-- docs/api/cassandra/cqlengine/models.rst | 6 ++++-- docs/api/cassandra/cqlengine/query.rst | 6 ++++-- docs/api/cassandra/cqlengine/usertype.rst | 6 ++++-- docs/api/cassandra/decoder.rst | 6 ++++-- docs/api/cassandra/encoder.rst | 6 ++++-- docs/api/cassandra/io/asyncioreactor.rst | 6 ++++-- docs/api/cassandra/io/asyncorereactor.rst | 6 ++++-- docs/api/cassandra/io/eventletreactor.rst | 6 ++++-- docs/api/cassandra/io/geventreactor.rst | 6 ++++-- docs/api/cassandra/io/libevreactor.rst | 6 ++++-- docs/api/cassandra/io/twistedreactor.rst | 6 ++++-- docs/api/cassandra/metadata.rst | 6 ++++-- docs/api/cassandra/metrics.rst | 6 ++++-- docs/api/cassandra/policies.rst | 6 ++++-- docs/api/cassandra/pool.rst | 6 ++++-- docs/api/cassandra/protocol.rst | 6 ++++-- docs/api/cassandra/query.rst | 6 ++++-- docs/api/cassandra/timestamps.rst | 6 ++++-- docs/api/cassandra/util.rst | 6 ++++-- docs/api/index.rst | 2 +- 28 files changed, 109 insertions(+), 55 deletions(-) diff --git a/docs/api/cassandra.rst b/docs/api/cassandra.rst index d46aae56cb..53789b9582 100644 --- a/docs/api/cassandra.rst +++ b/docs/api/cassandra.rst @@ -1,5 +1,7 @@ -:mod:`cassandra` - Exceptions and Enums -======================================= +cassandra +========= + +Exceptions and Enums .. module:: cassandra diff --git a/docs/api/cassandra/auth.rst b/docs/api/cassandra/auth.rst index 58c964cf89..91bb4e9139 100644 --- a/docs/api/cassandra/auth.rst +++ b/docs/api/cassandra/auth.rst @@ -1,5 +1,7 @@ -``cassandra.auth`` - Authentication -=================================== +cassandra.auth +============== + +Authentication .. module:: cassandra.auth diff --git a/docs/api/cassandra/cluster.rst b/docs/api/cassandra/cluster.rst index 01a1e414a0..51f03f3d97 100644 --- a/docs/api/cassandra/cluster.rst +++ b/docs/api/cassandra/cluster.rst @@ -1,5 +1,7 @@ -``cassandra.cluster`` - Clusters and Sessions -============================================= +cassandra.cluster +================= + +Clusters and Sessions .. module:: cassandra.cluster diff --git a/docs/api/cassandra/concurrent.rst b/docs/api/cassandra/concurrent.rst index f4bab6f048..8f403a3e3c 100644 --- a/docs/api/cassandra/concurrent.rst +++ b/docs/api/cassandra/concurrent.rst @@ -1,5 +1,7 @@ -``cassandra.concurrent`` - Utilities for Concurrent Statement Execution -======================================================================= +cassandra.concurrent +==================== + +Utilities for Concurrent Statement Execution .. module:: cassandra.concurrent diff --git a/docs/api/cassandra/connection.rst b/docs/api/cassandra/connection.rst index 32cca590c0..f9ec4eef61 100644 --- a/docs/api/cassandra/connection.rst +++ b/docs/api/cassandra/connection.rst @@ -1,5 +1,7 @@ -``cassandra.connection`` - Low Level Connection Info -==================================================== +cassandra.connection +==================== + +Low Level Connection Info .. module:: cassandra.connection diff --git a/docs/api/cassandra/cqlengine/columns.rst b/docs/api/cassandra/cqlengine/columns.rst index d44be8adb8..35a47f0ef4 100644 --- a/docs/api/cassandra/cqlengine/columns.rst +++ b/docs/api/cassandra/cqlengine/columns.rst @@ -1,5 +1,7 @@ -``cassandra.cqlengine.columns`` - Column types for object mapping models -======================================================================== +cassandra.cqlengine.columns +=========================== + +Column types for object mapping models .. module:: cassandra.cqlengine.columns diff --git a/docs/api/cassandra/cqlengine/connection.rst b/docs/api/cassandra/cqlengine/connection.rst index 0f584fcca2..6270b75c4e 100644 --- a/docs/api/cassandra/cqlengine/connection.rst +++ b/docs/api/cassandra/cqlengine/connection.rst @@ -1,5 +1,7 @@ -``cassandra.cqlengine.connection`` - Connection management for cqlengine -======================================================================== +cassandra.cqlengine.connection +============================== + +Connection management for cqlengine .. module:: cassandra.cqlengine.connection diff --git a/docs/api/cassandra/cqlengine/management.rst b/docs/api/cassandra/cqlengine/management.rst index fb483abc81..62709019da 100644 --- a/docs/api/cassandra/cqlengine/management.rst +++ b/docs/api/cassandra/cqlengine/management.rst @@ -1,5 +1,7 @@ -``cassandra.cqlengine.management`` - Schema management for cqlengine -======================================================================== +cassandra.cqlengine.management +============================== + +Schema management for cqlengine .. module:: cassandra.cqlengine.management diff --git a/docs/api/cassandra/cqlengine/models.rst b/docs/api/cassandra/cqlengine/models.rst index 44a015a9f4..9905926c4e 100644 --- a/docs/api/cassandra/cqlengine/models.rst +++ b/docs/api/cassandra/cqlengine/models.rst @@ -1,5 +1,7 @@ -``cassandra.cqlengine.models`` - Table models for object mapping -================================================================ +cassandra.cqlengine.models +========================== + +Table models for object mapping .. module:: cassandra.cqlengine.models diff --git a/docs/api/cassandra/cqlengine/query.rst b/docs/api/cassandra/cqlengine/query.rst index ce8f764b6b..0d8c52164f 100644 --- a/docs/api/cassandra/cqlengine/query.rst +++ b/docs/api/cassandra/cqlengine/query.rst @@ -1,5 +1,7 @@ -``cassandra.cqlengine.query`` - Query and filter model objects -================================================================= +cassandra.cqlengine.query +========================= + +Query and filter model objects .. module:: cassandra.cqlengine.query diff --git a/docs/api/cassandra/cqlengine/usertype.rst b/docs/api/cassandra/cqlengine/usertype.rst index ebed187da9..219de8c300 100644 --- a/docs/api/cassandra/cqlengine/usertype.rst +++ b/docs/api/cassandra/cqlengine/usertype.rst @@ -1,5 +1,7 @@ -``cassandra.cqlengine.usertype`` - Model classes for User Defined Types -======================================================================= +cassandra.cqlengine.usertype +============================ + +Model classes for User Defined Types .. module:: cassandra.cqlengine.usertype diff --git a/docs/api/cassandra/decoder.rst b/docs/api/cassandra/decoder.rst index e213cc6d74..6341664cb3 100644 --- a/docs/api/cassandra/decoder.rst +++ b/docs/api/cassandra/decoder.rst @@ -1,5 +1,7 @@ -``cassandra.decoder`` - Data Return Formats -=========================================== +cassandra.decoder +================= + +Data Return Formats .. module:: cassandra.decoder diff --git a/docs/api/cassandra/encoder.rst b/docs/api/cassandra/encoder.rst index de3b180510..8919c87ddd 100644 --- a/docs/api/cassandra/encoder.rst +++ b/docs/api/cassandra/encoder.rst @@ -1,5 +1,7 @@ -``cassandra.encoder`` - Encoders for non-prepared Statements -============================================================ +cassandra.encoder +================= + +Encoders for non-prepared Statements .. module:: cassandra.encoder diff --git a/docs/api/cassandra/io/asyncioreactor.rst b/docs/api/cassandra/io/asyncioreactor.rst index 38ae63ca7f..a7509ed6a8 100644 --- a/docs/api/cassandra/io/asyncioreactor.rst +++ b/docs/api/cassandra/io/asyncioreactor.rst @@ -1,5 +1,7 @@ -``cassandra.io.asyncioreactor`` - ``asyncio`` Event Loop -===================================================================== +cassandra.io.asyncioreactor +=========================== + +``asyncio`` Event Loop .. module:: cassandra.io.asyncioreactor diff --git a/docs/api/cassandra/io/asyncorereactor.rst b/docs/api/cassandra/io/asyncorereactor.rst index ade7887e70..661fd9c1ec 100644 --- a/docs/api/cassandra/io/asyncorereactor.rst +++ b/docs/api/cassandra/io/asyncorereactor.rst @@ -1,5 +1,7 @@ -``cassandra.io.asyncorereactor`` - ``asyncore`` Event Loop -========================================================== +cassandra.io.asyncorereactor +============================ + +``asyncore`` Event Loop .. module:: cassandra.io.asyncorereactor diff --git a/docs/api/cassandra/io/eventletreactor.rst b/docs/api/cassandra/io/eventletreactor.rst index 1ba742c7e9..2e71153b70 100644 --- a/docs/api/cassandra/io/eventletreactor.rst +++ b/docs/api/cassandra/io/eventletreactor.rst @@ -1,5 +1,7 @@ -``cassandra.io.eventletreactor`` - ``eventlet``-compatible Connection -===================================================================== +cassandra.io.eventletreactor +============================ + +``eventlet``-compatible Connection .. module:: cassandra.io.eventletreactor diff --git a/docs/api/cassandra/io/geventreactor.rst b/docs/api/cassandra/io/geventreactor.rst index 603affe140..a4b0235c6a 100644 --- a/docs/api/cassandra/io/geventreactor.rst +++ b/docs/api/cassandra/io/geventreactor.rst @@ -1,5 +1,7 @@ -``cassandra.io.geventreactor`` - ``gevent``-compatible Event Loop -================================================================= +cassandra.io.geventreactor +========================== + +``gevent``-compatible Event Loop .. module:: cassandra.io.geventreactor diff --git a/docs/api/cassandra/io/libevreactor.rst b/docs/api/cassandra/io/libevreactor.rst index 5b7288edf2..2269d0822a 100644 --- a/docs/api/cassandra/io/libevreactor.rst +++ b/docs/api/cassandra/io/libevreactor.rst @@ -1,5 +1,7 @@ -``cassandra.io.libevreactor`` - ``libev`` Event Loop -==================================================== +cassandra.io.libevreactor +========================= + +``libev`` Event Loop .. module:: cassandra.io.libevreactor diff --git a/docs/api/cassandra/io/twistedreactor.rst b/docs/api/cassandra/io/twistedreactor.rst index 24e93bd432..cc6944c9fd 100644 --- a/docs/api/cassandra/io/twistedreactor.rst +++ b/docs/api/cassandra/io/twistedreactor.rst @@ -1,5 +1,7 @@ -``cassandra.io.twistedreactor`` - Twisted Event Loop -==================================================== +cassandra.io.twistedreactor +=========================== + +Twisted Event Loop .. module:: cassandra.io.twistedreactor diff --git a/docs/api/cassandra/metadata.rst b/docs/api/cassandra/metadata.rst index 7c1280bcf7..25526f61ec 100644 --- a/docs/api/cassandra/metadata.rst +++ b/docs/api/cassandra/metadata.rst @@ -1,5 +1,7 @@ -``cassandra.metadata`` - Schema and Ring Topology -================================================= +cassandra.metadata +================== + +Schema and Ring Topology .. module:: cassandra.metadata diff --git a/docs/api/cassandra/metrics.rst b/docs/api/cassandra/metrics.rst index 0df7f8b5b9..d2ee997bca 100644 --- a/docs/api/cassandra/metrics.rst +++ b/docs/api/cassandra/metrics.rst @@ -1,5 +1,7 @@ -``cassandra.metrics`` - Performance Metrics -=========================================== +cassandra.metrics +================= + +Performance Metrics .. module:: cassandra.metrics diff --git a/docs/api/cassandra/policies.rst b/docs/api/cassandra/policies.rst index ea3b19d796..84d5575a40 100644 --- a/docs/api/cassandra/policies.rst +++ b/docs/api/cassandra/policies.rst @@ -1,5 +1,7 @@ -``cassandra.policies`` - Load balancing and Failure Handling Policies -===================================================================== +cassandra.policies +================== + +Load balancing and Failure Handling Policies .. module:: cassandra.policies diff --git a/docs/api/cassandra/pool.rst b/docs/api/cassandra/pool.rst index b14d30e19c..f6a59ce58a 100644 --- a/docs/api/cassandra/pool.rst +++ b/docs/api/cassandra/pool.rst @@ -1,5 +1,7 @@ -``cassandra.pool`` - Hosts and Connection Pools -=============================================== +cassandra.pool +============== + +Hosts and Connection Pools .. automodule:: cassandra.pool diff --git a/docs/api/cassandra/protocol.rst b/docs/api/cassandra/protocol.rst index f615ab1a70..258c6baeb6 100644 --- a/docs/api/cassandra/protocol.rst +++ b/docs/api/cassandra/protocol.rst @@ -1,5 +1,7 @@ -``cassandra.protocol`` - Protocol Features -===================================================================== +cassandra.protocol +================== + +Protocol Features .. module:: cassandra.protocol diff --git a/docs/api/cassandra/query.rst b/docs/api/cassandra/query.rst index fcd79739b9..aa3a8c1035 100644 --- a/docs/api/cassandra/query.rst +++ b/docs/api/cassandra/query.rst @@ -1,5 +1,7 @@ -``cassandra.query`` - Prepared Statements, Batch Statements, Tracing, and Row Factories -======================================================================================= +cassandra.query +=============== + +Prepared Statements, Batch Statements, Tracing, and Row Factories .. module:: cassandra.query diff --git a/docs/api/cassandra/timestamps.rst b/docs/api/cassandra/timestamps.rst index 00d25b06d9..4335784de3 100644 --- a/docs/api/cassandra/timestamps.rst +++ b/docs/api/cassandra/timestamps.rst @@ -1,5 +1,7 @@ -``cassandra.timestamps`` - Timestamp Generation -=============================================== +cassandra.timestamps +==================== + +Timestamp Generation .. module:: cassandra.timestamps diff --git a/docs/api/cassandra/util.rst b/docs/api/cassandra/util.rst index 848d4d5fc2..ace39f86dd 100644 --- a/docs/api/cassandra/util.rst +++ b/docs/api/cassandra/util.rst @@ -1,5 +1,7 @@ -``cassandra.util`` - Utilities -=================================== +cassandra.util +============== + +Utilities .. automodule:: cassandra.util :members: diff --git a/docs/api/index.rst b/docs/api/index.rst index cf792283d0..cecbea5e75 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -4,7 +4,7 @@ API Documentation Core Driver ----------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 cassandra cassandra/cluster From 067a85cac0a510aed963657f546239edf9a3ab75 Mon Sep 17 00:00:00 2001 From: David Garcia Date: Mon, 24 Nov 2025 13:41:30 +0000 Subject: [PATCH 150/298] docs: update uv.lock --- docs/uv.lock | 676 +++++++++++++++++++++++---------------------------- 1 file changed, 303 insertions(+), 373 deletions(-) diff --git a/docs/uv.lock b/docs/uv.lock index 9c3371f46a..6715944669 100644 --- a/docs/uv.lock +++ b/docs/uv.lock @@ -1,6 +1,6 @@ version = 1 -revision = 2 -requires-python = ">=3.13, <4" +revision = 3 +requires-python = "==3.13.*" [[package]] name = "aenum" @@ -21,7 +21,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.12.15" +version = "3.13.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -32,25 +32,25 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, - { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, - { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, - { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, - { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, - { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, - { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, - { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, - { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, - { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, - { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, - { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, - { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/bf/78/7e90ca79e5aa39f9694dcfd74f4720782d3c6828113bb1f3197f7e7c4a56/aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be", size = 732139, upload-time = "2025-10-28T20:57:02.455Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742", size = 490082, upload-time = "2025-10-28T20:57:04.784Z" }, + { url = "https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293", size = 489035, upload-time = "2025-10-28T20:57:06.894Z" }, + { url = "https://files.pythonhosted.org/packages/d2/04/db5279e38471b7ac801d7d36a57d1230feeee130bbe2a74f72731b23c2b1/aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811", size = 1720387, upload-time = "2025-10-28T20:57:08.685Z" }, + { url = "https://files.pythonhosted.org/packages/31/07/8ea4326bd7dae2bd59828f69d7fdc6e04523caa55e4a70f4a8725a7e4ed2/aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a", size = 1688314, upload-time = "2025-10-28T20:57:10.693Z" }, + { url = "https://files.pythonhosted.org/packages/48/ab/3d98007b5b87ffd519d065225438cc3b668b2f245572a8cb53da5dd2b1bc/aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4", size = 1756317, upload-time = "2025-10-28T20:57:12.563Z" }, + { url = "https://files.pythonhosted.org/packages/97/3d/801ca172b3d857fafb7b50c7c03f91b72b867a13abca982ed6b3081774ef/aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a", size = 1858539, upload-time = "2025-10-28T20:57:14.623Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e", size = 1739597, upload-time = "2025-10-28T20:57:16.399Z" }, + { url = "https://files.pythonhosted.org/packages/c4/52/7bd3c6693da58ba16e657eb904a5b6decfc48ecd06e9ac098591653b1566/aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb", size = 1555006, upload-time = "2025-10-28T20:57:18.288Z" }, + { url = "https://files.pythonhosted.org/packages/48/30/9586667acec5993b6f41d2ebcf96e97a1255a85f62f3c653110a5de4d346/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded", size = 1683220, upload-time = "2025-10-28T20:57:20.241Z" }, + { url = "https://files.pythonhosted.org/packages/71/01/3afe4c96854cfd7b30d78333852e8e851dceaec1c40fd00fec90c6402dd2/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b", size = 1712570, upload-time = "2025-10-28T20:57:22.253Z" }, + { url = "https://files.pythonhosted.org/packages/11/2c/22799d8e720f4697a9e66fd9c02479e40a49de3de2f0bbe7f9f78a987808/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8", size = 1733407, upload-time = "2025-10-28T20:57:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/34/cb/90f15dd029f07cebbd91f8238a8b363978b530cd128488085b5703683594/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04", size = 1550093, upload-time = "2025-10-28T20:57:26.257Z" }, + { url = "https://files.pythonhosted.org/packages/69/46/12dce9be9d3303ecbf4d30ad45a7683dc63d90733c2d9fe512be6716cd40/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476", size = 1758084, upload-time = "2025-10-28T20:57:28.349Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/0932b558da0c302ffd639fc6362a313b98fdf235dc417bc2493da8394df7/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23", size = 1716987, upload-time = "2025-10-28T20:57:30.233Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8b/f5bd1a75003daed099baec373aed678f2e9b34f2ad40d85baa1368556396/aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254", size = 425859, upload-time = "2025-10-28T20:57:32.105Z" }, + { url = "https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a", size = 452192, upload-time = "2025-10-28T20:57:34.166Z" }, ] [[package]] @@ -98,11 +98,11 @@ wheels = [ [[package]] name = "attrs" -version = "25.3.0" +version = "25.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] [[package]] @@ -116,33 +116,33 @@ wheels = [ [[package]] name = "beartype" -version = "0.21.0" +version = "0.22.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/f9/21e5a9c731e14f08addd53c71fea2e70794e009de5b98e6a2c3d2f3015d6/beartype-0.21.0.tar.gz", hash = "sha256:f9a5078f5ce87261c2d22851d19b050b64f6a805439e8793aecf01ce660d3244", size = 1437066, upload-time = "2025-05-22T05:09:27.116Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/e2/105ceb1704cb80fe4ab3872529ab7b6f365cf7c74f725e6132d0efcf1560/beartype-0.22.6.tar.gz", hash = "sha256:97fbda69c20b48c5780ac2ca60ce3c1bb9af29b3a1a0216898ffabdd523e48f4", size = 1588975, upload-time = "2025-11-20T04:47:14.736Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/31/87045d1c66ee10a52486c9d2047bc69f00f2689f69401bb1e998afb4b205/beartype-0.21.0-py3-none-any.whl", hash = "sha256:b6a1bd56c72f31b0a496a36cc55df6e2f475db166ad07fa4acc7e74f4c7f34c0", size = 1191340, upload-time = "2025-05-22T05:09:24.606Z" }, + { url = "https://files.pythonhosted.org/packages/98/c9/ceecc71fe2c9495a1d8e08d44f5f31f5bca1350d5b2e27a4b6265424f59e/beartype-0.22.6-py3-none-any.whl", hash = "sha256:0584bc46a2ea2a871509679278cda992eadde676c01356ab0ac77421f3c9a093", size = 1324807, upload-time = "2025-11-20T04:47:11.837Z" }, ] [[package]] name = "beautifulsoup4" -version = "4.13.5" +version = "4.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/2e/3e5079847e653b1f6dc647aa24549d68c6addb4c595cc0d902d1b19308ad/beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695", size = 622954, upload-time = "2025-08-24T14:06:13.168Z" } +sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/eb/f4151e0c7377a6e08a38108609ba5cede57986802757848688aeedd1b9e8/beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a", size = 105113, upload-time = "2025-08-24T14:06:14.884Z" }, + { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, ] [[package]] name = "certifi" -version = "2025.8.3" +version = "2025.11.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, ] [[package]] @@ -157,55 +157,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, - { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, - { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, - { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, - { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, - { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, - { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, - { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, - { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, - { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, - { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, - { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, - { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, - { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, - { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, - { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, - { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] [[package]] name = "click" -version = "8.3.0" +version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] @@ -259,45 +247,43 @@ wheels = [ [[package]] name = "frozenlist" -version = "1.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, - { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, - { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, - { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" }, - { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" }, - { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" }, - { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" }, - { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" }, - { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" }, - { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" }, - { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" }, - { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" }, - { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" }, - { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" }, - { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, - { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, - { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" }, - { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" }, - { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" }, - { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" }, - { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" }, - { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" }, - { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" }, - { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" }, - { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" }, - { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" }, - { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" }, - { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" }, - { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" }, - { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] [[package]] @@ -320,14 +306,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/75/6bbe57c19a7aa4527cc0f9afcdf5a5f2aed2603b08aadbccb5bf7f607ff4/gevent-25.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4e17c2d57e9a42e25f2a73d297b22b60b2470a74be5a515b36c984e1a246d47", size = 1829059, upload-time = "2025-09-17T15:52:42.596Z" }, { url = "https://files.pythonhosted.org/packages/06/6e/19a9bee9092be45679cb69e4dd2e0bf5f897b7140b4b39c57cc123d24829/gevent-25.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d94936f8f8b23d9de2251798fcb603b84f083fdf0d7f427183c1828fb64f117", size = 2173529, upload-time = "2025-09-17T15:24:13.897Z" }, { url = "https://files.pythonhosted.org/packages/ca/4f/50de9afd879440e25737e63f5ba6ee764b75a3abe17376496ab57f432546/gevent-25.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:eb51c5f9537b07da673258b4832f6635014fee31690c3f0944d34741b69f92fa", size = 1681518, upload-time = "2025-09-17T19:39:47.488Z" }, - { url = "https://files.pythonhosted.org/packages/15/1a/948f8167b2cdce573cf01cec07afc64d0456dc134b07900b26ac7018b37e/gevent-25.9.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:1a3fe4ea1c312dbf6b375b416925036fe79a40054e6bf6248ee46526ea628be1", size = 2982934, upload-time = "2025-09-17T14:54:11.302Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ec/726b146d1d3aad82e03d2e1e1507048ab6072f906e83f97f40667866e582/gevent-25.9.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0adb937f13e5fb90cca2edf66d8d7e99d62a299687400ce2edee3f3504009356", size = 1813982, upload-time = "2025-09-17T15:41:28.506Z" }, - { url = "https://files.pythonhosted.org/packages/35/5d/5f83f17162301662bd1ce702f8a736a8a8cac7b7a35e1d8b9866938d1f9d/gevent-25.9.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:427f869a2050a4202d93cf7fd6ab5cffb06d3e9113c10c967b6e2a0d45237cb8", size = 1894902, upload-time = "2025-09-17T15:49:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/83/cd/cf5e74e353f60dab357829069ffc300a7bb414c761f52cf8c0c6e9728b8d/gevent-25.9.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c049880175e8c93124188f9d926af0a62826a3b81aa6d3074928345f8238279e", size = 1861792, upload-time = "2025-09-17T15:49:23.279Z" }, - { url = "https://files.pythonhosted.org/packages/dd/65/b9a4526d4a4edce26fe4b3b993914ec9dc64baabad625a3101e51adb17f3/gevent-25.9.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b5a67a0974ad9f24721034d1e008856111e0535f1541499f72a733a73d658d1c", size = 2113215, upload-time = "2025-09-17T15:15:16.34Z" }, - { url = "https://files.pythonhosted.org/packages/e5/be/7d35731dfaf8370795b606e515d964a0967e129db76ea7873f552045dd39/gevent-25.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1d0f5d8d73f97e24ea8d24d8be0f51e0cf7c54b8021c1fddb580bf239474690f", size = 1833449, upload-time = "2025-09-17T15:52:43.75Z" }, - { url = "https://files.pythonhosted.org/packages/65/58/7bc52544ea5e63af88c4a26c90776feb42551b7555a1c89c20069c168a3f/gevent-25.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ddd3ff26e5c4240d3fbf5516c2d9d5f2a998ef87cfb73e1429cfaeaaec860fa6", size = 2176034, upload-time = "2025-09-17T15:24:15.676Z" }, - { url = "https://files.pythonhosted.org/packages/c2/69/a7c4ba2ffbc7c7dbf6d8b4f5d0f0a421f7815d229f4909854266c445a3d4/gevent-25.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:bb63c0d6cb9950cc94036a4995b9cc4667b8915366613449236970f4394f94d7", size = 1703019, upload-time = "2025-09-17T19:30:55.272Z" }, ] [[package]] @@ -344,14 +322,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" }, + { url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" }, { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, - { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, - { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, - { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, - { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, - { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, - { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, ] [[package]] @@ -396,11 +369,11 @@ wheels = [ [[package]] name = "idna" -version = "3.10" +version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] @@ -447,30 +420,32 @@ wheels = [ [[package]] name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, ] [[package]] @@ -496,47 +471,47 @@ wheels = [ [[package]] name = "multidict" -version = "6.6.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/5d/e1db626f64f60008320aab00fbe4f23fc3300d75892a3381275b3d284580/multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e", size = 75848, upload-time = "2025-08-11T12:07:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657", size = 45060, upload-time = "2025-08-11T12:07:21.163Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da", size = 43269, upload-time = "2025-08-11T12:07:22.392Z" }, - { url = "https://files.pythonhosted.org/packages/dc/31/d54eb0c62516776f36fe67f84a732f97e0b0e12f98d5685bebcc6d396910/multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa", size = 237158, upload-time = "2025-08-11T12:07:23.636Z" }, - { url = "https://files.pythonhosted.org/packages/c4/1c/8a10c1c25b23156e63b12165a929d8eb49a6ed769fdbefb06e6f07c1e50d/multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f", size = 257076, upload-time = "2025-08-11T12:07:25.049Z" }, - { url = "https://files.pythonhosted.org/packages/ad/86/90e20b5771d6805a119e483fd3d1e8393e745a11511aebca41f0da38c3e2/multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0", size = 240694, upload-time = "2025-08-11T12:07:26.458Z" }, - { url = "https://files.pythonhosted.org/packages/e7/49/484d3e6b535bc0555b52a0a26ba86e4d8d03fd5587d4936dc59ba7583221/multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879", size = 266350, upload-time = "2025-08-11T12:07:27.94Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b4/aa4c5c379b11895083d50021e229e90c408d7d875471cb3abf721e4670d6/multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a", size = 267250, upload-time = "2025-08-11T12:07:29.303Z" }, - { url = "https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f", size = 254900, upload-time = "2025-08-11T12:07:30.764Z" }, - { url = "https://files.pythonhosted.org/packages/17/38/58b27fed927c07035abc02befacab42491e7388ca105e087e6e0215ead64/multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5", size = 252355, upload-time = "2025-08-11T12:07:32.205Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a1/dad75d23a90c29c02b5d6f3d7c10ab36c3197613be5d07ec49c7791e186c/multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438", size = 250061, upload-time = "2025-08-11T12:07:33.623Z" }, - { url = "https://files.pythonhosted.org/packages/b8/1a/ac2216b61c7f116edab6dc3378cca6c70dc019c9a457ff0d754067c58b20/multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e", size = 249675, upload-time = "2025-08-11T12:07:34.958Z" }, - { url = "https://files.pythonhosted.org/packages/d4/79/1916af833b800d13883e452e8e0977c065c4ee3ab7a26941fbfdebc11895/multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7", size = 261247, upload-time = "2025-08-11T12:07:36.588Z" }, - { url = "https://files.pythonhosted.org/packages/c5/65/d1f84fe08ac44a5fc7391cbc20a7cedc433ea616b266284413fd86062f8c/multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812", size = 257960, upload-time = "2025-08-11T12:07:39.735Z" }, - { url = "https://files.pythonhosted.org/packages/13/b5/29ec78057d377b195ac2c5248c773703a6b602e132a763e20ec0457e7440/multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a", size = 250078, upload-time = "2025-08-11T12:07:41.525Z" }, - { url = "https://files.pythonhosted.org/packages/c4/0e/7e79d38f70a872cae32e29b0d77024bef7834b0afb406ddae6558d9e2414/multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69", size = 41708, upload-time = "2025-08-11T12:07:43.405Z" }, - { url = "https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf", size = 45912, upload-time = "2025-08-11T12:07:45.082Z" }, - { url = "https://files.pythonhosted.org/packages/c7/87/3bac136181e271e29170d8d71929cdeddeb77f3e8b6a0c08da3a8e9da114/multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605", size = 43076, upload-time = "2025-08-11T12:07:46.746Z" }, - { url = "https://files.pythonhosted.org/packages/64/94/0a8e63e36c049b571c9ae41ee301ada29c3fee9643d9c2548d7d558a1d99/multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb", size = 82812, upload-time = "2025-08-11T12:07:48.402Z" }, - { url = "https://files.pythonhosted.org/packages/25/1a/be8e369dfcd260d2070a67e65dd3990dd635cbd735b98da31e00ea84cd4e/multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e", size = 48313, upload-time = "2025-08-11T12:07:49.679Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/dd4ade298674b2f9a7b06a32c94ffbc0497354df8285f27317c66433ce3b/multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f", size = 46777, upload-time = "2025-08-11T12:07:51.318Z" }, - { url = "https://files.pythonhosted.org/packages/89/db/98aa28bc7e071bfba611ac2ae803c24e96dd3a452b4118c587d3d872c64c/multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773", size = 229321, upload-time = "2025-08-11T12:07:52.965Z" }, - { url = "https://files.pythonhosted.org/packages/c7/bc/01ddda2a73dd9d167bd85d0e8ef4293836a8f82b786c63fb1a429bc3e678/multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e", size = 249954, upload-time = "2025-08-11T12:07:54.423Z" }, - { url = "https://files.pythonhosted.org/packages/06/78/6b7c0f020f9aa0acf66d0ab4eb9f08375bac9a50ff5e3edb1c4ccd59eafc/multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0", size = 228612, upload-time = "2025-08-11T12:07:55.914Z" }, - { url = "https://files.pythonhosted.org/packages/00/44/3faa416f89b2d5d76e9d447296a81521e1c832ad6e40b92f990697b43192/multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395", size = 257528, upload-time = "2025-08-11T12:07:57.371Z" }, - { url = "https://files.pythonhosted.org/packages/05/5f/77c03b89af0fcb16f018f668207768191fb9dcfb5e3361a5e706a11db2c9/multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45", size = 256329, upload-time = "2025-08-11T12:07:58.844Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e9/ed750a2a9afb4f8dc6f13dc5b67b514832101b95714f1211cd42e0aafc26/multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb", size = 247928, upload-time = "2025-08-11T12:08:01.037Z" }, - { url = "https://files.pythonhosted.org/packages/1f/b5/e0571bc13cda277db7e6e8a532791d4403dacc9850006cb66d2556e649c0/multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5", size = 245228, upload-time = "2025-08-11T12:08:02.96Z" }, - { url = "https://files.pythonhosted.org/packages/f3/a3/69a84b0eccb9824491f06368f5b86e72e4af54c3067c37c39099b6687109/multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141", size = 235869, upload-time = "2025-08-11T12:08:04.746Z" }, - { url = "https://files.pythonhosted.org/packages/a9/9d/28802e8f9121a6a0804fa009debf4e753d0a59969ea9f70be5f5fdfcb18f/multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d", size = 243446, upload-time = "2025-08-11T12:08:06.332Z" }, - { url = "https://files.pythonhosted.org/packages/38/ea/6c98add069b4878c1d66428a5f5149ddb6d32b1f9836a826ac764b9940be/multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d", size = 252299, upload-time = "2025-08-11T12:08:07.931Z" }, - { url = "https://files.pythonhosted.org/packages/3a/09/8fe02d204473e14c0af3affd50af9078839dfca1742f025cca765435d6b4/multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0", size = 246926, upload-time = "2025-08-11T12:08:09.467Z" }, - { url = "https://files.pythonhosted.org/packages/37/3d/7b1e10d774a6df5175ecd3c92bff069e77bed9ec2a927fdd4ff5fe182f67/multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92", size = 243383, upload-time = "2025-08-11T12:08:10.981Z" }, - { url = "https://files.pythonhosted.org/packages/50/b0/a6fae46071b645ae98786ab738447de1ef53742eaad949f27e960864bb49/multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e", size = 47775, upload-time = "2025-08-11T12:08:12.439Z" }, - { url = "https://files.pythonhosted.org/packages/b2/0a/2436550b1520091af0600dff547913cb2d66fbac27a8c33bc1b1bccd8d98/multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4", size = 53100, upload-time = "2025-08-11T12:08:13.823Z" }, - { url = "https://files.pythonhosted.org/packages/97/ea/43ac51faff934086db9c072a94d327d71b7d8b40cd5dcb47311330929ef0/multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad", size = 45501, upload-time = "2025-08-11T12:08:15.173Z" }, - { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" }, + { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" }, + { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" }, + { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" }, + { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" }, + { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" }, + { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" }, + { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" }, + { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" }, + { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" }, + { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" }, + { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" }, + { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" }, + { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" }, + { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, ] [[package]] @@ -594,43 +569,41 @@ wheels = [ [[package]] name = "propcache" -version = "0.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, - { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, - { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, - { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, - { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, - { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, - { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, - { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, - { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, - { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, - { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, - { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, - { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, - { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, - { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, - { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, - { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, - { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, - { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, - { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, - { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] [[package]] @@ -714,24 +687,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] [[package]] @@ -778,15 +733,15 @@ wheels = [ [[package]] name = "rich" -version = "14.1.0" +version = "14.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, ] [[package]] @@ -944,12 +899,12 @@ wheels = [ [[package]] name = "sphinx-multiversion-scylla" -version = "0.3.2" +version = "0.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/48/aaa77dc9fedb1671a1729b731f4b359d594bc6af689e230027a6e299d661/sphinx_multiversion_scylla-0.3.2.tar.gz", hash = "sha256:f415311273228f4f766c36256503da8e2ce01f9d13423f3fcee3160d6284852b", size = 11442, upload-time = "2024-08-02T13:06:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/d2/f0700d3bc26d13f8161ccfba5ebeb9a69ee41d162f7e31855841fd72dd3a/sphinx_multiversion_scylla-0.3.3.tar.gz", hash = "sha256:a8fa7f4dc685bdb40a57b4abbe4ab0f15908302de7066c50e9ad234a6500d37f", size = 11529, upload-time = "2025-11-17T09:56:44.961Z" } [[package]] name = "sphinx-notfound-page" @@ -965,7 +920,7 @@ wheels = [ [[package]] name = "sphinx-scylladb-theme" -version = "1.8.8" +version = "1.8.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -978,26 +933,26 @@ dependencies = [ { name = "sphinx-tabs" }, { name = "sphinxcontrib-mermaid" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/83/a1cd84f0d0015dab4f9cb09488b89d12f19941098c595406068c23f5e2fa/sphinx_scylladb_theme-1.8.8.tar.gz", hash = "sha256:15af599424f8b2ddbf14644b267c0bd31bc3fbbd64bca5b97d7b31bdb84d2c3d", size = 1617725, upload-time = "2025-09-02T18:59:44.533Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/9e/1578c6a27e0f6844694aaff7c3b319db8175b4be8566b0b7962e37b2fd0e/sphinx_scylladb_theme-1.8.9.tar.gz", hash = "sha256:ab7cda4c10a0d067c5c3a45f7b1f68cb8ebefe135a0be0738bfa282a344769b6", size = 1620650, upload-time = "2025-11-21T16:10:50.359Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/22/3f1aa6399045b93362786c2255068c7aef70385d4471d214dbec8fecbeba/sphinx_scylladb_theme-1.8.8-py3-none-any.whl", hash = "sha256:9b37f58b745bfc818b6f0869cbcc414a3ad6658023b22a6abbec44da5c09cf80", size = 1659430, upload-time = "2025-09-02T18:59:42.733Z" }, + { url = "https://files.pythonhosted.org/packages/62/1d/32d0dad6c0814f4051a37152ed4d98350cfeb4cf6c91426ce64605e75e4a/sphinx_scylladb_theme-1.8.9-py3-none-any.whl", hash = "sha256:f8649a7753a29494fd2b417d1cb855035dddb9ebd498ea033fd73f5f9338271e", size = 1662403, upload-time = "2025-11-21T16:10:46.781Z" }, ] [[package]] name = "sphinx-sitemap" -version = "2.8.0" +version = "2.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx-last-updated-by-git" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/0e/e249fdd17c0530c8260191f0020556862b243fa718cf0e6b28d956eafb2c/sphinx_sitemap-2.8.0.tar.gz", hash = "sha256:749d7184a0c7b73d486a232b54b5c1b38a0e2d6f18cf19fb1b033b8162b44a82", size = 6829, upload-time = "2025-08-12T04:54:24.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/17/56fe0f65e3567f829b2b4153a622be1d4b222b781e0d90d7db5a7738f30f/sphinx_sitemap-2.9.0.tar.gz", hash = "sha256:70f97bcdf444e3d68e118355cf82a1f54c4d3c03d651cd17fe87398b26e25e21", size = 6978, upload-time = "2025-10-06T00:24:00.036Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/ab/3bc6f9ee09b5fd5ae2e5ae1febf41a2ed2bdde452a41c06e78fec0296b5c/sphinx_sitemap-2.8.0-py3-none-any.whl", hash = "sha256:332042cd5b9385f61ec2861dfd550d9bccbdfcff86f6b68c7072cf40c9f16363", size = 6167, upload-time = "2025-08-12T04:54:23.479Z" }, + { url = "https://files.pythonhosted.org/packages/e4/94/3c57e8b1985e755c48972e2ecd59526d4bf0b52a1fe805bc52a8e98cb92d/sphinx_sitemap-2.9.0-py3-none-any.whl", hash = "sha256:f1f1d3a9ad012ba17a7ef0b560d303bff2d0db26647567d6e810bcc754466664", size = 6218, upload-time = "2025-10-06T00:23:58.778Z" }, ] [[package]] name = "sphinx-substitution-extensions" -version = "2025.6.6" +version = "2025.11.17" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beartype" }, @@ -1005,9 +960,9 @@ dependencies = [ { name = "myst-parser" }, { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/92/86cfb27705791652a54d42f35409a5128fc29395fbe38039222fe15116ff/sphinx_substitution_extensions-2025.6.6.tar.gz", hash = "sha256:241ddb9f88962422a8b436243d2601f9d94616e94b520ce1a6a528d9a3b7804a", size = 22109, upload-time = "2025-06-06T20:53:19.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/53/feccf1b607de2aef65c6411b4b4a34a91aa8daf397e77258a7774f9d1990/sphinx_substitution_extensions-2025.11.17.tar.gz", hash = "sha256:aae17f8db9efc3d454a304373ae3df763f8739e05e0b98d5381db46f6d250b27", size = 30459, upload-time = "2025-11-17T14:34:45.072Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/11/d81677eb3dcd9b130cf97169ebf762170ade3fa676395329d59f9cf1540a/sphinx_substitution_extensions-2025.6.6-py2.py3-none-any.whl", hash = "sha256:96206ba7a2cdf43237edec81f87770a5683a91a1de3bf93f7a22309509ecbfd8", size = 7949, upload-time = "2025-06-06T20:53:18.341Z" }, + { url = "https://files.pythonhosted.org/packages/82/df/7e9cd4775c2782c894741c9274cc4c596ad02ab31257e5a5417f0a6af893/sphinx_substitution_extensions-2025.11.17-py2.py3-none-any.whl", hash = "sha256:ac18455bdc8324b337b0fe7498c1c0d0b1cb65c74d131459be4dea9edb6abbef", size = 8741, upload-time = "2025-11-17T14:34:43.66Z" }, ] [[package]] @@ -1062,15 +1017,15 @@ wheels = [ [[package]] name = "sphinxcontrib-mermaid" -version = "1.0.0" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/97/69/bf039237ad260073e8c02f820b3e00dc34f3a2de20aff7861e6b19d2f8c5/sphinxcontrib_mermaid-1.0.0.tar.gz", hash = "sha256:2e8ab67d3e1e2816663f9347d026a8dee4a858acdd4ad32dd1c808893db88146", size = 15153, upload-time = "2024-10-12T16:33:03.863Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/9c/3eefdb1bbb5ec8094ff4baeb9fd310e8320a43311aebe230cf4aaef3c942/sphinxcontrib_mermaid-1.1.0.tar.gz", hash = "sha256:e674e04503956de8263163e0c8f446d99f9d60455cb395f64e998f1137f04f8e", size = 18766, upload-time = "2025-11-19T19:51:09.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/c8/784b9ac6ea08aa594c1a4becbd0dbe77186785362e31fd633b8c6ae0197a/sphinxcontrib_mermaid-1.0.0-py3-none-any.whl", hash = "sha256:60b72710ea02087f212028feb09711225fbc2e343a10d34822fe787510e1caa3", size = 9597, upload-time = "2024-10-12T16:33:02.303Z" }, + { url = "https://files.pythonhosted.org/packages/25/89/899495013b11b09e687310dc76bab626babdbee2dd0e6640df0942090ab4/sphinxcontrib_mermaid-1.1.0-py3-none-any.whl", hash = "sha256:1133b4c00f33aedb76e37bfbd1a4280a0104c0a836da642a113eabdf261bdd0c", size = 14898, upload-time = "2025-11-19T19:51:06.036Z" }, ] [[package]] @@ -1093,14 +1048,14 @@ wheels = [ [[package]] name = "starlette" -version = "0.48.0" +version = "0.50.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949, upload-time = "2025-09-13T08:41:05.699Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" }, + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, ] [[package]] @@ -1124,16 +1079,16 @@ wheels = [ [[package]] name = "trove-classifiers" -version = "2025.9.11.17" +version = "2025.11.14.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/9a/778622bc06632529817c3c524c82749a112603ae2bbcf72ee3eb33a2c4f1/trove_classifiers-2025.9.11.17.tar.gz", hash = "sha256:931ca9841a5e9c9408bc2ae67b50d28acf85bef56219b56860876dd1f2d024dd", size = 16975, upload-time = "2025-09-11T17:07:50.97Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/a9/880cccf76af9e7b322112f52e4e2dbb3534cbe671197b8f443a42189dfc7/trove_classifiers-2025.11.14.15.tar.gz", hash = "sha256:6b60f49d40bbd895bc61d8dc414fc2f2286d70eb72ed23548db8cf94f62804ca", size = 16995, upload-time = "2025-11-14T15:23:13.78Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl", hash = "sha256:5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd", size = 14158, upload-time = "2025-09-11T17:07:49.886Z" }, + { url = "https://files.pythonhosted.org/packages/49/f6/73c4aa003d1237ee9bea8a46f49dc38c45dfe95af4f0da7e60678d388011/trove_classifiers-2025.11.14.15-py3-none-any.whl", hash = "sha256:d1dac259c1e908939862e3331177931c6df0a37af2c1a8debcc603d9115fcdd9", size = 14191, upload-time = "2025-11-14T15:23:12.467Z" }, ] [[package]] name = "typer" -version = "0.19.2" +version = "0.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1141,9 +1096,9 @@ dependencies = [ { name = "shellingham" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/ca/950278884e2ca20547ff3eb109478c6baf6b8cf219318e6bc4f666fad8e8/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca", size = 104755, upload-time = "2025-09-23T09:47:48.256Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/28/7c85c8032b91dbe79725b6f17d2fffc595dff06a35c7a30a37bef73a1ab4/typer-0.20.0.tar.gz", hash = "sha256:1aaf6494031793e4876fb0bacfa6a912b551cf43c1e63c800df8b1a866720c37", size = 106492, upload-time = "2025-10-20T17:03:49.445Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9", size = 46748, upload-time = "2025-09-23T09:47:46.777Z" }, + { url = "https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl", hash = "sha256:5b463df6793ec1dca6213a3cf4c0f03bc6e322ac5e16e13ddd622a889489784a", size = 47028, upload-time = "2025-10-20T17:03:47.617Z" }, ] [[package]] @@ -1166,69 +1121,49 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.37.0" +version = "0.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/57/1616c8274c3442d802621abf5deb230771c7a0fec9414cb6763900eb3868/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13", size = 80367, upload-time = "2025-09-23T13:33:47.486Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/cd/584a2ceb5532af99dd09e50919e3615ba99aa127e9850eafe5f31ddfdb9a/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c", size = 67976, upload-time = "2025-09-23T13:33:45.842Z" }, + { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, ] [[package]] name = "watchfiles" -version = "1.1.0" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" }, - { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" }, - { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" }, - { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" }, - { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" }, - { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" }, - { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" }, - { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" }, - { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" }, - { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" }, - { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" }, - { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" }, - { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" }, - { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" }, - { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" }, - { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, - { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, - { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" }, - { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" }, - { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" }, - { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" }, - { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" }, - { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" }, - { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" }, - { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" }, - { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" }, - { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" }, - { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" }, - { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" }, - { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, ] [[package]] @@ -1253,74 +1188,69 @@ wheels = [ [[package]] name = "yarl" -version = "1.20.1" +version = "1.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, - { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, - { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, - { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, - { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, - { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, - { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, - { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, - { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, - { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, - { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, - { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, - { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, - { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, - { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, - { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, - { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, - { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, - { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, - { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, - { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, - { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, - { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, - { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, - { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, - { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, - { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, - { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, - { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, - { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, ] [[package]] name = "zope-event" -version = "6.0" +version = "6.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c2/d8/9c8b0c6bb1db09725395618f68d3b8a08089fca0aed28437500caaf713ee/zope_event-6.0.tar.gz", hash = "sha256:0ebac894fa7c5f8b7a89141c272133d8c1de6ddc75ea4b1f327f00d1f890df92", size = 18731, upload-time = "2025-09-12T07:10:13.551Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/33/d3eeac228fc14de76615612ee208be2d8a5b5b0fada36bf9b62d6b40600c/zope_event-6.1.tar.gz", hash = "sha256:6052a3e0cb8565d3d4ef1a3a7809336ac519bc4fe38398cb8d466db09adef4f0", size = 18739, upload-time = "2025-11-07T08:05:49.934Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/b5/1abb5a8b443314c978617bf46d5d9ad648bdf21058074e817d7efbb257db/zope_event-6.0-py3-none-any.whl", hash = "sha256:6f0922593407cc673e7d8766b492c519f91bdc99f3080fe43dcec0a800d682a3", size = 6409, upload-time = "2025-09-12T07:10:12.316Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b0/956902e5e1302f8c5d124e219c6bf214e2649f92ad5fce85b05c039a04c9/zope_event-6.1-py3-none-any.whl", hash = "sha256:0ca78b6391b694272b23ec1335c0294cc471065ed10f7f606858fc54566c25a0", size = 6414, upload-time = "2025-11-07T08:05:48.874Z" }, ] [[package]] name = "zope-interface" -version = "8.0.1" +version = "8.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/3a/7fcf02178b8fad0a51e67e32765cd039ae505d054d744d76b8c2bbcba5ba/zope_interface-8.0.1.tar.gz", hash = "sha256:eba5610d042c3704a48222f7f7c6ab5b243ed26f917e2bc69379456b115e02d1", size = 253746, upload-time = "2025-09-25T05:55:51.285Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/c9/5ec8679a04d37c797d343f650c51ad67d178f0001c363e44b6ac5f97a9da/zope_interface-8.1.1.tar.gz", hash = "sha256:51b10e6e8e238d719636a401f44f1e366146912407b58453936b781a19be19ec", size = 254748, upload-time = "2025-11-15T08:32:52.404Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/dc/3c12fca01c910c793d636ffe9c0984e0646abaf804e44552070228ed0ede/zope_interface-8.0.1-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:c7cc027fc5c61c5d69e5080c30b66382f454f43dc379c463a38e78a9c6bab71a", size = 208992, upload-time = "2025-09-25T05:58:40.712Z" }, - { url = "https://files.pythonhosted.org/packages/46/71/6127b7282a3e380ca927ab2b40778a9c97935a4a57a2656dadc312db5f30/zope_interface-8.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fcf9097ff3003b7662299f1c25145e15260ec2a27f9a9e69461a585d79ca8552", size = 209051, upload-time = "2025-09-25T05:58:42.182Z" }, - { url = "https://files.pythonhosted.org/packages/56/86/4387a9f951ee18b0e41fda77da77d59c33e59f04660578e2bad688703e64/zope_interface-8.0.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6d965347dd1fb9e9a53aa852d4ded46b41ca670d517fd54e733a6b6a4d0561c2", size = 259223, upload-time = "2025-09-25T05:58:23.191Z" }, - { url = "https://files.pythonhosted.org/packages/61/08/ce60a114466abc067c68ed41e2550c655f551468ae17b4b17ea360090146/zope_interface-8.0.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9a3b8bb77a4b89427a87d1e9eb969ab05e38e6b4a338a9de10f6df23c33ec3c2", size = 264690, upload-time = "2025-09-25T05:58:15.052Z" }, - { url = "https://files.pythonhosted.org/packages/36/9a/62a9ba3a919594605a07c34eee3068659bbd648e2fa0c4a86d876810b674/zope_interface-8.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:87e6b089002c43231fb9afec89268391bcc7a3b66e76e269ffde19a8112fb8d5", size = 264201, upload-time = "2025-09-25T06:26:27.797Z" }, - { url = "https://files.pythonhosted.org/packages/da/06/8fe88bd7edef60566d21ef5caca1034e10f6b87441ea85de4bbf9ea74768/zope_interface-8.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:64a43f5280aa770cbafd0307cb3d1ff430e2a1001774e8ceb40787abe4bb6658", size = 212273, upload-time = "2025-09-25T06:00:25.398Z" }, + { url = "https://files.pythonhosted.org/packages/85/81/3c3b5386ce4fba4612fd82ffb8a90d76bcfea33ca2b6399f21e94d38484f/zope_interface-8.1.1-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:84f9be6d959640de9da5d14ac1f6a89148b16da766e88db37ed17e936160b0b1", size = 209046, upload-time = "2025-11-15T08:37:01.473Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e3/32b7cb950c4c4326b3760a8e28e5d6f70ad15f852bfd8f9364b58634f74b/zope_interface-8.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:531fba91dcb97538f70cf4642a19d6574269460274e3f6004bba6fe684449c51", size = 209104, upload-time = "2025-11-15T08:37:02.887Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3d/c4c68e1752a5f5effa2c1f5eaa4fea4399433c9b058fb7000a34bfb1c447/zope_interface-8.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:fc65f5633d5a9583ee8d88d1f5de6b46cd42c62e47757cfe86be36fb7c8c4c9b", size = 259277, upload-time = "2025-11-15T08:37:04.389Z" }, + { url = "https://files.pythonhosted.org/packages/fd/5b/cf4437b174af7591ee29bbad728f620cab5f47bd6e9c02f87d59f31a0dda/zope_interface-8.1.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efef80ddec4d7d99618ef71bc93b88859248075ca2e1ae1c78636654d3d55533", size = 264742, upload-time = "2025-11-15T08:37:05.613Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0e/0cf77356862852d3d3e62db9aadae5419a1a7d89bf963b219745283ab5ca/zope_interface-8.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49aad83525eca3b4747ef51117d302e891f0042b06f32aa1c7023c62642f962b", size = 264252, upload-time = "2025-11-15T08:37:07.035Z" }, + { url = "https://files.pythonhosted.org/packages/8a/10/2af54aa88b2fa172d12364116cc40d325fedbb1877c3bb031b0da6052855/zope_interface-8.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:71cf329a21f98cb2bd9077340a589e316ac8a415cac900575a32544b3dffcb98", size = 212330, upload-time = "2025-11-15T08:37:08.14Z" }, ] From 946c04157506abae27dda73ec8f02e08b01fc726 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 27 Nov 2025 08:39:37 -0400 Subject: [PATCH 151/298] Rename connection_metadata to client_routes It was decided to rename table and events for better clarity. --- cassandra/protocol.py | 4 ++-- tests/integration/standard/test_control_connection.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cassandra/protocol.py b/cassandra/protocol.py index 8fee0db101..e574965de8 100644 --- a/cassandra/protocol.py +++ b/cassandra/protocol.py @@ -957,7 +957,7 @@ def send_body(self, f, protocol_version): 'TOPOLOGY_CHANGE', 'STATUS_CHANGE', 'SCHEMA_CHANGE', - 'CONNECTION_METADATA_CHANGE' + 'CLIENT_ROUTES_CHANGE' )) @@ -989,7 +989,7 @@ def recv_body(cls, f, protocol_version, *args): raise NotSupportedError('Unknown event type %r' % event_type) @classmethod - def recv_connection_metadata_change(cls, f, protocol_version): + def recv_client_routes_change(cls, f, protocol_version): # "UPDATE_NODES" change_type = read_string(f) connection_ids = read_stringlist(f) diff --git a/tests/integration/standard/test_control_connection.py b/tests/integration/standard/test_control_connection.py index 350c6a5ffe..206945f0b3 100644 --- a/tests/integration/standard/test_control_connection.py +++ b/tests/integration/standard/test_control_connection.py @@ -132,9 +132,9 @@ def test_control_connection_port_discovery(self): assert 9042 == host.broadcast_rpc_port assert 7000 == host.broadcast_port - @xfail_scylla_version_lt(reason='scylladb/scylladb#26992 - system.connection_metadata is not yet supported', + @xfail_scylla_version_lt(reason='scylladb/scylladb#26992 - system.client_routes is not yet supported', oss_scylla_version="7.0", ent_scylla_version="2025.4.0") - def test_connection_metadata_change_event(self): + def test_client_routes_change_event(self): cluster = TestCluster() # Establish control connection @@ -157,7 +157,7 @@ def on_event(event): finally: flag.set() - cluster.control_connection._connection.register_watchers({"CONNECTION_METADATA_CHANGE": on_event}) + cluster.control_connection._connection.register_watchers({"CLIENT_ROUTES_CHANGE": on_event}) try: payload = [ @@ -185,7 +185,7 @@ def on_event(event): } ] response = requests.post( - "http://" + cluster.contact_points[0] + ":10000/v2/connection-metadata", + "http://" + cluster.contact_points[0] + ":10000/v2/client-routes", json=payload, headers={ "Content-Type": "application/json", From ba3aeaa3ddc5c98f0b94d7ae32fd4868fa2e07cd Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 27 Nov 2025 19:05:58 -0400 Subject: [PATCH 152/298] Release 3.29.6: changelog, version and documentation --- CHANGELOG.rst | 15 +++++++++++++++ cassandra/__init__.py | 2 +- docs/conf.py | 3 ++- docs/installation.rst | 4 ++-- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index de57c7b126..50fe20ab7d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,18 @@ +3.29.6 +====== +November 27, 2025 + +* Rename connection_metadata to client_routes (#608) +* TokenAwarePolicy: enable shuffling by default (#478) +* Add support of LWT flag for BatchStatement (#606) +* Add support of CONNECTION_METADATA_CHANGE event (#601) +* Add LWT support (#584) +* Add support for Python 3.14 (#566) +* Fix dict handling in pool and metrics (#595) +* Remove serverless code (#590) +* tests: drop `sure` package (#592) +* compression: better handle configuration problems (#585) + 3.29.5 ====== November 5, 2025 diff --git a/cassandra/__init__.py b/cassandra/__init__.py index 633e96d895..e07cbcc8ce 100644 --- a/cassandra/__init__.py +++ b/cassandra/__init__.py @@ -23,7 +23,7 @@ def emit(self, record): logging.getLogger('cassandra').addHandler(NullHandler()) -__version_info__ = (3, 29, 5) +__version_info__ = (3, 29, 6) __version__ = '.'.join(map(str, __version_info__)) diff --git a/docs/conf.py b/docs/conf.py index 1f3a54659c..4a20cc449b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,10 +26,11 @@ '3.29.3-scylla', '3.29.4-scylla', '3.29.5-scylla', + '3.29.6-scylla', ] BRANCHES = ['master'] # Set the latest version. -LATEST_VERSION = '3.29.5-scylla' +LATEST_VERSION = '3.29.6-scylla' # Set which versions are not released yet. UNSTABLE_VERSIONS = ['master'] # Set which versions are deprecated diff --git a/docs/installation.rst b/docs/installation.rst index 904e1fa2b0..f67df242a0 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -26,7 +26,7 @@ To check if the installation was successful, you can run:: python -c 'import cassandra; print(cassandra.__version__)' -It should print something like "3.29.5". +It should print something like "3.29.6". (*Optional*) Compression Support -------------------------------- @@ -208,7 +208,7 @@ through `Homebrew `_. For example, on Mac OS X:: $ brew install libev -The libev extension can now be built for Windows as of Python driver version 3.29.5. You can +The libev extension can now be built for Windows as of Python driver version 3.29.6. You can install libev using any Windows package manager. For example, to install using `vcpkg `_: $ vcpkg install libev From 592ebf7ffa1524e056b23065ff399e3d4d2b58b2 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 7 Dec 2025 05:20:41 -0400 Subject: [PATCH 153/298] Make it compression=None a valid case This regression was cased by 78f554236f0a7f1a6290e1616b1788feab6d20fe And missed during review. compression=None is the same as compression=False, but it is valid now. --- cassandra/cluster.py | 9 ++++---- tests/unit/test_cluster.py | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index fcf4a0e440..66bf7c7049 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -685,7 +685,7 @@ class Cluster(object): Used for testing new protocol features incrementally before the new version is complete. """ - compression: Union[bool, str] = True + compression: Union[bool, str, None] = True """ Controls compression for communications between the driver and Cassandra. If left as the default of :const:`True`, either lz4 or snappy compression @@ -695,7 +695,7 @@ class Cluster(object): You may also set this to 'snappy' or 'lz4' to request that specific compression type. - Setting this to :const:`False` disables compression. + Setting this to :const:`False` or :const:`None` disables compression. """ _application_info: Optional[ApplicationInfoBase] = None @@ -1172,7 +1172,7 @@ def token_metadata_enabled(self, enabled): def __init__(self, contact_points=_NOT_SET, port=9042, - compression: Union[bool, str] = True, + compression: Union[bool, str, None] = True, auth_provider=None, load_balancing_policy=None, reconnection_policy=None, @@ -1285,7 +1285,8 @@ def __init__(self, self._resolve_hostnames() - if isinstance(compression, bool): + if isinstance(compression, bool) or compression is None: + compression = bool(compression) if compression and not locally_supported_compressions: log.error( "Compression is enabled, but no compression libraries are available. " diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 383a4de7c8..f3efed9f54 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -122,6 +122,51 @@ def test_port_range(self): with pytest.raises(ValueError): cluster = Cluster(contact_points=['127.0.0.1'], port=invalid_port) + def test_compression_autodisabled_without_libraries(self): + with patch.dict('cassandra.cluster.locally_supported_compressions', {}, clear=True): + with patch('cassandra.cluster.log') as patched_logger: + cluster = Cluster(compression=True) + + patched_logger.error.assert_called_once() + assert cluster.compression is False + + def test_compression_validates_requested_algorithm(self): + with patch.dict('cassandra.cluster.locally_supported_compressions', {}, clear=True): + with pytest.raises(ValueError): + Cluster(compression='lz4') + + with patch.dict('cassandra.cluster.locally_supported_compressions', {'lz4': ('c', 'd')}, clear=True): + with patch('cassandra.cluster.log') as patched_logger: + cluster = Cluster(compression='lz4') + + patched_logger.error.assert_not_called() + assert cluster.compression == 'lz4' + + def test_compression_type_validation(self): + with pytest.raises(TypeError): + Cluster(compression=123) + + def test_connection_factory_passes_compression_kwarg(self): + endpoint = Mock(address='127.0.0.1') + scenarios = [ + ({}, True, False), + ({'snappy': ('c', 'd')}, True, True), + ({'lz4': ('c', 'd')}, 'lz4', 'lz4'), + ({'lz4': ('c', 'd'), 'snappy': ('c', 'd')}, False, False), + ({'lz4': ('c', 'd'), 'snappy': ('c', 'd')}, None, False), + ] + + for supported, configured, expected in scenarios: + with patch.dict('cassandra.cluster.locally_supported_compressions', supported, clear=True): + with patch.object(Cluster.connection_class, 'factory', autospec=True, return_value='connection') as factory: + cluster = Cluster(compression=configured) + conn = cluster.connection_factory(endpoint) + + assert conn == 'connection' + assert factory.call_count == 1 + assert factory.call_args.kwargs['compression'] == expected + assert cluster.compression == expected + class SchedulerTest(unittest.TestCase): # TODO: this suite could be expanded; for now just adding a test covering a ticket From 89f1a12b0a962b7f2583ae9957af42f9d8b3b851 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 8 Dec 2025 17:43:59 -0400 Subject: [PATCH 154/298] Release 3.29.7: changelog, version and documentation --- CHANGELOG.rst | 8 ++++++++ cassandra/__init__.py | 2 +- docs/conf.py | 3 ++- docs/installation.rst | 4 ++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 50fe20ab7d..82c84ccc51 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,11 @@ +3.29.7 +====== +December 08, 2025 + +Bug Fixes +--------- +* Make compression=None a valid case (#610) + 3.29.6 ====== November 27, 2025 diff --git a/cassandra/__init__.py b/cassandra/__init__.py index e07cbcc8ce..88fbb11f88 100644 --- a/cassandra/__init__.py +++ b/cassandra/__init__.py @@ -23,7 +23,7 @@ def emit(self, record): logging.getLogger('cassandra').addHandler(NullHandler()) -__version_info__ = (3, 29, 6) +__version_info__ = (3, 29, 7) __version__ = '.'.join(map(str, __version_info__)) diff --git a/docs/conf.py b/docs/conf.py index 4a20cc449b..1b0361db39 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,10 +27,11 @@ '3.29.4-scylla', '3.29.5-scylla', '3.29.6-scylla', + '3.29.7-scylla', ] BRANCHES = ['master'] # Set the latest version. -LATEST_VERSION = '3.29.6-scylla' +LATEST_VERSION = '3.29.7-scylla' # Set which versions are not released yet. UNSTABLE_VERSIONS = ['master'] # Set which versions are deprecated diff --git a/docs/installation.rst b/docs/installation.rst index f67df242a0..ee227618ef 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -26,7 +26,7 @@ To check if the installation was successful, you can run:: python -c 'import cassandra; print(cassandra.__version__)' -It should print something like "3.29.6". +It should print something like "3.29.7". (*Optional*) Compression Support -------------------------------- @@ -208,7 +208,7 @@ through `Homebrew `_. For example, on Mac OS X:: $ brew install libev -The libev extension can now be built for Windows as of Python driver version 3.29.6. You can +The libev extension can now be built for Windows as of Python driver version 3.29.7. You can install libev using any Windows package manager. For example, to install using `vcpkg `_: $ vcpkg install libev From 777ef58cff1c22cbad5fdf359c44416e159643bb Mon Sep 17 00:00:00 2001 From: David Garcia Date: Tue, 9 Dec 2025 10:30:55 +0000 Subject: [PATCH 155/298] chore: update docs theme to 1.8.10 --- docs/Makefile | 2 +- docs/uv.lock | 88 +++++++++++++++++++++++---------------------------- 2 files changed, 40 insertions(+), 50 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 5c848a8769..09512be470 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -24,7 +24,7 @@ all: dirhtml .PHONY: update update: - $(UV) update + $(UV) lock --upgrade # Clean commands .PHONY: pristine diff --git a/docs/uv.lock b/docs/uv.lock index 6715944669..4d40698dcc 100644 --- a/docs/uv.lock +++ b/docs/uv.lock @@ -76,15 +76,14 @@ wheels = [ [[package]] name = "anyio" -version = "4.11.0" +version = "4.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, - { name = "sniffio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, ] [[package]] @@ -116,24 +115,24 @@ wheels = [ [[package]] name = "beartype" -version = "0.22.6" +version = "0.22.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/e2/105ceb1704cb80fe4ab3872529ab7b6f365cf7c74f725e6132d0efcf1560/beartype-0.22.6.tar.gz", hash = "sha256:97fbda69c20b48c5780ac2ca60ce3c1bb9af29b3a1a0216898ffabdd523e48f4", size = 1588975, upload-time = "2025-11-20T04:47:14.736Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/1d/794ae2acaa67c8b216d91d5919da2606c2bb14086849ffde7f5555f3a3a5/beartype-0.22.8.tar.gz", hash = "sha256:b19b21c9359722ee3f7cc433f063b3e13997b27ae8226551ea5062e621f61165", size = 1602262, upload-time = "2025-12-03T05:11:10.766Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/c9/ceecc71fe2c9495a1d8e08d44f5f31f5bca1350d5b2e27a4b6265424f59e/beartype-0.22.6-py3-none-any.whl", hash = "sha256:0584bc46a2ea2a871509679278cda992eadde676c01356ab0ac77421f3c9a093", size = 1324807, upload-time = "2025-11-20T04:47:11.837Z" }, + { url = "https://files.pythonhosted.org/packages/14/2a/fbcbf5a025d3e71ddafad7efd43e34ec4362f4d523c3c471b457148fb211/beartype-0.22.8-py3-none-any.whl", hash = "sha256:b832882d04e41a4097bab9f63e6992bc6de58c414ee84cba9b45b67314f5ab2e", size = 1331895, upload-time = "2025-12-03T05:11:08.373Z" }, ] [[package]] name = "beautifulsoup4" -version = "4.14.2" +version = "4.14.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, ] [[package]] @@ -234,15 +233,15 @@ wheels = [ [[package]] name = "eventlet" -version = "0.40.3" +version = "0.40.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dnspython" }, { name = "greenlet" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/4f/1e5227b23aa77d9ea05056b98cf0bf187cca994991060245002b640f9830/eventlet-0.40.3.tar.gz", hash = "sha256:290852db0065d78cec17a821b78c8a51cafb820a792796a354592ae4d5fceeb0", size = 565741, upload-time = "2025-08-27T09:56:16.085Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/d8/f72d8583db7c559445e0e9500a9b9787332370c16980802204a403634585/eventlet-0.40.4.tar.gz", hash = "sha256:69bef712b1be18b4930df6f0c495d2a882bf7b63aa111e7b6eeff461cfcaf26f", size = 565920, upload-time = "2025-11-26T13:57:31.126Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/b4/981362608131dc4ee8de9fdca6a38ef19e3da66ab6a13937bd158882db91/eventlet-0.40.3-py3-none-any.whl", hash = "sha256:e681cae6ee956cfb066a966b5c0541e734cc14879bda6058024104790595ac9d", size = 364333, upload-time = "2025-08-27T09:56:10.774Z" }, + { url = "https://files.pythonhosted.org/packages/22/6d/8e1fa901f6a8307f90e7bd932064e27a0062a4a7a16af38966a9c3293c52/eventlet-0.40.4-py3-none-any.whl", hash = "sha256:6326c6d0bf55810bece151f7a5750207c610f389ba110ffd1541ed6e5215485b", size = 364588, upload-time = "2025-11-26T13:57:29.09Z" }, ] [[package]] @@ -310,21 +309,18 @@ wheels = [ [[package]] name = "greenlet" -version = "3.2.4" +version = "3.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, - { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, - { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, - { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, - { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, - { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, - { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, - { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, - { url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" }, - { url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" }, - { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" }, + { url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" }, + { url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" }, + { url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" }, + { url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" }, + { url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" }, ] [[package]] @@ -789,15 +785,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, -] - [[package]] name = "snowballstemmer" version = "3.0.1" @@ -899,12 +886,15 @@ wheels = [ [[package]] name = "sphinx-multiversion-scylla" -version = "0.3.3" +version = "0.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/d2/f0700d3bc26d13f8161ccfba5ebeb9a69ee41d162f7e31855841fd72dd3a/sphinx_multiversion_scylla-0.3.3.tar.gz", hash = "sha256:a8fa7f4dc685bdb40a57b4abbe4ab0f15908302de7066c50e9ad234a6500d37f", size = 11529, upload-time = "2025-11-17T09:56:44.961Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/1d/e2b1a214b20d33cc631422e483ed1c8cf6883870940b58cc46341b65e2d7/sphinx_multiversion_scylla-0.3.4.tar.gz", hash = "sha256:8f7c94a89c794334d78ef21761a8bf455aaa7361e71037cf2ac2ca51cb47a0ba", size = 12427, upload-time = "2025-11-24T07:42:01.506Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/aa/82c27991640fe47921f74894a192d374dc1eb609d2276de4abeefe85f4aa/sphinx_multiversion_scylla-0.3.4-py3-none-any.whl", hash = "sha256:e64d49d39a8eccf06a9cb8bbe88eecb3eb2082e6b91a478b55dc7d0268d8e0b6", size = 12302, upload-time = "2025-11-24T07:42:00.403Z" }, +] [[package]] name = "sphinx-notfound-page" @@ -920,7 +910,7 @@ wheels = [ [[package]] name = "sphinx-scylladb-theme" -version = "1.8.9" +version = "1.8.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -933,9 +923,9 @@ dependencies = [ { name = "sphinx-tabs" }, { name = "sphinxcontrib-mermaid" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/9e/1578c6a27e0f6844694aaff7c3b319db8175b4be8566b0b7962e37b2fd0e/sphinx_scylladb_theme-1.8.9.tar.gz", hash = "sha256:ab7cda4c10a0d067c5c3a45f7b1f68cb8ebefe135a0be0738bfa282a344769b6", size = 1620650, upload-time = "2025-11-21T16:10:50.359Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/cd/bbd41f0d058f0ef4997cb044326f15dd28a1a17a4336e9b52cb67b8dd242/sphinx_scylladb_theme-1.8.10.tar.gz", hash = "sha256:8a78a9b692d9a946be2c4a64aa472fd82204cc8ea0b1ee7f60de6db35b356326", size = 1620675, upload-time = "2025-12-05T16:49:38.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/1d/32d0dad6c0814f4051a37152ed4d98350cfeb4cf6c91426ce64605e75e4a/sphinx_scylladb_theme-1.8.9-py3-none-any.whl", hash = "sha256:f8649a7753a29494fd2b417d1cb855035dddb9ebd498ea033fd73f5f9338271e", size = 1662403, upload-time = "2025-11-21T16:10:46.781Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0e/7577d9bb6e2e7378e6c9f49263c59061a2ae9e370b806d8d1fd8c3be2a23/sphinx_scylladb_theme-1.8.10-py3-none-any.whl", hash = "sha256:8b930f33bec7308ccaa92698ebb5ad85059bcbf93a463f92917aeaf473fce632", size = 1662434, upload-time = "2025-12-05T16:49:36.265Z" }, ] [[package]] @@ -1017,15 +1007,15 @@ wheels = [ [[package]] name = "sphinxcontrib-mermaid" -version = "1.1.0" +version = "1.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/9c/3eefdb1bbb5ec8094ff4baeb9fd310e8320a43311aebe230cf4aaef3c942/sphinxcontrib_mermaid-1.1.0.tar.gz", hash = "sha256:e674e04503956de8263163e0c8f446d99f9d60455cb395f64e998f1137f04f8e", size = 18766, upload-time = "2025-11-19T19:51:09.902Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/49/c6ddfe709a4ab76ac6e5a00e696f73626b2c189dc1e1965a361ec102e6cc/sphinxcontrib_mermaid-1.2.3.tar.gz", hash = "sha256:358699d0ec924ef679b41873d9edd97d0773446daf9760c75e18dc0adfd91371", size = 18885, upload-time = "2025-11-26T04:18:32.43Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/89/899495013b11b09e687310dc76bab626babdbee2dd0e6640df0942090ab4/sphinxcontrib_mermaid-1.1.0-py3-none-any.whl", hash = "sha256:1133b4c00f33aedb76e37bfbd1a4280a0104c0a836da642a113eabdf261bdd0c", size = 14898, upload-time = "2025-11-19T19:51:06.036Z" }, + { url = "https://files.pythonhosted.org/packages/d1/39/8b54299ffa00e597d3b0b4d042241a0a0b22cb429ad007ccfb9c1745b4d1/sphinxcontrib_mermaid-1.2.3-py3-none-any.whl", hash = "sha256:5be782b27026bef97bfb15ccb2f7868b674a1afc0982b54cb149702cfc25aa02", size = 13413, upload-time = "2025-11-26T04:18:31.269Z" }, ] [[package]] @@ -1079,11 +1069,11 @@ wheels = [ [[package]] name = "trove-classifiers" -version = "2025.11.14.15" +version = "2025.12.1.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bf/a9/880cccf76af9e7b322112f52e4e2dbb3534cbe671197b8f443a42189dfc7/trove_classifiers-2025.11.14.15.tar.gz", hash = "sha256:6b60f49d40bbd895bc61d8dc414fc2f2286d70eb72ed23548db8cf94f62804ca", size = 16995, upload-time = "2025-11-14T15:23:13.78Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/e1/000add3b3e0725ce7ee0ea6ea4543f1e1d9519742f3b2320de41eeefa7c7/trove_classifiers-2025.12.1.14.tar.gz", hash = "sha256:a74f0400524fc83620a9be74a07074b5cbe7594fd4d97fd4c2bfde625fdc1633", size = 16985, upload-time = "2025-12-01T14:47:11.456Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/f6/73c4aa003d1237ee9bea8a46f49dc38c45dfe95af4f0da7e60678d388011/trove_classifiers-2025.11.14.15-py3-none-any.whl", hash = "sha256:d1dac259c1e908939862e3331177931c6df0a37af2c1a8debcc603d9115fcdd9", size = 14191, upload-time = "2025-11-14T15:23:12.467Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl", hash = "sha256:a8206978ede95937b9959c3aff3eb258bbf7b07dff391ddd4ea7e61f316635ab", size = 14184, upload-time = "2025-12-01T14:47:10.113Z" }, ] [[package]] @@ -1112,11 +1102,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/1d/0f3a93cca1ac5e8287842ed4eebbd0f7a991315089b1a0b01c7788aa7b63/urllib3-2.6.1.tar.gz", hash = "sha256:5379eb6e1aba4088bae84f8242960017ec8d8e3decf30480b3a1abdaa9671a3f", size = 432678, upload-time = "2025-12-08T15:25:26.773Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/bc/56/190ceb8cb10511b730b564fb1e0293fa468363dbad26145c34928a60cb0c/urllib3-2.6.1-py3-none-any.whl", hash = "sha256:e67d06fe947c36a7ca39f4994b08d73922d40e6cca949907be05efa6fd75110b", size = 131138, upload-time = "2025-12-08T15:25:25.51Z" }, ] [[package]] From dd1adc72aff3664fd7beadb70802db288b43dcb9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 20:44:18 +0000 Subject: [PATCH 156/298] chore(deps): update github artifact actions --- .github/workflows/build-push.yml | 2 +- .github/workflows/lib-build-and-push.yml | 6 +++--- .github/workflows/publish-manually.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index f76c6e44cd..15c77f3861 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -23,7 +23,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: path: dist merge-multiple: true diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index e41a06d78e..735a4638f4 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -153,7 +153,7 @@ jobs: run: | GITHUB_WORKFLOW_REF="scylladb/python-driver/.github/workflows/lib-build-and-push.yml@refs/heads/master" CIBW_BUILD="cp3*" cibuildwheel --archs aarch64 --output-dir wheelhouse - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: wheels-${{ matrix.target }}-${{ matrix.os }} path: ./wheelhouse/*.whl @@ -172,7 +172,7 @@ jobs: - name: Build sdist run: uv build --sdist - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: source-dist path: dist/*.tar.gz @@ -185,7 +185,7 @@ jobs: id-token: write steps: - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: path: dist merge-multiple: true diff --git a/.github/workflows/publish-manually.yml b/.github/workflows/publish-manually.yml index 444ec42bc7..d69475f394 100644 --- a/.github/workflows/publish-manually.yml +++ b/.github/workflows/publish-manually.yml @@ -53,7 +53,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: path: dist merge-multiple: true From 43665f99e152cc8af160ce4f2e41809c13c29550 Mon Sep 17 00:00:00 2001 From: Yaniv Kaul Date: Tue, 16 Dec 2025 18:15:21 +0200 Subject: [PATCH 157/298] Apply suggested fix to docs/api/cassandra/protocol.rst from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- docs/api/cassandra/protocol.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/cassandra/protocol.rst b/docs/api/cassandra/protocol.rst index 258c6baeb6..581c4f1204 100644 --- a/docs/api/cassandra/protocol.rst +++ b/docs/api/cassandra/protocol.rst @@ -53,5 +53,5 @@ These protocol handlers comprise different parsers, and return results as descri - LazyProtocolHandler: near drop-in replacement for the above, except that it returns an iterator over rows, lazily decoded into the default row format (this is more efficient since all decoded results are not materialized at once) -- NumpyProtocolHander: deserializes results directly into NumPy arrays. This facilitates efficient integration with +- NumpyProtocolHandler: deserializes results directly into NumPy arrays. This facilitates efficient integration with analysis toolkits such as Pandas. From cbec15129c5b9590108b9121711783e257a0fe59 Mon Sep 17 00:00:00 2001 From: Yaniv Kaul Date: Tue, 16 Dec 2025 18:15:22 +0200 Subject: [PATCH 158/298] Apply suggested fix to docs/api/cassandra/protocol.rst from Copilot Autofix Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- docs/api/cassandra/protocol.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/cassandra/protocol.rst b/docs/api/cassandra/protocol.rst index 581c4f1204..8b8f303574 100644 --- a/docs/api/cassandra/protocol.rst +++ b/docs/api/cassandra/protocol.rst @@ -16,7 +16,7 @@ holding custom key/value pairs. By default these are ignored by the server. They can be useful for servers implementing a custom QueryHandler. -See :meth:`.Session.execute`, ::meth:`.Session.execute_async`, :attr:`.ResponseFuture.custom_payload`. +See :meth:`.Session.execute`, :meth:`.Session.execute_async`, :attr:`.ResponseFuture.custom_payload`. .. autoclass:: _ProtocolHandler From 90fdce6d408b319b623186f385ffa804c3c5ba34 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 12:10:22 +0000 Subject: [PATCH 159/298] chore(deps): update dependency hatchling to v1.28.0 --- docs/pyproject.toml | 4 ++-- docs/uv.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 49a4d000ae..f6ee417aee 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -25,14 +25,14 @@ dependencies = [ [dependency-groups] # Add any dev-only tools here; example shown -dev = ["hatchling==1.27.0"] +dev = ["hatchling==1.28.0"] [tool.uv.sources] # Keep the driver editable from the parent directory scylla-driver = { path = "../", editable = true } [build-system] -requires = ["hatchling==1.27.0"] +requires = ["hatchling==1.28.0"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] diff --git a/docs/uv.lock b/docs/uv.lock index 4d40698dcc..23468c7170 100644 --- a/docs/uv.lock +++ b/docs/uv.lock @@ -350,7 +350,7 @@ wheels = [ [[package]] name = "hatchling" -version = "1.27.0" +version = "1.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, @@ -358,9 +358,9 @@ dependencies = [ { name = "pluggy" }, { name = "trove-classifiers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/8a/cc1debe3514da292094f1c3a700e4ca25442489731ef7c0814358816bb03/hatchling-1.27.0.tar.gz", hash = "sha256:971c296d9819abb3811112fc52c7a9751c8d381898f36533bb16f9791e941fd6", size = 54983, upload-time = "2024-12-15T17:08:11.894Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/8e/e480359492affde4119a131da729dd26da742c2c9b604dff74836e47eef9/hatchling-1.28.0.tar.gz", hash = "sha256:4d50b02aece6892b8cd0b3ce6c82cb218594d3ec5836dbde75bf41a21ab004c8", size = 55365, upload-time = "2025-11-27T00:31:13.766Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/e7/ae38d7a6dfba0533684e0b2136817d667588ae3ec984c1a4e5df5eb88482/hatchling-1.27.0-py3-none-any.whl", hash = "sha256:d3a2f3567c4f926ea39849cdf924c7e99e6686c9c8e288ae1037c8fa2a5d937b", size = 75794, upload-time = "2024-12-15T17:08:10.364Z" }, + { url = "https://files.pythonhosted.org/packages/0d/a5/48cb7efb8b4718b1a4c0c331e3364a3a33f614ff0d6afd2b93ee883d3c47/hatchling-1.28.0-py3-none-any.whl", hash = "sha256:dc48722b68b3f4bbfa3ff618ca07cdea6750e7d03481289ffa8be1521d18a961", size = 76075, upload-time = "2025-11-27T00:31:12.544Z" }, ] [[package]] @@ -665,7 +665,7 @@ requires-dist = [ ] [package.metadata.requires-dev] -dev = [{ name = "hatchling", specifier = "==1.27.0" }] +dev = [{ name = "hatchling", specifier = "==1.28.0" }] [[package]] name = "pyyaml" From 5dd0640ffa200eea9db35497bb299767a4d1d1b7 Mon Sep 17 00:00:00 2001 From: Yaniv Kaul Date: Tue, 16 Dec 2025 18:18:01 +0200 Subject: [PATCH 160/298] Fix for Missing call to superclass `__init__` during object initialization Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- cassandra/metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cassandra/metadata.py b/cassandra/metadata.py index 6379de069a..bbfaf2605b 100644 --- a/cassandra/metadata.py +++ b/cassandra/metadata.py @@ -1892,7 +1892,7 @@ def hash_fn(cls, key): def __init__(self, token): """ `token` is an int or string representing the token. """ - self.value = int(token) + super().__init__(int(token)) class MD5Token(HashToken): From 234deef16b8e9e827892f9993df6e22423d5ddff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:37:35 +0000 Subject: [PATCH 161/298] Apply Python style improvements to test assertions Co-authored-by: mykaul <4655593+mykaul@users.noreply.github.com> --- tests/integration/standard/test_control_connection.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/integration/standard/test_control_connection.py b/tests/integration/standard/test_control_connection.py index 206945f0b3..a2f4e051d3 100644 --- a/tests/integration/standard/test_control_connection.py +++ b/tests/integration/standard/test_control_connection.py @@ -90,14 +90,14 @@ def test_get_control_connection_host(self): """ host = self.cluster.get_control_connection_host() - assert host == None + assert host is None self.session = self.cluster.connect() cc_host = self.cluster.control_connection._connection.host host = self.cluster.get_control_connection_host() assert host.address == cc_host - assert host.is_up == True + assert host.is_up # reconnect and make sure that the new host is reflected correctly self.cluster.control_connection._reconnect() @@ -117,16 +117,16 @@ def test_control_connection_port_discovery(self): self.cluster = TestCluster() host = self.cluster.get_control_connection_host() - assert host == None + assert host is None self.session = self.cluster.connect() cc_endpoint = self.cluster.control_connection._connection.endpoint host = self.cluster.get_control_connection_host() assert host.endpoint == cc_endpoint - assert host.is_up == True + assert host.is_up hosts = self.cluster.metadata.all_hosts() - assert 3 == len(hosts) + assert len(hosts) == 3 for host in hosts: assert 9042 == host.broadcast_rpc_port From fdca667c0a9acc30c546f0518e41174149767fd3 Mon Sep 17 00:00:00 2001 From: Yaniv Kaul Date: Mon, 22 Dec 2025 15:51:39 +0200 Subject: [PATCH 162/298] Potential fix for code scanning alert no. 3: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/docs-pages.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index f017201c7c..0da86fef34 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -2,6 +2,9 @@ name: "Docs / Publish" # For more information, # see https://sphinx-theme.scylladb.com/stable/deployment/production.html#available-workflows +permissions: + contents: write + on: push: branches: From 20c696edf1e62a84dd6ee5cd821cf9a3634e6d4c Mon Sep 17 00:00:00 2001 From: Yaniv Kaul Date: Mon, 22 Dec 2025 13:51:44 +0200 Subject: [PATCH 163/298] .github/workflows/publish-manually.yml: Potential fix for code scanning alert no. 7: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/publish-manually.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/publish-manually.yml b/.github/workflows/publish-manually.yml index d69475f394..09b9779117 100644 --- a/.github/workflows/publish-manually.yml +++ b/.github/workflows/publish-manually.yml @@ -1,5 +1,8 @@ name: Build and upload to PyPi manually +permissions: + contents: read + on: workflow_dispatch: inputs: From 7acc9f6660cf540dd9c1912328d5db04e879818e Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 30 Dec 2025 12:27:47 -0400 Subject: [PATCH 164/298] Don't mark node down when control connection fails to connect Node pools should be stable, if cc fails to connect it is not good enough reason to neither to kill it nor to mark node down. --- cassandra/cluster.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 66bf7c7049..1fff739e97 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -3546,10 +3546,6 @@ def _connect_host_in_lbp(self): for host in lbp.make_query_plan(): try: return (self._try_connect(host), None) - except ConnectionException as exc: - errors[str(host.endpoint)] = exc - log.warning("[control connection] Error connecting to %s:", host, exc_info=True) - self._cluster.signal_connection_failure(host, exc, is_host_addition=False) except Exception as exc: errors[str(host.endpoint)] = exc log.warning("[control connection] Error connecting to %s:", host, exc_info=True) From f3e0c62422b0521d5a966b0754fa748610df44b9 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sun, 4 Jan 2026 09:54:37 +0200 Subject: [PATCH 165/298] (improvement) remove supprot for protocols <3 from cython files Continued effort to remove protocol versions < 3 as was done in the native Python code. Signed-off-by: Yaniv Kaul --- cassandra/deserializers.pyx | 74 ++++++++++++------------------------- 1 file changed, 24 insertions(+), 50 deletions(-) diff --git a/cassandra/deserializers.pyx b/cassandra/deserializers.pyx index 7c256674b0..97d249d02f 100644 --- a/cassandra/deserializers.pyx +++ b/cassandra/deserializers.pyx @@ -208,15 +208,9 @@ cdef class _DesSingleParamType(_DesParameterizedType): cdef class DesListType(_DesSingleParamType): cdef deserialize(self, Buffer *buf, int protocol_version): - cdef uint16_t v2_and_below = 2 - cdef int32_t v3_and_above = 3 - if protocol_version >= 3: - result = _deserialize_list_or_set[int32_t]( - v3_and_above, buf, protocol_version, self.deserializer) - else: - result = _deserialize_list_or_set[uint16_t]( - v2_and_below, buf, protocol_version, self.deserializer) + result = _deserialize_list_or_set( + buf, protocol_version, self.deserializer) return result @@ -225,60 +219,49 @@ cdef class DesSetType(DesListType): return util.sortedset(DesListType.deserialize(self, buf, protocol_version)) -ctypedef fused itemlen_t: - uint16_t # protocol <= v2 - int32_t # protocol >= v3 - -cdef list _deserialize_list_or_set(itemlen_t dummy_version, - Buffer *buf, int protocol_version, +cdef list _deserialize_list_or_set(Buffer *buf, int protocol_version, Deserializer deserializer): """ Deserialize a list or set. - - The 'dummy' parameter is needed to make fused types work, so that - we can specialize on the protocol version. """ cdef Buffer itemlen_buf cdef Buffer elem_buf - cdef itemlen_t numelements + cdef int32_t numelements cdef int offset cdef list result = [] - _unpack_len[itemlen_t](buf, 0, &numelements) - offset = sizeof(itemlen_t) + _unpack_len(buf, 0, &numelements) + offset = sizeof(int32_t) protocol_version = max(3, protocol_version) for _ in range(numelements): - subelem[itemlen_t](buf, &elem_buf, &offset, dummy_version) + subelem(buf, &elem_buf, &offset) result.append(from_binary(deserializer, &elem_buf, protocol_version)) return result cdef inline int subelem( - Buffer *buf, Buffer *elem_buf, int* offset, itemlen_t dummy) except -1: + Buffer *buf, Buffer *elem_buf, int* offset) except -1: """ Read the next element from the buffer: first read the size (in bytes) of the element, then fill elem_buf with a newly sliced buffer of this size (and the right offset). """ - cdef itemlen_t elemlen + cdef int32_t elemlen - _unpack_len[itemlen_t](buf, offset[0], &elemlen) - offset[0] += sizeof(itemlen_t) + _unpack_len(buf, offset[0], &elemlen) + offset[0] += sizeof(int32_t) slice_buffer(buf, elem_buf, offset[0], elemlen) offset[0] += elemlen return 0 -cdef int _unpack_len(Buffer *buf, int offset, itemlen_t *output) except -1: +cdef int _unpack_len(Buffer *buf, int offset, int32_t *output) except -1: cdef Buffer itemlen_buf - slice_buffer(buf, &itemlen_buf, offset, sizeof(itemlen_t)) + slice_buffer(buf, &itemlen_buf, offset, sizeof(int32_t)) - if itemlen_t is uint16_t: - output[0] = unpack_num[uint16_t](&itemlen_buf) - else: - output[0] = unpack_num[int32_t](&itemlen_buf) + output[0] = unpack_num[int32_t](&itemlen_buf) return 0 @@ -295,42 +278,33 @@ cdef class DesMapType(_DesParameterizedType): self.val_deserializer = self.deserializers[1] cdef deserialize(self, Buffer *buf, int protocol_version): - cdef uint16_t v2_and_below = 0 - cdef int32_t v3_and_above = 0 key_type, val_type = self.cqltype.subtypes - if protocol_version >= 3: - result = _deserialize_map[int32_t]( - v3_and_above, buf, protocol_version, - self.key_deserializer, self.val_deserializer, - key_type, val_type) - else: - result = _deserialize_map[uint16_t]( - v2_and_below, buf, protocol_version, - self.key_deserializer, self.val_deserializer, - key_type, val_type) + result = _deserialize_map( + buf, protocol_version, + self.key_deserializer, self.val_deserializer, + key_type, val_type) return result -cdef _deserialize_map(itemlen_t dummy_version, - Buffer *buf, int protocol_version, +cdef _deserialize_map(Buffer *buf, int protocol_version, Deserializer key_deserializer, Deserializer val_deserializer, key_type, val_type): cdef Buffer key_buf, val_buf cdef Buffer itemlen_buf - cdef itemlen_t numelements + cdef int32_t numelements cdef int offset cdef list result = [] - _unpack_len[itemlen_t](buf, 0, &numelements) - offset = sizeof(itemlen_t) + _unpack_len(buf, 0, &numelements) + offset = sizeof(int32_t) themap = util.OrderedMapSerializedKey(key_type, protocol_version) protocol_version = max(3, protocol_version) for _ in range(numelements): - subelem[itemlen_t](buf, &key_buf, &offset, dummy_version) - subelem[itemlen_t](buf, &val_buf, &offset, numelements) + subelem(buf, &key_buf, &offset) + subelem(buf, &val_buf, &offset) key = from_binary(key_deserializer, &key_buf, protocol_version) val = from_binary(val_deserializer, &val_buf, protocol_version) themap._insert_unchecked(key, to_bytes(&key_buf), val) From d37e3e58c8cfbd995e93ed6539b17ef016dcf2d0 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 1 Jan 2026 00:59:34 -0400 Subject: [PATCH 166/298] Pull version information from systel.local, when version info is not present --- cassandra/metadata.py | 26 +++++++++++++++++++++++--- tests/unit/test_metadata.py | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/cassandra/metadata.py b/cassandra/metadata.py index bbfaf2605b..85f6c45ac6 100644 --- a/cassandra/metadata.py +++ b/cassandra/metadata.py @@ -139,8 +139,9 @@ def export_schema_as_string(self): def refresh(self, connection, timeout, target_type=None, change_type=None, fetch_size=None, metadata_request_timeout=None, **kwargs): - server_version = self.get_host(connection.original_endpoint).release_version - dse_version = self.get_host(connection.original_endpoint).dse_version + host = self.get_host(connection.original_endpoint) + server_version = host.release_version if host else None + dse_version = host.dse_version if host else None parser = get_schema_parser(connection, server_version, dse_version, timeout, metadata_request_timeout, fetch_size) if not target_type: @@ -3409,8 +3410,27 @@ def __init__( self.to_clustering_columns = to_clustering_columns +def get_column_from_system_local(connection, column_name: str, timeout, metadata_request_timeout) -> str: + success, local_result = connection.wait_for_response( + QueryMessage( + query=maybe_add_timeout_to_query( + "SELECT " + column_name + " FROM system.local WHERE key='local'", + metadata_request_timeout), + consistency_level=ConsistencyLevel.ONE) + , timeout=timeout, fail_on_error=False) + if not success or not local_result.parsed_rows: + return "" + local_rows = dict_factory(local_result.column_names, local_result.parsed_rows) + local_row = local_rows[0] + return local_row.get(column_name) + + def get_schema_parser(connection, server_version, dse_version, timeout, metadata_request_timeout, fetch_size=None): - version = Version(server_version) + if server_version is None and dse_version is None: + server_version = get_column_from_system_local(connection, "release_version", timeout, metadata_request_timeout) + dse_version = get_column_from_system_local(connection, "dse_version", timeout, metadata_request_timeout) + + version = Version(server_version or "0") if dse_version: v = Version(dse_version) if v >= Version('6.8.0'): diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index 3069f6bced..c471fab827 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -30,9 +30,11 @@ UserType, KeyspaceMetadata, get_schema_parser, _UnknownStrategy, ColumnMetadata, TableMetadata, IndexMetadata, Function, Aggregate, - Metadata, TokenMap, ReplicationFactor) + Metadata, TokenMap, ReplicationFactor, + SchemaParserDSE68) from cassandra.policies import SimpleConvictionPolicy from cassandra.pool import Host +from cassandra.protocol import QueryMessage from tests.util import assertCountEqual import pytest @@ -616,6 +618,37 @@ def test_build_index_as_cql(self): assert index_meta.as_cql_query() == "CREATE CUSTOM INDEX index_name_here ON keyspace_name_here.table_name_here (column_name_here) USING 'class_name_here'" +class SchemaParserLookupTests(unittest.TestCase): + + def test_reads_versions_from_system_local_when_missing(self): + connection = Mock() + + release_version_resp = Mock() + release_version_resp.column_names = ["release_version"] + release_version_resp.parsed_rows = [["4.0.0"]] + + dse_version_resp = Mock() + dse_version_resp.column_names = ["dse_version"] + dse_version_resp.parsed_rows = [["6.8.0"]] + + def mock_system_local(query, *args, **kwargs): + if not isinstance(query, QueryMessage): + raise RuntimeError("first argument should be a QueryMessage") + if "release_version" in query.query: + return (True, release_version_resp) + if "dse_version" in query.query: + return (True, dse_version_resp) + raise RuntimeError("unexpected query") + + connection.wait_for_response.side_effect = mock_system_local + + parser = get_schema_parser(connection, None, None, 0.1, None) + + assert isinstance(parser, SchemaParserDSE68) + message = connection.wait_for_response.call_args[0][0] + assert "system.local" in message.query + + class UnicodeIdentifiersTests(unittest.TestCase): """ Exercise cql generation with unicode characters. Keyspace, Table, and Index names From f08cdeb3280d239f2996a43fd2854e0cf4f763fc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 11:31:35 +0000 Subject: [PATCH 167/298] Fix infinite retry when single host fails with server error Co-authored-by: mykaul <4655593+mykaul@users.noreply.github.com> --- cassandra/cluster.py | 2 +- tests/unit/test_response_future.py | 57 +++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 1fff739e97..fe1c0ea4c5 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -4543,7 +4543,7 @@ def _make_query_plan(self): # or to the explicit host target if set if self._host: # returning a single value effectively disables retries - self.query_plan = [self._host] + self.query_plan = iter([self._host]) else: # convert the list/generator/etc to an iterator so that subsequent # calls to send_request (which retries may do) will resume where diff --git a/tests/unit/test_response_future.py b/tests/unit/test_response_future.py index bcca28ac73..7168ad2940 100644 --- a/tests/unit/test_response_future.py +++ b/tests/unit/test_response_future.py @@ -24,7 +24,7 @@ from cassandra.protocol import (ReadTimeoutErrorMessage, WriteTimeoutErrorMessage, UnavailableErrorMessage, ResultMessage, QueryMessage, OverloadedErrorMessage, IsBootstrappingErrorMessage, - PreparedQueryNotFound, PrepareMessage, + PreparedQueryNotFound, PrepareMessage, ServerError, RESULT_KIND_ROWS, RESULT_KIND_SET_KEYSPACE, RESULT_KIND_SCHEMA_CHANGE, RESULT_KIND_PREPARED, ProtocolHandler) @@ -668,3 +668,58 @@ def test_timeout_does_not_release_stream_id(self): assert len(connection.request_ids) == 0, \ "Request IDs should be empty but it's not: {}".format(connection.request_ids) + + def test_single_host_query_plan_exhausted_after_one_retry(self): + """ + Test that when a specific host is provided, the query plan is properly + exhausted after one attempt and doesn't cause infinite retries. + + This test reproduces the issue where providing a single host in the query plan + (via the host parameter) would cause infinite retries on server errors because + the query_plan was a list instead of an iterator. + """ + session = self.make_basic_session() + pool = self.make_pool() + session._pools.get.return_value = pool + + # Create a specific host + specific_host = Mock() + + connection = Mock(spec=Connection) + pool.borrow_connection.return_value = (connection, 1) + + query = SimpleStatement("INSERT INTO foo (a, b) VALUES (1, 2)") + message = QueryMessage(query=query, consistency_level=ConsistencyLevel.ONE) + + # Create ResponseFuture with a specific host (this is the key to reproducing the bug) + rf = ResponseFuture(session, message, query, 1, host=specific_host) + rf.send_request() + + # Verify initial request was sent + rf.session._pools.get.assert_called_once_with(specific_host) + pool.borrow_connection.assert_called_once_with(timeout=ANY, routing_key=ANY, keyspace=ANY, table=ANY) + connection.send_msg.assert_called_once_with(rf.message, 1, cb=ANY, encoder=ProtocolHandler.encode_message, decoder=ProtocolHandler.decode_message, result_metadata=[]) + + # Simulate a ServerError response (which triggers RETRY_NEXT_HOST by default) + result = Mock(spec=ServerError, info={}) + result.to_exception.return_value = result + rf._set_result(specific_host, None, None, result) + + # The retry should be scheduled + rf.session.cluster.scheduler.schedule.assert_called_once_with(ANY, rf._retry_task, False, specific_host) + assert 1 == rf._query_retries + + # Reset mocks to track next calls + pool.borrow_connection.reset_mock() + connection.send_msg.reset_mock() + + # Now simulate the retry task executing + # The bug would cause this to succeed and retry again infinitely + # The fix ensures the iterator is exhausted after the first try + rf._retry_task(False, specific_host) + + # After the retry, send_request should be called but the query_plan iterator + # should be exhausted, so no new request should be sent + # Instead, it should set a NoHostAvailable exception + assert rf._final_exception is not None + assert isinstance(rf._final_exception, NoHostAvailable) From de89eb15a4cea81446764e62032d97c25562e2d8 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Mon, 29 Dec 2025 14:46:58 +0100 Subject: [PATCH 168/298] Use endpoint instead od Host in _try_connect --- cassandra/cluster.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index fe1c0ea4c5..8db58c13cd 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -838,8 +838,8 @@ def default_retry_policy(self, policy): Using ssl_options without ssl_context is deprecated and will be removed in the next major release. - An optional dict which will be used as kwargs for ``ssl.SSLContext.wrap_socket`` - when new sockets are created. This should be used when client encryption is enabled + An optional dict which will be used as kwargs for ``ssl.SSLContext.wrap_socket`` + when new sockets are created. This should be used when client encryption is enabled in Cassandra. The following documentation only applies when ssl_options is used without ssl_context. @@ -1086,10 +1086,10 @@ def default_retry_policy(self, policy): """ Specifies a server-side timeout (in seconds) for all internal driver queries, such as schema metadata lookups and cluster topology requests. - + The timeout is enforced by appending `USING TIMEOUT ` to queries executed by the driver. - + - A value of `0` disables explicit timeout enforcement. In this case, the driver does not add `USING TIMEOUT`, and the timeout is determined by the server's defaults. @@ -1717,7 +1717,7 @@ def connect(self, keyspace=None, wait_for_all_pools=False): self.contact_points, self.protocol_version) self.connection_class.initialize_reactor() _register_cluster_shutdown(self) - + self._add_resolved_hosts() try: @@ -3534,7 +3534,7 @@ def _set_new_connection(self, conn): if old: log.debug("[control connection] Closing old connection %r, replacing with %r", old, conn) old.close() - + def _connect_host_in_lbp(self): errors = {} lbp = ( @@ -3545,13 +3545,13 @@ def _connect_host_in_lbp(self): for host in lbp.make_query_plan(): try: - return (self._try_connect(host), None) + return (self._try_connect(host.endpoint), None) except Exception as exc: errors[str(host.endpoint)] = exc log.warning("[control connection] Error connecting to %s:", host, exc_info=True) if self._is_shutdown: raise DriverException("[control connection] Reconnection in progress during shutdown") - + return (None, errors) def _reconnect_internal(self): @@ -3575,31 +3575,31 @@ def _reconnect_internal(self): (conn, errors) = self._connect_host_in_lbp() if conn is not None: return conn - + raise NoHostAvailable("Unable to connect to any servers", errors) - def _try_connect(self, host): + def _try_connect(self, endpoint): """ Creates a new Connection, registers for pushed events, and refreshes node/token and schema metadata. """ - log.debug("[control connection] Opening new connection to %s", host) + log.debug("[control connection] Opening new connection to %s", endpoint) while True: try: - connection = self._cluster.connection_factory(host.endpoint, is_control_connection=True) + connection = self._cluster.connection_factory(endpoint, is_control_connection=True) if self._is_shutdown: connection.close() raise DriverException("Reconnecting during shutdown") break except ProtocolVersionUnsupported as e: - self._cluster.protocol_downgrade(host.endpoint, e.startup_version) + self._cluster.protocol_downgrade(endpoint, e.startup_version) except ProtocolException as e: # protocol v5 is out of beta in C* >=4.0-beta5 and is now the default driver # protocol version. If the protocol version was not explicitly specified, # and that the server raises a beta protocol error, we should downgrade. if not self._cluster._protocol_version_explicit and e.is_beta_protocol_error: - self._cluster.protocol_downgrade(host.endpoint, self._cluster.protocol_version) + self._cluster.protocol_downgrade(endpoint, self._cluster.protocol_version) else: raise From d39917337467a6adb3ab55693dbfafbce9b9958b Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Mon, 29 Dec 2025 15:28:56 +0100 Subject: [PATCH 169/298] tests/integration/standard: fix test to reflect RR policy randomizing starting point The `test_profile_lb_swap` test logic assumed that `populate` was called before control connection (cc) was created, meaning only the contact points from the cluster configuration were known (a single host). Due to that the starting point was not random. This commit updates the test to reflect the new behavior, where `populate` is called on the load-balancing policy after the control connection is created. This allows the policy to be updated with all known hosts and ensures the starting point is properly randomized. --- tests/integration/standard/test_cluster.py | 36 ++++++++++++++-------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index d7f89ad598..1208edb9d2 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -900,8 +900,9 @@ def test_profile_lb_swap(self): """ Tests that profile load balancing policies are not shared - Creates two LBP, runs a few queries, and validates that each LBP is execised - seperately between EP's + Creates two LBP, runs a few queries, and validates that each LBP is exercised + separately between EP's. Each RoundRobinPolicy starts from its own random + position and maintains independent round-robin ordering. @since 3.5 @jira_ticket PYTHON-569 @@ -916,17 +917,28 @@ def test_profile_lb_swap(self): with TestCluster(execution_profiles=exec_profiles) as cluster: session = cluster.connect(wait_for_all_pools=True) - # default is DCA RR for all hosts expected_hosts = set(cluster.metadata.all_hosts()) - rr1_queried_hosts = set() - rr2_queried_hosts = set() - - rs = session.execute(query, execution_profile='rr1') - rr1_queried_hosts.add(rs.response_future._current_host) - rs = session.execute(query, execution_profile='rr2') - rr2_queried_hosts.add(rs.response_future._current_host) - - assert rr2_queried_hosts == rr1_queried_hosts + num_hosts = len(expected_hosts) + assert num_hosts > 1, "Need at least 2 hosts for this test" + + rr1_queried_hosts = [] + rr2_queried_hosts = [] + + for _ in range(num_hosts * 2): + rs = session.execute(query, execution_profile='rr1') + rr1_queried_hosts.append(rs.response_future._current_host) + rs = session.execute(query, execution_profile='rr2') + rr2_queried_hosts.append(rs.response_future._current_host) + + # Both policies should have queried all hosts + assert set(rr1_queried_hosts) == expected_hosts + assert set(rr2_queried_hosts) == expected_hosts + + # The order of hosts should demonstrate round-robin behavior + # After num_hosts queries, the pattern should repeat + for i in range(num_hosts): + assert rr1_queried_hosts[i] == rr1_queried_hosts[i + num_hosts] + assert rr2_queried_hosts[i] == rr2_queried_hosts[i + num_hosts] def test_ta_lbp(self): """ From 26be3d6aacdeee59acf00684881e8227ee30c349 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Mon, 29 Dec 2025 15:37:31 +0100 Subject: [PATCH 170/298] tests/integration/standard: update test to reflect new behavior Previously, the driver relied on the load-balancing policy (LBP) to determine the order of hosts to connect to. Since the default LBP is Round Robin, each reconnection would start from a different host. After removing fake hosts with random IDs at startup, this behavior changed. When the LBP is not yet initialized, the driver now uses the endpoints provided by the control connection (CC), so there is no guarantee that different hosts will be selected on reconnection. This change updates the test logic to first establish a connection and initialize the LBP, and only then verify that two subsequent reconnections land on different hosts in a healthy cluster. --- tests/integration/standard/test_control_connection.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/integration/standard/test_control_connection.py b/tests/integration/standard/test_control_connection.py index a2f4e051d3..cb7820f0a6 100644 --- a/tests/integration/standard/test_control_connection.py +++ b/tests/integration/standard/test_control_connection.py @@ -101,8 +101,12 @@ def test_get_control_connection_host(self): # reconnect and make sure that the new host is reflected correctly self.cluster.control_connection._reconnect() - new_host = self.cluster.get_control_connection_host() - assert host != new_host + new_host1 = self.cluster.get_control_connection_host() + + self.cluster.control_connection._reconnect() + new_host2 = self.cluster.get_control_connection_host() + + assert new_host1 != new_host2 # TODO: enable after https://github.com/scylladb/python-driver/issues/121 is fixed @unittest.skip('Fails on scylla due to the broadcast_rpc_port is None') From 2bd2fb8e236b226fa64b5952745defe62207cfc9 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Mon, 29 Dec 2025 16:03:38 +0100 Subject: [PATCH 171/298] tests/integration/standard: don't compare Host instances Only compare hosts endpoints not whole Host instances as we don't know hosts ids. --- tests/integration/standard/test_policies.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/integration/standard/test_policies.py b/tests/integration/standard/test_policies.py index 0c84fd06be..2de12f7b7f 100644 --- a/tests/integration/standard/test_policies.py +++ b/tests/integration/standard/test_policies.py @@ -45,9 +45,6 @@ def test_predicate_changes(self): external_event = True contact_point = DefaultEndPoint("127.0.0.1") - single_host = {Host(contact_point, SimpleConvictionPolicy)} - all_hosts = {Host(DefaultEndPoint("127.0.0.{}".format(i)), SimpleConvictionPolicy) for i in (1, 2, 3)} - predicate = lambda host: host.endpoint == contact_point if external_event else True hfp = ExecutionProfile( load_balancing_policy=HostFilterPolicy(RoundRobinPolicy(), predicate=predicate) @@ -62,7 +59,8 @@ def test_predicate_changes(self): response = session.execute("SELECT * from system.local WHERE key='local'") queried_hosts.update(response.response_future.attempted_hosts) - assert queried_hosts == single_host + assert len(queried_hosts) == 1 + assert queried_hosts.pop().endpoint == contact_point external_event = False futures = session.update_created_pools() @@ -72,7 +70,8 @@ def test_predicate_changes(self): for _ in range(10): response = session.execute("SELECT * from system.local WHERE key='local'") queried_hosts.update(response.response_future.attempted_hosts) - assert queried_hosts == all_hosts + assert len(queried_hosts) == 3 + assert {host.endpoint for host in queried_hosts} == {DefaultEndPoint(f"127.0.0.{i}") for i in range(1, 4)} class WhiteListRoundRobinPolicyTests(unittest.TestCase): From 240c538060c072c39ca846dd11abb171950d300b Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Mon, 22 Dec 2025 14:50:01 +0100 Subject: [PATCH 172/298] tests/unit: Provide host_id when initializing Host --- tests/unit/advanced/test_policies.py | 5 +- tests/unit/test_cluster.py | 23 +++---- tests/unit/test_concurrent.py | 3 +- tests/unit/test_host_connection_pool.py | 15 +++-- tests/unit/test_metadata.py | 51 +++++++-------- tests/unit/test_policies.py | 85 +++++++++++++------------ tests/unit/test_types.py | 6 +- 7 files changed, 97 insertions(+), 91 deletions(-) diff --git a/tests/unit/advanced/test_policies.py b/tests/unit/advanced/test_policies.py index 8e421a859d..75cfd3fbf9 100644 --- a/tests/unit/advanced/test_policies.py +++ b/tests/unit/advanced/test_policies.py @@ -13,6 +13,7 @@ # limitations under the License. import unittest from unittest.mock import Mock +import uuid from cassandra.pool import Host from cassandra.policies import RoundRobinPolicy @@ -72,7 +73,7 @@ def test_target_no_host(self): def test_target_host_down(self): node_count = 4 - hosts = [Host(i, Mock()) for i in range(node_count)] + hosts = [Host(i, Mock(), host_id=uuid.uuid4()) for i in range(node_count)] target_host = hosts[1] policy = DSELoadBalancingPolicy(RoundRobinPolicy()) @@ -87,7 +88,7 @@ def test_target_host_down(self): def test_target_host_nominal(self): node_count = 4 - hosts = [Host(i, Mock()) for i in range(node_count)] + hosts = [Host(i, Mock(), host_id=uuid.uuid4()) for i in range(node_count)] target_host = hosts[1] target_host.is_up = True diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index f3efed9f54..49208ac53e 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -17,6 +17,7 @@ import socket from unittest.mock import patch, Mock +import uuid from cassandra import ConsistencyLevel, DriverException, Timeout, Unavailable, RequestExecutionException, ReadTimeout, WriteTimeout, CoordinationFailure, ReadFailure, WriteFailure, FunctionFailure, AlreadyExists,\ InvalidRequest, Unauthorized, AuthenticationFailed, OperationTimedOut, UnsupportedOperation, RequestValidationException, ConfigurationException, ProtocolVersion @@ -200,7 +201,7 @@ def test_default_serial_consistency_level_ep(self, *_): PR #510 """ c = Cluster(protocol_version=4) - s = Session(c, [Host("127.0.0.1", SimpleConvictionPolicy)]) + s = Session(c, [Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())]) c.connection_class.initialize_reactor() # default is None @@ -229,7 +230,7 @@ def test_default_serial_consistency_level_legacy(self, *_): PR #510 """ c = Cluster(protocol_version=4) - s = Session(c, [Host("127.0.0.1", SimpleConvictionPolicy)]) + s = Session(c, [Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())]) c.connection_class.initialize_reactor() # default is None assert s.default_serial_consistency_level is None @@ -286,7 +287,7 @@ def test_default_exec_parameters(self): assert cluster.profile_manager.default.load_balancing_policy.__class__ == default_lbp_factory().__class__ assert cluster.default_retry_policy.__class__ == RetryPolicy assert cluster.profile_manager.default.retry_policy.__class__ == RetryPolicy - session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) + session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())]) assert session.default_timeout == 10.0 assert cluster.profile_manager.default.request_timeout == 10.0 assert session.default_consistency_level == ConsistencyLevel.LOCAL_ONE @@ -300,7 +301,7 @@ def test_default_exec_parameters(self): def test_default_legacy(self): cluster = Cluster(load_balancing_policy=RoundRobinPolicy(), default_retry_policy=DowngradingConsistencyRetryPolicy()) assert cluster._config_mode == _ConfigMode.LEGACY - session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) + session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())]) session.default_timeout = 3.7 session.default_consistency_level = ConsistencyLevel.ALL session.default_serial_consistency_level = ConsistencyLevel.SERIAL @@ -314,7 +315,7 @@ def test_default_legacy(self): def test_default_profile(self): non_default_profile = ExecutionProfile(RoundRobinPolicy(), *[object() for _ in range(2)]) cluster = Cluster(execution_profiles={'non-default': non_default_profile}) - session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) + session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())]) assert cluster._config_mode == _ConfigMode.PROFILES @@ -347,7 +348,7 @@ def test_serial_consistency_level_validation(self): def test_statement_params_override_legacy(self): cluster = Cluster(load_balancing_policy=RoundRobinPolicy(), default_retry_policy=DowngradingConsistencyRetryPolicy()) assert cluster._config_mode == _ConfigMode.LEGACY - session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) + session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())]) ss = SimpleStatement("query", retry_policy=DowngradingConsistencyRetryPolicy(), consistency_level=ConsistencyLevel.ALL, serial_consistency_level=ConsistencyLevel.SERIAL) @@ -368,7 +369,7 @@ def test_statement_params_override_legacy(self): def test_statement_params_override_profile(self): non_default_profile = ExecutionProfile(RoundRobinPolicy(), *[object() for _ in range(2)]) cluster = Cluster(execution_profiles={'non-default': non_default_profile}) - session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) + session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())]) assert cluster._config_mode == _ConfigMode.PROFILES @@ -406,7 +407,7 @@ def test_no_profile_with_legacy(self): # session settings lock out profiles cluster = Cluster() - session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) + session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())]) for attr, value in (('default_timeout', 1), ('default_consistency_level', ConsistencyLevel.ANY), ('default_serial_consistency_level', ConsistencyLevel.SERIAL), @@ -432,7 +433,7 @@ def test_no_legacy_with_profile(self): ('load_balancing_policy', default_lbp_factory())): with pytest.raises(ValueError): setattr(cluster, attr, value) - session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) + session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())]) for attr, value in (('default_timeout', 1), ('default_consistency_level', ConsistencyLevel.ANY), ('default_serial_consistency_level', ConsistencyLevel.SERIAL), @@ -445,7 +446,7 @@ def test_profile_name_value(self): internalized_profile = ExecutionProfile(RoundRobinPolicy(), *[object() for _ in range(2)]) cluster = Cluster(execution_profiles={'by-name': internalized_profile}) - session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) + session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())]) assert cluster._config_mode == _ConfigMode.PROFILES rf = session.execute_async("query", execution_profile='by-name') @@ -459,7 +460,7 @@ def test_profile_name_value(self): def test_exec_profile_clone(self): cluster = Cluster(execution_profiles={EXEC_PROFILE_DEFAULT: ExecutionProfile(), 'one': ExecutionProfile()}) - session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy)]) + session = Session(cluster, hosts=[Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())]) profile_attrs = {'request_timeout': 1, 'consistency_level': ConsistencyLevel.ANY, diff --git a/tests/unit/test_concurrent.py b/tests/unit/test_concurrent.py index a3587a3e16..9c85b1ccac 100644 --- a/tests/unit/test_concurrent.py +++ b/tests/unit/test_concurrent.py @@ -22,6 +22,7 @@ from queue import PriorityQueue import sys import platform +import uuid from cassandra.cluster import Cluster, Session from cassandra.concurrent import execute_concurrent, execute_concurrent_with_args @@ -248,7 +249,7 @@ def test_recursion_limited(self): PYTHON-585 """ max_recursion = sys.getrecursionlimit() - s = Session(Cluster(), [Host("127.0.0.1", SimpleConvictionPolicy)]) + s = Session(Cluster(), [Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())]) with pytest.raises(TypeError): execute_concurrent_with_args(s, "doesn't matter", [('param',)] * max_recursion, raise_on_first_error=True) diff --git a/tests/unit/test_host_connection_pool.py b/tests/unit/test_host_connection_pool.py index e7b930a990..580eb336b2 100644 --- a/tests/unit/test_host_connection_pool.py +++ b/tests/unit/test_host_connection_pool.py @@ -14,6 +14,7 @@ from concurrent.futures import ThreadPoolExecutor import logging import time +import uuid from cassandra.protocol_features import ProtocolFeatures from cassandra.shard_info import _ShardingInfo @@ -205,20 +206,20 @@ def test_host_instantiations(self): """ with pytest.raises(ValueError): - Host(None, None) + Host(None, None, host_id=uuid.uuid4()) with pytest.raises(ValueError): - Host('127.0.0.1', None) + Host('127.0.0.1', None, host_id=uuid.uuid4()) with pytest.raises(ValueError): - Host(None, SimpleConvictionPolicy) + Host(None, SimpleConvictionPolicy, host_id=uuid.uuid4()) def test_host_equality(self): """ Test host equality has correct logic """ - a = Host('127.0.0.1', SimpleConvictionPolicy) - b = Host('127.0.0.1', SimpleConvictionPolicy) - c = Host('127.0.0.2', SimpleConvictionPolicy) + a = Host('127.0.0.1', SimpleConvictionPolicy, host_id=uuid.uuid4()) + b = Host('127.0.0.1', SimpleConvictionPolicy, host_id=uuid.uuid4()) + c = Host('127.0.0.2', SimpleConvictionPolicy, host_id=uuid.uuid4()) assert a == b, 'Two Host instances should be equal when sharing.' assert a != c, 'Two Host instances should NOT be equal when using two different addresses.' @@ -253,7 +254,7 @@ def mock_connection_factory(self, *args, **kwargs): connection.is_shutdown = False connection.is_defunct = False connection.is_closed = False - connection.features = ProtocolFeatures(shard_id=self.connection_counter, + connection.features = ProtocolFeatures(shard_id=self.connection_counter, sharding_info=_ShardingInfo(shard_id=1, shards_count=14, partitioner="", sharding_algorithm="", sharding_ignore_msb=0, shard_aware_port="", shard_aware_port_ssl="")) diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index c471fab827..dcbb840447 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -18,6 +18,7 @@ from unittest.mock import Mock import os import timeit +import uuid import cassandra from cassandra.cqltypes import strip_frozen @@ -123,7 +124,7 @@ def test_simple_replication_type_parsing(self): # make token replica map ring = [MD5Token(0), MD5Token(1), MD5Token(2)] - hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy) for host in range(3)] + hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy, host_id=uuid.uuid4()) for host in range(3)] token_to_host = dict(zip(ring, hosts)) assert simple_int.make_token_replica_map(token_to_host, ring) == simple_str.make_token_replica_map(token_to_host, ring) @@ -141,7 +142,7 @@ def test_transient_replication_parsing(self): # make token replica map ring = [MD5Token(0), MD5Token(1), MD5Token(2)] - hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy) for host in range(3)] + hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy, host_id=uuid.uuid4()) for host in range(3)] token_to_host = dict(zip(ring, hosts)) assert simple_transient.make_token_replica_map(token_to_host, ring) == simple_str.make_token_replica_map(token_to_host, ring) @@ -162,7 +163,7 @@ def test_nts_replication_parsing(self): # make token replica map ring = [MD5Token(0), MD5Token(1), MD5Token(2)] - hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy) for host in range(3)] + hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy, host_id=uuid.uuid4()) for host in range(3)] token_to_host = dict(zip(ring, hosts)) assert nts_int.make_token_replica_map(token_to_host, ring) == nts_str.make_token_replica_map(token_to_host, ring) @@ -182,30 +183,30 @@ def test_nts_transient_parsing(self): # make token replica map ring = [MD5Token(0), MD5Token(1), MD5Token(2)] - hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy) for host in range(3)] + hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy, host_id=uuid.uuid4()) for host in range(3)] token_to_host = dict(zip(ring, hosts)) assert nts_transient.make_token_replica_map(token_to_host, ring) == nts_str.make_token_replica_map(token_to_host, ring) def test_nts_make_token_replica_map(self): token_to_host_owner = {} - dc1_1 = Host('dc1.1', SimpleConvictionPolicy) - dc1_2 = Host('dc1.2', SimpleConvictionPolicy) - dc1_3 = Host('dc1.3', SimpleConvictionPolicy) + dc1_1 = Host('dc1.1', SimpleConvictionPolicy, host_id=uuid.uuid4()) + dc1_2 = Host('dc1.2', SimpleConvictionPolicy, host_id=uuid.uuid4()) + dc1_3 = Host('dc1.3', SimpleConvictionPolicy, host_id=uuid.uuid4()) for host in (dc1_1, dc1_2, dc1_3): host.set_location_info('dc1', 'rack1') token_to_host_owner[MD5Token(0)] = dc1_1 token_to_host_owner[MD5Token(100)] = dc1_2 token_to_host_owner[MD5Token(200)] = dc1_3 - dc2_1 = Host('dc2.1', SimpleConvictionPolicy) - dc2_2 = Host('dc2.2', SimpleConvictionPolicy) + dc2_1 = Host('dc2.1', SimpleConvictionPolicy, host_id=uuid.uuid4()) + dc2_2 = Host('dc2.2', SimpleConvictionPolicy, host_id=uuid.uuid4()) dc2_1.set_location_info('dc2', 'rack1') dc2_2.set_location_info('dc2', 'rack1') token_to_host_owner[MD5Token(1)] = dc2_1 token_to_host_owner[MD5Token(101)] = dc2_2 - dc3_1 = Host('dc3.1', SimpleConvictionPolicy) + dc3_1 = Host('dc3.1', SimpleConvictionPolicy, host_id=uuid.uuid4()) dc3_1.set_location_info('dc3', 'rack3') token_to_host_owner[MD5Token(2)] = dc3_1 @@ -240,7 +241,7 @@ def test_nts_token_performance(self): vnodes_per_host = 500 for i in range(dc1hostnum): - host = Host('dc1.{0}'.format(i), SimpleConvictionPolicy) + host = Host('dc1.{0}'.format(i), SimpleConvictionPolicy, host_id=uuid.uuid4()) host.set_location_info('dc1', "rack1") for vnode_num in range(vnodes_per_host): md5_token = MD5Token(current_token+vnode_num) @@ -264,10 +265,10 @@ def test_nts_make_token_replica_map_multi_rack(self): token_to_host_owner = {} # (A) not enough distinct racks, first skipped is used - dc1_1 = Host('dc1.1', SimpleConvictionPolicy) - dc1_2 = Host('dc1.2', SimpleConvictionPolicy) - dc1_3 = Host('dc1.3', SimpleConvictionPolicy) - dc1_4 = Host('dc1.4', SimpleConvictionPolicy) + dc1_1 = Host('dc1.1', SimpleConvictionPolicy, host_id=uuid.uuid4()) + dc1_2 = Host('dc1.2', SimpleConvictionPolicy, host_id=uuid.uuid4()) + dc1_3 = Host('dc1.3', SimpleConvictionPolicy, host_id=uuid.uuid4()) + dc1_4 = Host('dc1.4', SimpleConvictionPolicy, host_id=uuid.uuid4()) dc1_1.set_location_info('dc1', 'rack1') dc1_2.set_location_info('dc1', 'rack1') dc1_3.set_location_info('dc1', 'rack2') @@ -278,9 +279,9 @@ def test_nts_make_token_replica_map_multi_rack(self): token_to_host_owner[MD5Token(300)] = dc1_4 # (B) distinct racks, but not contiguous - dc2_1 = Host('dc2.1', SimpleConvictionPolicy) - dc2_2 = Host('dc2.2', SimpleConvictionPolicy) - dc2_3 = Host('dc2.3', SimpleConvictionPolicy) + dc2_1 = Host('dc2.1', SimpleConvictionPolicy, host_id=uuid.uuid4()) + dc2_2 = Host('dc2.2', SimpleConvictionPolicy, host_id=uuid.uuid4()) + dc2_3 = Host('dc2.3', SimpleConvictionPolicy, host_id=uuid.uuid4()) dc2_1.set_location_info('dc2', 'rack1') dc2_2.set_location_info('dc2', 'rack1') dc2_3.set_location_info('dc2', 'rack2') @@ -303,7 +304,7 @@ def test_nts_make_token_replica_map_multi_rack(self): assertCountEqual(token_replicas, (dc1_1, dc1_2, dc1_3, dc2_1, dc2_3)) def test_nts_make_token_replica_map_empty_dc(self): - host = Host('1', SimpleConvictionPolicy) + host = Host('1', SimpleConvictionPolicy, host_id=uuid.uuid4()) host.set_location_info('dc1', 'rack1') token_to_host_owner = {MD5Token(0): host} ring = [MD5Token(0)] @@ -317,9 +318,9 @@ def test_nts_export_for_schema(self): assert "{'class': 'NetworkTopologyStrategy', 'dc1': '1', 'dc2': '2'}" == strategy.export_for_schema() def test_simple_strategy_make_token_replica_map(self): - host1 = Host('1', SimpleConvictionPolicy) - host2 = Host('2', SimpleConvictionPolicy) - host3 = Host('3', SimpleConvictionPolicy) + host1 = Host('1', SimpleConvictionPolicy, host_id=uuid.uuid4()) + host2 = Host('2', SimpleConvictionPolicy, host_id=uuid.uuid4()) + host3 = Host('3', SimpleConvictionPolicy, host_id=uuid.uuid4()) token_to_host_owner = { MD5Token(0): host1, MD5Token(100): host2, @@ -408,7 +409,7 @@ def test_is_valid_name(self): class GetReplicasTest(unittest.TestCase): def _get_replicas(self, token_klass): tokens = [token_klass(i) for i in range(0, (2 ** 127 - 1), 2 ** 125)] - hosts = [Host("ip%d" % i, SimpleConvictionPolicy) for i in range(len(tokens))] + hosts = [Host("ip%d" % i, SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(len(tokens))] token_to_primary_replica = dict(zip(tokens, hosts)) keyspace = KeyspaceMetadata("ks", True, "SimpleStrategy", {"replication_factor": "1"}) metadata = Mock(spec=Metadata, keyspaces={'ks': keyspace}) @@ -817,8 +818,8 @@ def test_iterate_all_hosts_and_modify(self): PYTHON-572 """ metadata = Metadata() - metadata.add_or_return_host(Host('dc1.1', SimpleConvictionPolicy)) - metadata.add_or_return_host(Host('dc1.2', SimpleConvictionPolicy)) + metadata.add_or_return_host(Host('dc1.1', SimpleConvictionPolicy, host_id=uuid.uuid4())) + metadata.add_or_return_host(Host('dc1.2', SimpleConvictionPolicy, host_id=uuid.uuid4())) assert len(metadata.all_hosts()) == 2 diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index e15705c8f7..65feaf72e5 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -17,6 +17,7 @@ from itertools import islice, cycle from unittest.mock import Mock, patch, call from random import randint +import uuid import pytest from _thread import LockType import sys @@ -46,7 +47,7 @@ def test_non_implemented(self): """ policy = LoadBalancingPolicy() - host = Host(DefaultEndPoint("ip1"), SimpleConvictionPolicy) + host = Host(DefaultEndPoint("ip1"), SimpleConvictionPolicy, host_id=uuid.uuid4()) host.set_location_info("dc1", "rack1") with pytest.raises(NotImplementedError): @@ -192,11 +193,11 @@ class TestRackOrDCAwareRoundRobinPolicy: def test_no_remote(self, policy_specialization, constructor_args): hosts = [] for i in range(2): - h = Host(DefaultEndPoint(i), SimpleConvictionPolicy) + h = Host(DefaultEndPoint(i), SimpleConvictionPolicy, host_id=uuid.uuid4()) h.set_location_info("dc1", "rack2") hosts.append(h) for i in range(2): - h = Host(DefaultEndPoint(i + 2), SimpleConvictionPolicy) + h = Host(DefaultEndPoint(i + 2), SimpleConvictionPolicy, host_id=uuid.uuid4()) h.set_location_info("dc1", "rack1") hosts.append(h) @@ -208,7 +209,7 @@ def test_no_remote(self, policy_specialization, constructor_args): assert sorted(qplan) == sorted(hosts) def test_with_remotes(self, policy_specialization, constructor_args): - hosts = [Host(DefaultEndPoint(i), SimpleConvictionPolicy) for i in range(6)] + hosts = [Host(DefaultEndPoint(i), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(6)] for h in hosts[:2]: h.set_location_info("dc1", "rack1") for h in hosts[2:4]: @@ -263,7 +264,7 @@ def test_get_distance(self, policy_specialization, constructor_args): policy = policy_specialization(*constructor_args, used_hosts_per_remote_dc=0) # same dc, same rack - host = Host(DefaultEndPoint("ip1"), SimpleConvictionPolicy) + host = Host(DefaultEndPoint("ip1"), SimpleConvictionPolicy, host_id=uuid.uuid4()) host.set_location_info("dc1", "rack1") policy.populate(Mock(), [host]) @@ -273,14 +274,14 @@ def test_get_distance(self, policy_specialization, constructor_args): assert policy.distance(host) == HostDistance.LOCAL_RACK # same dc different rack - host = Host(DefaultEndPoint("ip1"), SimpleConvictionPolicy) + host = Host(DefaultEndPoint("ip1"), SimpleConvictionPolicy, host_id=uuid.uuid4()) host.set_location_info("dc1", "rack2") policy.populate(Mock(), [host]) assert policy.distance(host) == HostDistance.LOCAL # used_hosts_per_remote_dc is set to 0, so ignore it - remote_host = Host(DefaultEndPoint("ip2"), SimpleConvictionPolicy) + remote_host = Host(DefaultEndPoint("ip2"), SimpleConvictionPolicy, host_id=uuid.uuid4()) remote_host.set_location_info("dc2", "rack1") assert policy.distance(remote_host) == HostDistance.IGNORED @@ -294,14 +295,14 @@ def test_get_distance(self, policy_specialization, constructor_args): # since used_hosts_per_remote_dc is set to 1, only the first # remote host in dc2 will be REMOTE, the rest are IGNORED - second_remote_host = Host(DefaultEndPoint("ip3"), SimpleConvictionPolicy) + second_remote_host = Host(DefaultEndPoint("ip3"), SimpleConvictionPolicy, host_id=uuid.uuid4()) second_remote_host.set_location_info("dc2", "rack1") policy.populate(Mock(), [host, remote_host, second_remote_host]) distances = set([policy.distance(remote_host), policy.distance(second_remote_host)]) assert distances == set([HostDistance.REMOTE, HostDistance.IGNORED]) def test_status_updates(self, policy_specialization, constructor_args): - hosts = [Host(DefaultEndPoint(i), SimpleConvictionPolicy) for i in range(5)] + hosts = [Host(DefaultEndPoint(i), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(5)] for h in hosts[:2]: h.set_location_info("dc1", "rack1") for h in hosts[2:4]: @@ -314,11 +315,11 @@ def test_status_updates(self, policy_specialization, constructor_args): policy.on_down(hosts[0]) policy.on_remove(hosts[2]) - new_local_host = Host(DefaultEndPoint(5), SimpleConvictionPolicy) + new_local_host = Host(DefaultEndPoint(5), SimpleConvictionPolicy, host_id=uuid.uuid4()) new_local_host.set_location_info("dc1", "rack1") policy.on_up(new_local_host) - new_remote_host = Host(DefaultEndPoint(6), SimpleConvictionPolicy) + new_remote_host = Host(DefaultEndPoint(6), SimpleConvictionPolicy, host_id=uuid.uuid4()) new_remote_host.set_location_info("dc9000", "rack1") policy.on_add(new_remote_host) @@ -343,7 +344,7 @@ def test_status_updates(self, policy_specialization, constructor_args): assert qplan == [] def test_modification_during_generation(self, policy_specialization, constructor_args): - hosts = [Host(DefaultEndPoint(i), SimpleConvictionPolicy) for i in range(4)] + hosts = [Host(DefaultEndPoint(i), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(4)] for h in hosts[:2]: h.set_location_info("dc1", "rack1") for h in hosts[2:]: @@ -357,7 +358,7 @@ def test_modification_during_generation(self, policy_specialization, constructor # approach that changes specific things during known phases of the # generator. - new_host = Host(DefaultEndPoint(4), SimpleConvictionPolicy) + new_host = Host(DefaultEndPoint(4), SimpleConvictionPolicy, host_id=uuid.uuid4()) new_host.set_location_info("dc1", "rack1") # new local before iteration @@ -468,7 +469,7 @@ def test_modification_during_generation(self, policy_specialization, constructor policy.on_up(hosts[2]) policy.on_up(hosts[3]) - another_host = Host(DefaultEndPoint(5), SimpleConvictionPolicy) + another_host = Host(DefaultEndPoint(5), SimpleConvictionPolicy, host_id=uuid.uuid4()) another_host.set_location_info("dc3", "rack1") new_host.set_location_info("dc3", "rack1") @@ -502,7 +503,7 @@ def test_no_live_nodes(self, policy_specialization, constructor_args): hosts = [] for i in range(4): - h = Host(DefaultEndPoint(i), SimpleConvictionPolicy) + h = Host(DefaultEndPoint(i), SimpleConvictionPolicy, host_id=uuid.uuid4()) h.set_location_info("dc1", "rack1") hosts.append(h) @@ -527,7 +528,7 @@ def test_no_nodes(self, policy_specialization, constructor_args): assert qplan == [] def test_wrong_dc(self, policy_specialization, constructor_args): - hosts = [Host(DefaultEndPoint(i), SimpleConvictionPolicy) for i in range(3)] + hosts = [Host(DefaultEndPoint(i), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(3)] for h in hosts[:3]: h.set_location_info("dc2", "rack2") @@ -539,9 +540,9 @@ def test_wrong_dc(self, policy_specialization, constructor_args): class DCAwareRoundRobinPolicyTest(unittest.TestCase): def test_default_dc(self): - host_local = Host(DefaultEndPoint(1), SimpleConvictionPolicy, 'local') - host_remote = Host(DefaultEndPoint(2), SimpleConvictionPolicy, 'remote') - host_none = Host(DefaultEndPoint(1), SimpleConvictionPolicy) + host_local = Host(DefaultEndPoint(1), SimpleConvictionPolicy, 'local', host_id=uuid.uuid4()) + host_remote = Host(DefaultEndPoint(2), SimpleConvictionPolicy, 'remote', host_id=uuid.uuid4()) + host_none = Host(DefaultEndPoint(1), SimpleConvictionPolicy, host_id=uuid.uuid4()) # contact point is '1' cluster = Mock(endpoints_resolved=[DefaultEndPoint(1)]) @@ -585,7 +586,7 @@ def test_wrap_round_robin(self): cluster.metadata = Mock(spec=Metadata) cluster.metadata._tablets = Mock(spec=Tablets) cluster.metadata._tablets.table_has_tablets.return_value = [] - hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy) for i in range(4)] + hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(4)] for host in hosts: host.set_up() @@ -618,7 +619,7 @@ def test_wrap_dc_aware(self): cluster.metadata = Mock(spec=Metadata) cluster.metadata._tablets = Mock(spec=Tablets) cluster.metadata._tablets.table_has_tablets.return_value = [] - hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy) for i in range(4)] + hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(4)] for host in hosts: host.set_up() for h in hosts[:2]: @@ -667,7 +668,7 @@ def test_wrap_rack_aware(self): cluster.metadata = Mock(spec=Metadata) cluster.metadata._tablets = Mock(spec=Tablets) cluster.metadata._tablets.table_has_tablets.return_value = [] - hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy) for i in range(8)] + hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(8)] for host in hosts: host.set_up() hosts[0].set_location_info("dc1", "rack1") @@ -731,7 +732,7 @@ def test_get_distance(self): """ policy = TokenAwarePolicy(DCAwareRoundRobinPolicy("dc1", used_hosts_per_remote_dc=0)) - host = Host(DefaultEndPoint("ip1"), SimpleConvictionPolicy) + host = Host(DefaultEndPoint("ip1"), SimpleConvictionPolicy, host_id=uuid.uuid4()) host.set_location_info("dc1", "rack1") policy.populate(self.FakeCluster(), [host]) @@ -739,7 +740,7 @@ def test_get_distance(self): assert policy.distance(host) == HostDistance.LOCAL # used_hosts_per_remote_dc is set to 0, so ignore it - remote_host = Host(DefaultEndPoint("ip2"), SimpleConvictionPolicy) + remote_host = Host(DefaultEndPoint("ip2"), SimpleConvictionPolicy, host_id=uuid.uuid4()) remote_host.set_location_info("dc2", "rack1") assert policy.distance(remote_host) == HostDistance.IGNORED @@ -753,7 +754,7 @@ def test_get_distance(self): # since used_hosts_per_remote_dc is set to 1, only the first # remote host in dc2 will be REMOTE, the rest are IGNORED - second_remote_host = Host(DefaultEndPoint("ip3"), SimpleConvictionPolicy) + second_remote_host = Host(DefaultEndPoint("ip3"), SimpleConvictionPolicy, host_id=uuid.uuid4()) second_remote_host.set_location_info("dc2", "rack1") policy.populate(self.FakeCluster(), [host, remote_host, second_remote_host]) distances = set([policy.distance(remote_host), policy.distance(second_remote_host)]) @@ -764,7 +765,7 @@ def test_status_updates(self): Same test as DCAwareRoundRobinPolicyTest.test_status_updates() """ - hosts = [Host(DefaultEndPoint(i), SimpleConvictionPolicy) for i in range(4)] + hosts = [Host(DefaultEndPoint(i), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(4)] for h in hosts[:2]: h.set_location_info("dc1", "rack1") for h in hosts[2:]: @@ -775,11 +776,11 @@ def test_status_updates(self): policy.on_down(hosts[0]) policy.on_remove(hosts[2]) - new_local_host = Host(DefaultEndPoint(4), SimpleConvictionPolicy) + new_local_host = Host(DefaultEndPoint(4), SimpleConvictionPolicy, host_id=uuid.uuid4()) new_local_host.set_location_info("dc1", "rack1") policy.on_up(new_local_host) - new_remote_host = Host(DefaultEndPoint(5), SimpleConvictionPolicy) + new_remote_host = Host(DefaultEndPoint(5), SimpleConvictionPolicy, host_id=uuid.uuid4()) new_remote_host.set_location_info("dc9000", "rack1") policy.on_add(new_remote_host) @@ -802,7 +803,7 @@ def test_status_updates(self): assert qplan == [] def test_statement_keyspace(self): - hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy) for i in range(4)] + hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(4)] for host in hosts: host.set_up() @@ -896,7 +897,7 @@ def test_no_shuffle_if_given_no_routing_key(self): self._assert_shuffle(cluster=self._prepare_cluster_with_tablets(), keyspace='keyspace', routing_key=None) def _prepare_cluster_with_vnodes(self): - hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy) for i in range(4)] + hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(4)] for host in hosts: host.set_up() cluster = Mock(spec=Cluster) @@ -908,7 +909,7 @@ def _prepare_cluster_with_vnodes(self): return cluster def _prepare_cluster_with_tablets(self): - hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy) for i in range(4)] + hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(4)] for host in hosts: host.set_up() cluster = Mock(spec=Cluster) @@ -1422,7 +1423,7 @@ class WhiteListRoundRobinPolicyTest(unittest.TestCase): def test_hosts_with_hostname(self): hosts = ['localhost'] policy = WhiteListRoundRobinPolicy(hosts) - host = Host(DefaultEndPoint("127.0.0.1"), SimpleConvictionPolicy) + host = Host(DefaultEndPoint("127.0.0.1"), SimpleConvictionPolicy, host_id=uuid.uuid4()) policy.populate(None, [host]) qplan = list(policy.make_query_plan()) @@ -1433,7 +1434,7 @@ def test_hosts_with_hostname(self): def test_hosts_with_socket_hostname(self): hosts = [UnixSocketEndPoint('/tmp/scylla-workdir/cql.m')] policy = WhiteListRoundRobinPolicy(hosts) - host = Host(UnixSocketEndPoint('/tmp/scylla-workdir/cql.m'), SimpleConvictionPolicy) + host = Host(UnixSocketEndPoint('/tmp/scylla-workdir/cql.m'), SimpleConvictionPolicy, host_id=uuid.uuid4()) policy.populate(None, [host]) qplan = list(policy.make_query_plan()) @@ -1559,8 +1560,8 @@ def setUp(self): child_policy=Mock(name='child_policy', distance=Mock(name='distance')), predicate=lambda host: host.address == 'acceptme' ) - self.ignored_host = Host(DefaultEndPoint('ignoreme'), conviction_policy_factory=Mock()) - self.accepted_host = Host(DefaultEndPoint('acceptme'), conviction_policy_factory=Mock()) + self.ignored_host = Host(DefaultEndPoint('ignoreme'), conviction_policy_factory=Mock(), host_id=uuid.uuid4()) + self.accepted_host = Host(DefaultEndPoint('acceptme'), conviction_policy_factory=Mock(), host_id=uuid.uuid4()) def test_ignored_with_filter(self): assert self.hfp.distance(self.ignored_host) == HostDistance.IGNORED @@ -1629,7 +1630,7 @@ def test_query_plan_deferred_to_child(self): def test_wrap_token_aware(self): cluster = Mock(spec=Cluster) - hosts = [Host(DefaultEndPoint("127.0.0.{}".format(i)), SimpleConvictionPolicy) for i in range(1, 6)] + hosts = [Host(DefaultEndPoint("127.0.0.{}".format(i)), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(1, 6)] for host in hosts: host.set_up() @@ -1656,13 +1657,13 @@ def get_replicas(keyspace, packed_key): query_plan = hfp.make_query_plan("keyspace", mocked_query) # First the not filtered replica, and then the rest of the allowed hosts ordered query_plan = list(query_plan) - assert query_plan[0] == Host(DefaultEndPoint("127.0.0.2"), SimpleConvictionPolicy) - assert set(query_plan[1:]) == {Host(DefaultEndPoint("127.0.0.3"), SimpleConvictionPolicy), - Host(DefaultEndPoint("127.0.0.5"), SimpleConvictionPolicy)} + assert query_plan[0] == Host(DefaultEndPoint("127.0.0.2"), SimpleConvictionPolicy, host_id=uuid.uuid4()) + assert set(query_plan[1:]) == {Host(DefaultEndPoint("127.0.0.3"), SimpleConvictionPolicy, host_id=uuid.uuid4()), + Host(DefaultEndPoint("127.0.0.5"), SimpleConvictionPolicy, host_id=uuid.uuid4())} def test_create_whitelist(self): cluster = Mock(spec=Cluster) - hosts = [Host(DefaultEndPoint("127.0.0.{}".format(i)), SimpleConvictionPolicy) for i in range(1, 6)] + hosts = [Host(DefaultEndPoint("127.0.0.{}".format(i)), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(1, 6)] for host in hosts: host.set_up() @@ -1680,5 +1681,5 @@ def test_create_whitelist(self): mocked_query = Mock() query_plan = hfp.make_query_plan("keyspace", mocked_query) # Only the filtered replicas should be allowed - assert set(query_plan) == {Host(DefaultEndPoint("127.0.0.1"), SimpleConvictionPolicy), - Host(DefaultEndPoint("127.0.0.4"), SimpleConvictionPolicy)} + assert set(query_plan) == {Host(DefaultEndPoint("127.0.0.1"), SimpleConvictionPolicy, host_id=uuid.uuid4()), + Host(DefaultEndPoint("127.0.0.4"), SimpleConvictionPolicy, host_id=uuid.uuid4())} diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index 3390f6dbd6..a5bd028b26 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -1002,11 +1002,11 @@ def test_host_order(self): @test_category data_types """ - hosts = [Host(addr, SimpleConvictionPolicy) for addr in + hosts = [Host(addr, SimpleConvictionPolicy, host_id=uuid.uuid4()) for addr in ("127.0.0.1", "127.0.0.2", "127.0.0.3", "127.0.0.4")] - hosts_equal = [Host(addr, SimpleConvictionPolicy) for addr in + hosts_equal = [Host(addr, SimpleConvictionPolicy, host_id=uuid.uuid4()) for addr in ("127.0.0.1", "127.0.0.1")] - hosts_equal_conviction = [Host("127.0.0.1", SimpleConvictionPolicy), Host("127.0.0.1", ConvictionPolicy)] + hosts_equal_conviction = [Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4()), Host("127.0.0.1", ConvictionPolicy, host_id=uuid.uuid4())] check_sequence_consistency(hosts) check_sequence_consistency(hosts_equal, equal=True) check_sequence_consistency(hosts_equal_conviction, equal=True) From 2766ad8656aa2ded36c85085b8ad735715abce1f Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Tue, 30 Dec 2025 09:23:32 +0100 Subject: [PATCH 173/298] tests/integration/standard: return empty query plan if there are no live hosts --- tests/integration/standard/test_query.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index a3bdf8a735..9cebc22b05 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -460,7 +460,8 @@ def make_query_plan(self, working_keyspace=None, query=None): live_hosts = sorted(list(self._live_hosts)) host = [] try: - host = [live_hosts[self.host_index_to_use]] + if len(live_hosts) > 0: + host = [live_hosts[self.host_index_to_use]] except IndexError as e: raise IndexError( 'You specified an index larger than the number of hosts. Total hosts: {}. Index specified: {}'.format( From 2a369df2da712b541063b59a48a3302e1c71111e Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Mon, 12 Jan 2026 13:29:48 +0100 Subject: [PATCH 174/298] tests/integration/standard: allow execute to throw Unavailable exception --- tests/integration/standard/test_metrics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/standard/test_metrics.py b/tests/integration/standard/test_metrics.py index 8ccd278ee4..48c7b49b95 100644 --- a/tests/integration/standard/test_metrics.py +++ b/tests/integration/standard/test_metrics.py @@ -218,7 +218,7 @@ def test_metrics_per_cluster(self): try: # Test write query = SimpleStatement("INSERT INTO {0}.{0} (k, v) VALUES (2, 2)".format(self.ks_name), consistency_level=ConsistencyLevel.ALL) - with pytest.raises(WriteTimeout): + with pytest.raises((WriteTimeout, Unavailable)): self.session.execute(query, timeout=None) finally: get_node(1).resume() @@ -230,7 +230,7 @@ def test_metrics_per_cluster(self): stats_cluster2 = cluster2.metrics.get_stats() # Test direct access to stats - assert 1 == self.cluster.metrics.stats.write_timeouts + assert (1 == self.cluster.metrics.stats.write_timeouts or 1 == self.cluster.metrics.stats.unavailables) assert 0 == cluster2.metrics.stats.write_timeouts # Test direct access to a child stats From 3419cd7c743681c09b695ca64de0184fe7721b52 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Thu, 8 Jan 2026 13:20:26 +0100 Subject: [PATCH 175/298] Don't check if host is in initial contact points when setting default local_dc --- cassandra/policies.py | 18 +++++------------- tests/unit/test_policies.py | 11 +---------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/cassandra/policies.py b/cassandra/policies.py index bcfd797706..7eea1e709a 100644 --- a/cassandra/policies.py +++ b/cassandra/policies.py @@ -245,7 +245,6 @@ def __init__(self, local_dc='', used_hosts_per_remote_dc=0): self.used_hosts_per_remote_dc = used_hosts_per_remote_dc self._dc_live_hosts = {} self._position = 0 - self._endpoints = [] LoadBalancingPolicy.__init__(self) def _dc(self, host): @@ -255,11 +254,6 @@ def populate(self, cluster, hosts): for dc, dc_hosts in groupby(hosts, lambda h: self._dc(h)): self._dc_live_hosts[dc] = tuple({*dc_hosts, *self._dc_live_hosts.get(dc, [])}) - if not self.local_dc: - self._endpoints = [ - endpoint - for endpoint in cluster.endpoints_resolved] - self._position = randint(0, len(hosts) - 1) if hosts else 0 def distance(self, host): @@ -301,13 +295,11 @@ def on_up(self, host): # not worrying about threads because this will happen during # control connection startup/refresh if not self.local_dc and host.datacenter: - if host.endpoint in self._endpoints: - self.local_dc = host.datacenter - log.info("Using datacenter '%s' for DCAwareRoundRobinPolicy (via host '%s'); " - "if incorrect, please specify a local_dc to the constructor, " - "or limit contact points to local cluster nodes" % - (self.local_dc, host.endpoint)) - del self._endpoints + self.local_dc = host.datacenter + log.info("Using datacenter '%s' for DCAwareRoundRobinPolicy (via host '%s'); " + "if incorrect, please specify a local_dc to the constructor, " + "or limit contact points to local cluster nodes" % + (self.local_dc, host.endpoint)) dc = self._dc(host) with self._hosts_lock: diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index 65feaf72e5..ecaf6ca7e4 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -556,15 +556,6 @@ def test_default_dc(self): assert policy.local_dc != host_remote.datacenter assert policy.local_dc == host_local.datacenter - # contact DC second - policy = DCAwareRoundRobinPolicy() - policy.populate(cluster, [host_none]) - assert not policy.local_dc - policy.on_add(host_remote) - policy.on_add(host_local) - assert policy.local_dc != host_remote.datacenter - assert policy.local_dc == host_local.datacenter - # no DC policy = DCAwareRoundRobinPolicy() policy.populate(cluster, [host_none]) @@ -577,7 +568,7 @@ def test_default_dc(self): policy.populate(cluster, [host_none]) assert not policy.local_dc policy.on_add(host_remote) - assert not policy.local_dc + assert policy.local_dc class TokenAwarePolicyTest(unittest.TestCase): From a3adf76d36b3cf61c1621ddd9fa05a7bfe66efd4 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Thu, 8 Jan 2026 15:30:03 +0100 Subject: [PATCH 176/298] Call on_add before distance to properly initialize lbp In DC aware lbp when local_dc is not provided we set it in on_add and it needs to be initialized for distance to give proper results. --- cassandra/cluster.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 8db58c13cd..8af4e19801 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -2016,14 +2016,14 @@ def on_add(self, host, refresh_nodes=True): log.debug("Handling new host %r and notifying listeners", host) + self.profile_manager.on_add(host) + self.control_connection.on_add(host, refresh_nodes) + distance = self.profile_manager.distance(host) if distance != HostDistance.IGNORED: self._prepare_all_queries(host) log.debug("Done preparing queries for new host %r", host) - self.profile_manager.on_add(host) - self.control_connection.on_add(host, refresh_nodes) - if distance == HostDistance.IGNORED: log.debug("Not adding connection pool for new host %r because the " "load balancing policy has marked it as IGNORED", host) From a0cde2e01925947520bb0bbccbbe00e10fe209e5 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Mon, 29 Dec 2025 14:57:42 +0100 Subject: [PATCH 177/298] Don't create Host instances with random host_id Previously, we used endpoints provided to the cluster to create Host instances with random host_ids in order to populate the LBP before the ControlConnection was established. This logic led to creating many connections that were opened and then quickly closed, because once we learned the correct host_ids from system.peers, we removed the old Hosts with random IDs and created new ones with the proper host_ids. This commit introduces a new approach. To establish the ControlConnection, we now use only the resolved contact points from the cluster configuration. Only after a successful connection do we populate Host information in the LBP. If the LBP is already initialized during ControlConnection reconnection, we reuse the existing values. --- cassandra/cluster.py | 108 +++++++----------------------------------- cassandra/metadata.py | 2 +- cassandra/pool.py | 2 +- 3 files changed, 20 insertions(+), 92 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 8af4e19801..a9c1d00e97 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -1683,14 +1683,7 @@ def protocol_downgrade(self, host_endpoint, previous_version): "http://datastax.github.io/python-driver/api/cassandra/cluster.html#cassandra.cluster.Cluster.protocol_version", self.protocol_version, new_version, host_endpoint) self.protocol_version = new_version - def _add_resolved_hosts(self): - for endpoint in self.endpoints_resolved: - host, new = self.add_host(endpoint, signal=False) - if new: - host.set_up() - for listener in self.listeners: - listener.on_add(host) - + def _populate_hosts(self): self.profile_manager.populate( weakref.proxy(self), self.metadata.all_hosts()) self.load_balancing_policy.populate( @@ -1718,16 +1711,9 @@ def connect(self, keyspace=None, wait_for_all_pools=False): self.connection_class.initialize_reactor() _register_cluster_shutdown(self) - self._add_resolved_hosts() - try: self.control_connection.connect() - - # we set all contact points up for connecting, but we won't infer state after this - for endpoint in self.endpoints_resolved: - h = self.metadata.get_host(endpoint) - if h and self.profile_manager.distance(h) == HostDistance.IGNORED: - h.is_up = None + self._populate_hosts() log.debug("Control connection created") except Exception: @@ -3535,20 +3521,18 @@ def _set_new_connection(self, conn): log.debug("[control connection] Closing old connection %r, replacing with %r", old, conn) old.close() - def _connect_host_in_lbp(self): + def _try_connect_to_hosts(self): errors = {} - lbp = ( - self._cluster.load_balancing_policy - if self._cluster._config_mode == _ConfigMode.LEGACY else - self._cluster._default_load_balancing_policy - ) - for host in lbp.make_query_plan(): + lbp = self._cluster.load_balancing_policy \ + if self._cluster._config_mode == _ConfigMode.LEGACY else self._cluster._default_load_balancing_policy + + for endpoint in chain((host.endpoint for host in lbp.make_query_plan()), self._cluster.endpoints_resolved): try: - return (self._try_connect(host.endpoint), None) + return (self._try_connect(endpoint), None) except Exception as exc: - errors[str(host.endpoint)] = exc - log.warning("[control connection] Error connecting to %s:", host, exc_info=True) + errors[str(endpoint)] = exc + log.warning("[control connection] Error connecting to %s:", endpoint, exc_info=True) if self._is_shutdown: raise DriverException("[control connection] Reconnection in progress during shutdown") @@ -3563,16 +3547,16 @@ def _reconnect_internal(self): to the exception that was raised when an attempt was made to open a connection to that host. """ - (conn, _) = self._connect_host_in_lbp() + (conn, _) = self._try_connect_to_hosts() if conn is not None: return conn # Try to re-resolve hostnames as a fallback when all hosts are unreachable self._cluster._resolve_hostnames() - self._cluster._add_resolved_hosts() + self._cluster._populate_hosts() - (conn, errors) = self._connect_host_in_lbp() + (conn, errors) = self._try_connect_to_hosts() if conn is not None: return conn @@ -3814,67 +3798,10 @@ def _refresh_node_list_and_token_map(self, connection, preloaded_results=None, self._cluster.metadata.cluster_name = cluster_name partitioner = local_row.get("partitioner") - tokens = local_row.get("tokens") - - host = self._cluster.metadata.get_host(connection.original_endpoint) - if host: - datacenter = local_row.get("data_center") - rack = local_row.get("rack") - self._update_location_info(host, datacenter, rack) - - # support the use case of connecting only with public address - if isinstance(self._cluster.endpoint_factory, SniEndPointFactory): - new_endpoint = self._cluster.endpoint_factory.create(local_row) - - if new_endpoint.address: - host.endpoint = new_endpoint - - host.host_id = local_row.get("host_id") - - found_host_ids.add(host.host_id) - found_endpoints.add(host.endpoint) - - host.listen_address = local_row.get("listen_address") - host.listen_port = local_row.get("listen_port") - host.broadcast_address = _NodeInfo.get_broadcast_address(local_row) - host.broadcast_port = _NodeInfo.get_broadcast_port(local_row) - - host.broadcast_rpc_address = _NodeInfo.get_broadcast_rpc_address(local_row) - host.broadcast_rpc_port = _NodeInfo.get_broadcast_rpc_port(local_row) - if host.broadcast_rpc_address is None: - if self._token_meta_enabled: - # local rpc_address is not available, use the connection endpoint - host.broadcast_rpc_address = connection.endpoint.address - host.broadcast_rpc_port = connection.endpoint.port - else: - # local rpc_address has not been queried yet, try to fetch it - # separately, which might fail because C* < 2.1.6 doesn't have rpc_address - # in system.local. See CASSANDRA-9436. - local_rpc_address_query = QueryMessage( - query=maybe_add_timeout_to_query(self._SELECT_LOCAL_NO_TOKENS_RPC_ADDRESS, self._metadata_request_timeout), - consistency_level=ConsistencyLevel.ONE) - success, local_rpc_address_result = connection.wait_for_response( - local_rpc_address_query, timeout=self._timeout, fail_on_error=False) - if success: - row = dict_factory( - local_rpc_address_result.column_names, - local_rpc_address_result.parsed_rows) - host.broadcast_rpc_address = _NodeInfo.get_broadcast_rpc_address(row[0]) - host.broadcast_rpc_port = _NodeInfo.get_broadcast_rpc_port(row[0]) - else: - host.broadcast_rpc_address = connection.endpoint.address - host.broadcast_rpc_port = connection.endpoint.port - - host.release_version = local_row.get("release_version") - host.dse_version = local_row.get("dse_version") - host.dse_workload = local_row.get("workload") - host.dse_workloads = local_row.get("workloads") + tokens = local_row.get("tokens", None) - if partitioner and tokens: - token_map[host] = tokens + peers_result.insert(0, local_row) - self._cluster.metadata.update_host(host, old_endpoint=connection.endpoint) - connection.original_endpoint = connection.endpoint = host.endpoint # Check metadata.partitioner to see if we haven't built anything yet. If # every node in the cluster was in the contact points, we won't discover # any new nodes, so we need this additional check. (See PYTHON-90) @@ -4173,8 +4100,9 @@ def _get_peers_query(self, peers_query_type, connection=None): query_template = (self._SELECT_SCHEMA_PEERS_TEMPLATE if peers_query_type == self.PeersQueryType.PEERS_SCHEMA else self._SELECT_PEERS_NO_TOKENS_TEMPLATE) - host_release_version = self._cluster.metadata.get_host(connection.original_endpoint).release_version - host_dse_version = self._cluster.metadata.get_host(connection.original_endpoint).dse_version + original_endpoint_host = self._cluster.metadata.get_host(connection.original_endpoint) + host_release_version = None if original_endpoint_host is None else original_endpoint_host.release_version + host_dse_version = None if original_endpoint_host is None else original_endpoint_host.dse_version uses_native_address_query = ( host_dse_version and Version(host_dse_version) >= self._MINIMUM_NATIVE_ADDRESS_DSE_VERSION) diff --git a/cassandra/metadata.py b/cassandra/metadata.py index 85f6c45ac6..b85308449e 100644 --- a/cassandra/metadata.py +++ b/cassandra/metadata.py @@ -3481,7 +3481,7 @@ def group_keys_by_replica(session, keyspace, table, keys): :class:`~.NO_VALID_REPLICA` Example usage:: - + >>> result = group_keys_by_replica( ... session, "system", "peers", ... (("127.0.0.1", ), ("127.0.0.2", ))) diff --git a/cassandra/pool.py b/cassandra/pool.py index b8a8ef7493..2da657256f 100644 --- a/cassandra/pool.py +++ b/cassandra/pool.py @@ -176,7 +176,7 @@ def __init__(self, endpoint, conviction_policy_factory, datacenter=None, rack=No self.endpoint = endpoint if isinstance(endpoint, EndPoint) else DefaultEndPoint(endpoint) self.conviction_policy = conviction_policy_factory(self) if not host_id: - host_id = uuid.uuid4() + raise ValueError("host_id may not be None") self.host_id = host_id self.set_location_info(datacenter, rack) self.lock = RLock() From 472461cc2920f03fcf51640dbc35a8deeaf143e9 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Wed, 21 Jan 2026 09:19:32 +0200 Subject: [PATCH 178/298] (improvement)TokenAwarePolicy::make_query_plan(): remove redundant check if a table is using tablets table_has_tablets() performs the same self._tablets.get((keyspace, table) that get_tablet_for_key() does which is a called a line later does, so it's redundant. Removed the former. Note - perhaps table_has_tablets() is not needed and can be removed? Unsure, it's unclear if it's part of the API or not. It's now completely unused across the code. Adjusted unit tests as well. Signed-off-by: Yaniv Kaul --- cassandra/policies.py | 11 +++++------ tests/unit/test_policies.py | 15 +++++++-------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/cassandra/policies.py b/cassandra/policies.py index 7eea1e709a..e742708019 100644 --- a/cassandra/policies.py +++ b/cassandra/policies.py @@ -503,15 +503,14 @@ def make_query_plan(self, working_keyspace=None, query=None): return replicas = [] - if self._cluster_metadata._tablets.table_has_tablets(keyspace, query.table): - tablet = self._cluster_metadata._tablets.get_tablet_for_key( + tablet = self._cluster_metadata._tablets.get_tablet_for_key( keyspace, query.table, self._cluster_metadata.token_map.token_class.from_key(query.routing_key)) - if tablet is not None: - replicas_mapped = set(map(lambda r: r[0], tablet.replicas)) - child_plan = child.make_query_plan(keyspace, query) + if tablet is not None: + replicas_mapped = set(map(lambda r: r[0], tablet.replicas)) + child_plan = child.make_query_plan(keyspace, query) - replicas = [host for host in child_plan if host.host_id in replicas_mapped] + replicas = [host for host in child_plan if host.host_id in replicas_mapped] else: replicas = self._cluster_metadata.get_replicas(keyspace, query.routing_key) diff --git a/tests/unit/test_policies.py b/tests/unit/test_policies.py index ecaf6ca7e4..6142af1aa1 100644 --- a/tests/unit/test_policies.py +++ b/tests/unit/test_policies.py @@ -576,7 +576,7 @@ def test_wrap_round_robin(self): cluster = Mock(spec=Cluster) cluster.metadata = Mock(spec=Metadata) cluster.metadata._tablets = Mock(spec=Tablets) - cluster.metadata._tablets.table_has_tablets.return_value = [] + cluster.metadata._tablets.get_tablet_for_key.return_value = None hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(4)] for host in hosts: host.set_up() @@ -609,7 +609,7 @@ def test_wrap_dc_aware(self): cluster = Mock(spec=Cluster) cluster.metadata = Mock(spec=Metadata) cluster.metadata._tablets = Mock(spec=Tablets) - cluster.metadata._tablets.table_has_tablets.return_value = [] + cluster.metadata._tablets.get_tablet_for_key.return_value = None hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(4)] for host in hosts: host.set_up() @@ -658,7 +658,7 @@ def test_wrap_rack_aware(self): cluster = Mock(spec=Cluster) cluster.metadata = Mock(spec=Metadata) cluster.metadata._tablets = Mock(spec=Tablets) - cluster.metadata._tablets.table_has_tablets.return_value = [] + cluster.metadata._tablets.get_tablet_for_key.return_value = None hosts = [Host(DefaultEndPoint(str(i)), SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(8)] for host in hosts: host.set_up() @@ -803,7 +803,7 @@ def test_statement_keyspace(self): cluster.metadata._tablets = Mock(spec=Tablets) replicas = hosts[2:] cluster.metadata.get_replicas.return_value = replicas - cluster.metadata._tablets.table_has_tablets.return_value = [] + cluster.metadata._tablets.get_tablet_for_key.return_value = None child_policy = Mock() child_policy.make_query_plan.return_value = hosts @@ -896,7 +896,7 @@ def _prepare_cluster_with_vnodes(self): cluster.metadata._tablets = Mock(spec=Tablets) cluster.metadata.all_hosts.return_value = hosts cluster.metadata.get_replicas.return_value = hosts[2:] - cluster.metadata._tablets.table_has_tablets.return_value = False + cluster.metadata._tablets.get_tablet_for_key.return_value = None return cluster def _prepare_cluster_with_tablets(self): @@ -908,7 +908,6 @@ def _prepare_cluster_with_tablets(self): cluster.metadata._tablets = Mock(spec=Tablets) cluster.metadata.all_hosts.return_value = hosts cluster.metadata.get_replicas.return_value = hosts[2:] - cluster.metadata._tablets.table_has_tablets.return_value = True cluster.metadata._tablets.get_tablet_for_key.return_value = Tablet(replicas=[(h.host_id, 0) for h in hosts[2:]]) return cluster @@ -923,7 +922,7 @@ def _assert_shuffle(self, patched_shuffle, cluster, keyspace, routing_key): policy = TokenAwarePolicy(child_policy, shuffle_replicas=True) policy.populate(cluster, hosts) - is_tablets = cluster.metadata._tablets.table_has_tablets() + is_tablets = cluster.metadata._tablets.get_tablet_for_key() is not None cluster.metadata.get_replicas.reset_mock() child_policy.make_query_plan.reset_mock() @@ -1630,7 +1629,7 @@ def get_replicas(keyspace, packed_key): cluster.metadata.get_replicas.side_effect = get_replicas cluster.metadata._tablets = Mock(spec=Tablets) - cluster.metadata._tablets.table_has_tablets.return_value = [] + cluster.metadata._tablets.get_tablet_for_key.return_value = None child_policy = TokenAwarePolicy(RoundRobinPolicy()) From 711a7eb139f10a8362d7a496bf1eb7db5d88368c Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sun, 18 Jan 2026 09:19:16 +0200 Subject: [PATCH 179/298] test: optimize test_fast_shutdown with event-based synchronization Replace sleep-based timing with proper Event synchronization: - Remove executor_init with 0.5s sleep - Replace time.sleep(0.5) with connection_created.wait() - Replace time.sleep(3) with executor.shutdown(wait=True) - Reduce iterations from 20 to 3 (deterministic with proper sync) - Add explicit assertions for shutdown state Performance improvement: - Before: 70.13s call tests/unit/test_host_connection_pool.py::HostConnectionTests::test_fast_shutdown - After: 0.01s call tests/unit/test_host_connection_pool.py::HostConnectionTests::test_fast_shutdown Signed-off-by: Yaniv Kaul --- tests/unit/test_host_connection_pool.py | 30 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/tests/unit/test_host_connection_pool.py b/tests/unit/test_host_connection_pool.py index 580eb336b2..f92bb53785 100644 --- a/tests/unit/test_host_connection_pool.py +++ b/tests/unit/test_host_connection_pool.py @@ -239,7 +239,8 @@ class MockSession(MagicMock): def __init__(self, *args, **kwargs): super(MockSession, self).__init__(*args, **kwargs) self.cluster = MagicMock() - self.cluster.executor = ThreadPoolExecutor(max_workers=2, initializer=self.executor_init) + self.connection_created = Event() + self.cluster.executor = ThreadPoolExecutor(max_workers=2) self.cluster.signal_connection_failure = lambda *args, **kwargs: False self.cluster.connection_factory = self.mock_connection_factory self.connection_counter = 0 @@ -259,23 +260,30 @@ def mock_connection_factory(self, *args, **kwargs): partitioner="", sharding_algorithm="", sharding_ignore_msb=0, shard_aware_port="", shard_aware_port_ssl="")) self.connection_counter += 1 + self.connection_created.set() return connection - def executor_init(self, *args): - time.sleep(0.5) - LOGGER.info("Future start: %s", args) - - for attempt_num in range(20): - LOGGER.info("Testing fast shutdown %d / 20 times", attempt_num + 1) + for attempt_num in range(3): + LOGGER.info("Testing fast shutdown %d / 3 times", attempt_num + 1) host = MagicMock() host.endpoint = "1.2.3.4" - session = self.make_session() + session = MockSession() pool = HostConnection(host=host, host_distance=HostDistance.REMOTE, session=session) LOGGER.info("Initialized pool %s", pool) + + # Wait for initial connection to be created (with timeout) + if not session.connection_created.wait(timeout=2.0): + pytest.fail("Initial connection failed to be created within 2 seconds") + LOGGER.info("Connections: %s", pool._connections) - time.sleep(0.5) + + # Shutdown the pool pool.shutdown() - time.sleep(3) - session.cluster.executor.shutdown() + + # Verify pool is shut down + assert pool.is_shutdown, "Pool should be marked as shutdown" + + # Cleanup executor with proper wait + session.cluster.executor.shutdown(wait=True) From 016b5de3bc37473c400e94d0483b4a56ad7b9e4f Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 23 Jan 2026 19:44:49 +0200 Subject: [PATCH 180/298] (Fix)race condition during host IP address update When a host changes its IP address, the driver previously updated the host endpoint to the new IP before calling on_down. This caused on_down to mistakenly target the new IP for connection cleanup. This change reorders the operations to ensure on_down cleans up the old IP's resources before the host object is updated and on_up is called for the new IP. Signed-off-by: Yaniv Kaul --- cassandra/cluster.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index a9c1d00e97..099043eae0 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -3831,14 +3831,16 @@ def _refresh_node_list_and_token_map(self, connection, preloaded_results=None, host = self._cluster.metadata.get_host_by_host_id(host_id) if host and host.endpoint != endpoint: log.debug("[control connection] Updating host ip from %s to %s for (%s)", host.endpoint, endpoint, host_id) - old_endpoint = host.endpoint - host.endpoint = endpoint - self._cluster.metadata.update_host(host, old_endpoint) reconnector = host.get_and_set_reconnection_handler(None) if reconnector: reconnector.cancel() self._cluster.on_down(host, is_host_addition=False, expect_host_to_be_down=True) + old_endpoint = host.endpoint + host.endpoint = endpoint + self._cluster.metadata.update_host(host, old_endpoint) + self._cluster.on_up(host) + if host is None: log.debug("[control connection] Found new host to connect to: %s", endpoint) host, _ = self._cluster.add_host(endpoint, datacenter=datacenter, rack=rack, signal=True, refresh_nodes=False, host_id=host_id) From 0688d6f304d198541cfa85075cfff9b5d8248fc7 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 29 Jan 2026 08:29:37 -0400 Subject: [PATCH 181/298] add uv files to .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 28cf1ba218..1b60f54642 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,11 @@ tests/unit/cython/bytesio_testhelper.c #iPython *.ipynb +uv.lock +.venv/ + + + # Files from upstream that we don't need Jenkinsfile Jenkinsfile.bak From bf0a4b3e785a3ff2234e48d0d97c78b2900b7882 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sat, 10 Jan 2026 00:05:31 +0200 Subject: [PATCH 182/298] Optimize write path in protocol.py to reduce copies Refactored `_ProtocolHandler.encode_message` to reduce memory copies and allocations. - Implemented 'Reserve and Seek' strategy for the write path. - In uncompressed scenarios (including Protocol V5+), we now write directly to the final buffer instead of an intermediate one, avoiding `bytes` creation and buffer copying. - Reserved space for the frame header, wrote the body, and then back-filled the header with the correct length. - Unified buffer initialization and header writing logic for cleaner code. - Optimized conditional checks for compression support. Signed-off-by: Yaniv Kaul --- cassandra/protocol.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/cassandra/protocol.py b/cassandra/protocol.py index e574965de8..f37633a756 100644 --- a/cassandra/protocol.py +++ b/cassandra/protocol.py @@ -1085,20 +1085,10 @@ def encode_message(cls, msg, stream_id, protocol_version, compressor, allow_beta :param compressor: optional compression function to be used on the body """ flags = 0 - body = io.BytesIO() if msg.custom_payload: if protocol_version < 4: raise UnsupportedOperation("Custom key/value payloads can only be used with protocol version 4 or higher") flags |= CUSTOM_PAYLOAD_FLAG - write_bytesmap(body, msg.custom_payload) - msg.send_body(body, protocol_version) - body = body.getvalue() - - # With checksumming, the compression is done at the segment frame encoding - if (not ProtocolVersion.has_checksumming_support(protocol_version) - and compressor and len(body) > 0): - body = compressor(body) - flags |= COMPRESSED_FLAG if msg.tracing: flags |= TRACING_FLAG @@ -1107,9 +1097,31 @@ def encode_message(cls, msg, stream_id, protocol_version, compressor, allow_beta flags |= USE_BETA_FLAG buff = io.BytesIO() - cls._write_header(buff, protocol_version, flags, stream_id, msg.opcode, len(body)) - buff.write(body) + buff.seek(9) + + # With checksumming, the compression is done at the segment frame encoding + if (compressor and not ProtocolVersion.has_checksumming_support(protocol_version)): + body = io.BytesIO() + if msg.custom_payload: + write_bytesmap(body, msg.custom_payload) + msg.send_body(body, protocol_version) + body = body.getvalue() + + if len(body) > 0: + body = compressor(body) + flags |= COMPRESSED_FLAG + + buff.write(body) + length = len(body) + else: + if msg.custom_payload: + write_bytesmap(buff, msg.custom_payload) + msg.send_body(buff, protocol_version) + + length = buff.tell() - 9 + buff.seek(0) + cls._write_header(buff, protocol_version, flags, stream_id, msg.opcode, length) return buff.getvalue() @staticmethod From 8f40fea1c640c6894d0265348147c7958ab3b098 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Thu, 29 Jan 2026 20:47:29 +0100 Subject: [PATCH 183/298] Allow xfail_scylla_version_lt to reference scylla-enterprise repo --- tests/integration/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index edac685d12..b4eab35875 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -690,8 +690,8 @@ def xfail_scylla_version_lt(reason, oss_scylla_version, ent_scylla_version, *arg :param oss_scylla_version: str, oss version from which test supposed to succeed :param ent_scylla_version: str, enterprise version from which test supposed to succeed """ - if not reason.startswith("scylladb/scylladb#"): - raise ValueError('reason should start with scylladb/scylladb# to reference issue in scylla repo') + if not (reason.startswith("scylladb/scylladb#") or reason.startswith("scylladb/scylla-enterprise#")): + raise ValueError('reason should start with scylladb/scylladb# or scylladb/scylla-enterprise# to reference issue in scylla repo') if not isinstance(ent_scylla_version, str): raise ValueError('ent_scylla_version should be a str') From b9a4a4e6f652e276e93a2a1c12633d42eb043078 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Thu, 29 Jan 2026 20:37:02 +0100 Subject: [PATCH 184/298] Only xfail ApplicationInfoTest for versions < 2026.1 --- tests/integration/standard/test_application_info.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/integration/standard/test_application_info.py b/tests/integration/standard/test_application_info.py index e1e0cb2ee4..384eb0a3e1 100644 --- a/tests/integration/standard/test_application_info.py +++ b/tests/integration/standard/test_application_info.py @@ -15,7 +15,7 @@ import unittest from cassandra.application_info import ApplicationInfo -from tests.integration import TestCluster, use_single_node, remove_cluster, xfail_scylla +from tests.integration import TestCluster, use_single_node, remove_cluster, xfail_scylla_version_lt def setup_module(): @@ -26,7 +26,8 @@ def teardown_module(): remove_cluster() -@xfail_scylla("#scylladb/scylla-enterprise#5467 - not released yet") +@xfail_scylla_version_lt(reason='scylladb/scylla-enterprise#5467 - system.client_options is not yet supported', + oss_scylla_version="7.0", ent_scylla_version="2026.1.0") class ApplicationInfoTest(unittest.TestCase): attribute_to_startup_key = { 'application_name': 'APPLICATION_NAME', @@ -74,7 +75,7 @@ def test_create_session_and_check_system_views_clients(self): )) found = False - for row in cluster.connect().execute("select client_options from system_views.clients"): + for row in cluster.connect().execute("select client_options from system.clients"): if not row[0]: continue for attribute_key, startup_key in self.attribute_to_startup_key.items(): From 5ae7e2904c95cabcf0e41fc1538294040134ef55 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Thu, 29 Jan 2026 21:00:16 +0100 Subject: [PATCH 185/298] test_application_info: use system_views if system table not available --- tests/integration/standard/test_application_info.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/integration/standard/test_application_info.py b/tests/integration/standard/test_application_info.py index 384eb0a3e1..719f37843a 100644 --- a/tests/integration/standard/test_application_info.py +++ b/tests/integration/standard/test_application_info.py @@ -75,7 +75,14 @@ def test_create_session_and_check_system_views_clients(self): )) found = False - for row in cluster.connect().execute("select client_options from system.clients"): + session = cluster.connect() + + try: + rows = list(session.execute("SELECT client_options FROM system.clients")) + except Exception: + rows = list(session.execute("SELECT client_options FROM system_views.clients")) + + for row in rows: if not row[0]: continue for attribute_key, startup_key in self.attribute_to_startup_key.items(): From 5065299e8464c66b661b1c26c493eea89cb9c0ea Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 29 Jan 2026 23:48:07 -0400 Subject: [PATCH 186/298] Add personal todo list file --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 1b60f54642..881012f340 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,9 @@ docs/geo_types.rst docs/graph.rst docs/graph_fluent.rst +# Personal list of items to do +TODO.md + # Codex - AI assistant metadata .codex/ .codex-cache/ From 20469c02538204a70c25d54fdb5566047e144429 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 29 Jan 2026 22:44:49 -0400 Subject: [PATCH 187/298] Upgrade dependency constraints in pyproject.toml - gremlinpython: ==3.7.4 -> >=3.7.4,<4 (allows 3.8.0) - setuptools: >=42 -> >=70 in build-system.requires - cython: unversioned -> >=3.2 in dev dependencies - packaging: unversioned -> >=25.0 in dev dependencies - cryptography: >=35.0 -> >=42.0 --- pyproject.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f0dffa23c9..730a69c350 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,8 +31,8 @@ requires-python = ">=3.9" "Issues" = "https://github.com/scylladb/python-driver/issues" [project.optional-dependencies] -graph = ['gremlinpython==3.7.4'] -cle = ['cryptography>=35.0'] +graph = ['gremlinpython>=3.7.4,<4'] +cle = ['cryptography>=42.0'] compress-lz4 = ['lz4'] compress-snappy = ['python-snappy'] @@ -46,8 +46,8 @@ dev = [ "twisted[tls]", "gevent", "eventlet>=0.33.3", - "cython", - "packaging", + "cython>=3.2", + "packaging>=25.0", "futurist", "asynctest", "pyyaml", @@ -75,7 +75,7 @@ version = { attr = "cassandra.__version__" } # any module attri readme = { file = "README.rst", content-type = "text/x-rst" } [build-system] -requires = ["setuptools>=42", "Cython"] +requires = ["setuptools>=70", "Cython"] build-backend = "setuptools.build_meta" From 1e5fd56be3d006828bdda5142718638ec4ee12e7 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 29 Jan 2026 14:15:16 -0400 Subject: [PATCH 188/298] Include original error in ConnectionShutdown messages When a connection is closed or becomes defunct, include the last_error in the ConnectionShutdown exception message. This helps investigate issues where the original cause (e.g., "Bad file descriptor") was previously lost when subsequent operations tried to use the connection. Refs #614 --- cassandra/connection.py | 15 +++++-- cassandra/io/asyncioreactor.py | 6 ++- cassandra/io/asyncorereactor.py | 8 ++-- cassandra/io/eventletreactor.py | 6 ++- cassandra/io/geventreactor.py | 6 ++- cassandra/io/libevreactor.py | 6 ++- cassandra/io/twistedreactor.py | 6 ++- tests/unit/test_connection.py | 70 ++++++++++++++++++++++++++++++++- 8 files changed, 106 insertions(+), 17 deletions(-) diff --git a/cassandra/connection.py b/cassandra/connection.py index 9ac02c9776..87f860f32b 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -1087,9 +1087,15 @@ def handle_pushed(self, response): def send_msg(self, msg, request_id, cb, encoder=ProtocolHandler.encode_message, decoder=ProtocolHandler.decode_message, result_metadata=None): if self.is_defunct: - raise ConnectionShutdown("Connection to %s is defunct" % self.endpoint) + msg = "Connection to %s is defunct" % self.endpoint + if self.last_error: + msg += ": %s" % (self.last_error,) + raise ConnectionShutdown(msg) elif self.is_closed: - raise ConnectionShutdown("Connection to %s is closed" % self.endpoint) + msg = "Connection to %s is closed" % self.endpoint + if self.last_error: + msg += ": %s" % (self.last_error,) + raise ConnectionShutdown(msg) elif not self._socket_writable: raise ConnectionBusy("Connection %s is overloaded" % self.endpoint) @@ -1120,7 +1126,10 @@ def wait_for_responses(self, *msgs, **kwargs): failed, the corresponding Exception will be raised. """ if self.is_closed or self.is_defunct: - raise ConnectionShutdown("Connection %s is already closed" % (self, )) + msg = "Connection %s is already closed" % (self,) + if self.last_error: + msg += ": %s" % (self.last_error,) + raise ConnectionShutdown(msg) timeout = kwargs.get('timeout') fail_on_error = kwargs.get('fail_on_error', True) waiter = ResponseWaiter(self, len(msgs), fail_on_error) diff --git a/cassandra/io/asyncioreactor.py b/cassandra/io/asyncioreactor.py index 41b744602d..66e1d7295c 100644 --- a/cassandra/io/asyncioreactor.py +++ b/cassandra/io/asyncioreactor.py @@ -160,8 +160,10 @@ async def _close(self): log.debug("Closed socket to %s" % (self.endpoint,)) if not self.is_defunct: - self.error_all_requests( - ConnectionShutdown("Connection to %s was closed" % self.endpoint)) + msg = "Connection to %s was closed" % self.endpoint + if self.last_error: + msg += ": %s" % (self.last_error,) + self.error_all_requests(ConnectionShutdown(msg)) # don't leave in-progress operations hanging self.connected_event.set() diff --git a/cassandra/io/asyncorereactor.py b/cassandra/io/asyncorereactor.py index 2c75e7139d..02466ad0d2 100644 --- a/cassandra/io/asyncorereactor.py +++ b/cassandra/io/asyncorereactor.py @@ -385,12 +385,14 @@ def close(self): log.debug("Closed socket to %s", self.endpoint) if not self.is_defunct: - self.error_all_requests( - ConnectionShutdown("Connection to %s was closed" % self.endpoint)) + msg = "Connection to %s was closed" % self.endpoint + if self.last_error: + msg += ": %s" % (self.last_error,) + self.error_all_requests(ConnectionShutdown(msg)) #This happens when the connection is shutdown while waiting for the ReadyMessage if not self.connected_event.is_set(): - self.last_error = ConnectionShutdown("Connection to %s was closed" % self.endpoint) + self.last_error = ConnectionShutdown(msg) # don't leave in-progress operations hanging self.connected_event.set() diff --git a/cassandra/io/eventletreactor.py b/cassandra/io/eventletreactor.py index 42874036d5..234a4a574c 100644 --- a/cassandra/io/eventletreactor.py +++ b/cassandra/io/eventletreactor.py @@ -145,8 +145,10 @@ def close(self): log.debug("Closed socket to %s" % (self.endpoint,)) if not self.is_defunct: - self.error_all_requests( - ConnectionShutdown("Connection to %s was closed" % self.endpoint)) + msg = "Connection to %s was closed" % self.endpoint + if self.last_error: + msg += ": %s" % (self.last_error,) + self.error_all_requests(ConnectionShutdown(msg)) # don't leave in-progress operations hanging self.connected_event.set() diff --git a/cassandra/io/geventreactor.py b/cassandra/io/geventreactor.py index 4f1f158aa7..7516fdd6df 100644 --- a/cassandra/io/geventreactor.py +++ b/cassandra/io/geventreactor.py @@ -95,8 +95,10 @@ def close(self): log.debug("Closed socket to %s" % (self.endpoint,)) if not self.is_defunct: - self.error_all_requests( - ConnectionShutdown("Connection to %s was closed" % self.endpoint)) + msg = "Connection to %s was closed" % self.endpoint + if self.last_error: + msg += ": %s" % (self.last_error,) + self.error_all_requests(ConnectionShutdown(msg)) # don't leave in-progress operations hanging self.connected_event.set() diff --git a/cassandra/io/libevreactor.py b/cassandra/io/libevreactor.py index 58c876fdcc..d7b365e451 100644 --- a/cassandra/io/libevreactor.py +++ b/cassandra/io/libevreactor.py @@ -297,8 +297,10 @@ def close(self): # don't leave in-progress operations hanging if not self.is_defunct: - self.error_all_requests( - ConnectionShutdown("Connection to %s was closed" % self.endpoint)) + msg = "Connection to %s was closed" % self.endpoint + if self.last_error: + msg += ": %s" % (self.last_error,) + self.error_all_requests(ConnectionShutdown(msg)) self.connected_event.set() def handle_write(self, watcher, revents, errno=None): diff --git a/cassandra/io/twistedreactor.py b/cassandra/io/twistedreactor.py index e4605a7446..446200bf63 100644 --- a/cassandra/io/twistedreactor.py +++ b/cassandra/io/twistedreactor.py @@ -283,8 +283,10 @@ def close(self): log.debug("Closed socket to %s", self.endpoint) if not self.is_defunct: - self.error_all_requests( - ConnectionShutdown("Connection to %s was closed" % self.endpoint)) + msg = "Connection to %s was closed" % self.endpoint + if self.last_error: + msg += ": %s" % (self.last_error,) + self.error_all_requests(ConnectionShutdown(msg)) # don't leave in-progress operations hanging self.connected_event.set() diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 97dbe39957..6ac63ff761 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -22,7 +22,7 @@ from cassandra.cluster import Cluster from cassandra.connection import (Connection, HEADER_DIRECTION_TO_CLIENT, ProtocolError, locally_supported_compressions, ConnectionHeartbeat, _Frame, Timer, TimerManager, - ConnectionException, DefaultEndPoint, ShardAwarePortGenerator) + ConnectionException, ConnectionShutdown, DefaultEndPoint, ShardAwarePortGenerator) from cassandra.marshal import uint8_pack, uint32_pack, int32_pack from cassandra.protocol import (write_stringmultimap, write_int, write_string, SupportedMessage, ProtocolHandler) @@ -260,6 +260,74 @@ def test_set_connection_class(self): cluster = Cluster(connection_class='test') assert 'test' == cluster.connection_class + def test_connection_shutdown_includes_last_error(self): + """ + Test that ConnectionShutdown exceptions include the last_error when available. + This helps debug issues like "Bad file descriptor" by showing the original cause. + See https://github.com/scylladb/python-driver/issues/614 + """ + c = self.make_connection() + c.lock = Lock() + c._requests = {} + + # Simulate the connection becoming defunct with a specific error + original_error = OSError(9, "Bad file descriptor") + c.is_defunct = True + c.last_error = original_error + + # send_msg should raise ConnectionShutdown that includes the last_error + with pytest.raises(ConnectionShutdown) as exc_info: + c.send_msg(Mock(), 1, Mock()) + + # Verify the error message includes the original error + error_message = str(exc_info.value) + assert "is defunct" in error_message + assert "Bad file descriptor" in error_message + + def test_connection_shutdown_closed_includes_last_error(self): + """ + Test that ConnectionShutdown exceptions for closed connections include last_error. + """ + c = self.make_connection() + c.lock = Lock() + c._requests = {} + + # Simulate the connection being closed with a specific error + original_error = OSError(9, "Bad file descriptor") + c.is_closed = True + c.last_error = original_error + + # send_msg should raise ConnectionShutdown that includes the last_error + with pytest.raises(ConnectionShutdown) as exc_info: + c.send_msg(Mock(), 1, Mock()) + + # Verify the error message includes the original error + error_message = str(exc_info.value) + assert "is closed" in error_message + assert "Bad file descriptor" in error_message + + def test_wait_for_responses_shutdown_includes_last_error(self): + """ + Test that wait_for_responses raises ConnectionShutdown with last_error. + """ + c = self.make_connection() + c.lock = Lock() + c._requests = {} + + # Simulate the connection being defunct with a specific error + original_error = OSError(9, "Bad file descriptor") + c.is_defunct = True + c.last_error = original_error + + # wait_for_responses should raise ConnectionShutdown that includes the last_error + with pytest.raises(ConnectionShutdown) as exc_info: + c.wait_for_responses(Mock()) + + # Verify the error message includes the original error + error_message = str(exc_info.value) + assert "already closed" in error_message + assert "Bad file descriptor" in error_message + @patch('cassandra.connection.ConnectionHeartbeat._raise_if_stopped') class ConnectionHeartbeatTest(unittest.TestCase): From bd70891bd5e3ae1f22d62a118d9359c46a243c6f Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 29 Jan 2026 16:06:40 -0400 Subject: [PATCH 189/298] Add missing dev and optional dependencies - Add numpy and objgraph to dev dependencies (used in tests) - Add auth-kerberos optional dependency for GSSAPI authentication --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 730a69c350..aa742ad02b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,10 @@ graph = ['gremlinpython>=3.7.4,<4'] cle = ['cryptography>=42.0'] compress-lz4 = ['lz4'] compress-snappy = ['python-snappy'] +auth-kerberos = [ + 'kerberos; platform_system != "Windows"', + 'winkerberos; platform_system == "Windows"', +] [dependency-groups] dev = [ @@ -51,6 +55,8 @@ dev = [ "futurist", "asynctest", "pyyaml", + "numpy", + "objgraph", "ccm @ git+https://git@github.com/scylladb/scylla-ccm.git@master", ] From 4331c5b76724de25ef1c3d41d47abe61fd46fd79 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 29 Jan 2026 17:30:20 -0400 Subject: [PATCH 190/298] Fix UnboundLocalError in test_numpy_results_paged Initialize `count` variable before the for loop to prevent UnboundLocalError when the loop doesn't execute. This follows the same pattern already used in verify_iterator_data(). --- tests/integration/standard/test_cython_protocol_handlers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index 9ec16cecc7..1dfb0ccdd9 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -110,6 +110,7 @@ def test_numpy_results_paged(self): results = session.execute("SELECT * FROM test_table") assert results.has_more_pages + count = 0 for count, page in enumerate(results, 1): assert isinstance(page, dict) for colname, arr in page.items(): From a35dc6c0337ff4923178cff2b0bb8d37742082ba Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 29 Jan 2026 21:24:14 -0400 Subject: [PATCH 191/298] Fix NumPy 2.0 compatibility in numpy_parser The `ndarray.newbyteorder()` method was removed in NumPy 2.0. Use `arr.view(arr.dtype.newbyteorder())` instead, which is compatible with both NumPy 1.x and 2.x. This fixes the failing numpy protocol handler integration tests that started running after numpy was added to dev dependencies. --- cassandra/numpy_parser.pyx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cassandra/numpy_parser.pyx b/cassandra/numpy_parser.pyx index 030c2c65c7..0ad34f66e2 100644 --- a/cassandra/numpy_parser.pyx +++ b/cassandra/numpy_parser.pyx @@ -181,5 +181,6 @@ def make_native_byteorder(arr): # accordingly (e.g. from '>i8' to ' Date: Mon, 26 Jan 2026 19:07:45 +0200 Subject: [PATCH 192/298] (fix)test_user_function_failure need to be SKIPPED (Scylla does not support UDFs written in Java) See title - added decorator so it'll be skipped. Signed-off-by: Yaniv Kaul --- tests/integration/long/test_failure_types.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/long/test_failure_types.py b/tests/integration/long/test_failure_types.py index 33d2c99130..beb10f02c0 100644 --- a/tests/integration/long/test_failure_types.py +++ b/tests/integration/long/test_failure_types.py @@ -32,6 +32,8 @@ get_node, start_cluster_wait_for_up, requiresmallclockgranularity, local, CASSANDRA_VERSION, TestCluster) +from tests.integration import requires_java_udf + import unittest import pytest @@ -269,6 +271,7 @@ def test_tombstone_overflow_read_failure(self): DROP TABLE test3rf.test2; """, consistency_level=ConsistencyLevel.ALL, expected_exception=None) + @requires_java_udf def test_user_function_failure(self): """ Test to validate that exceptions in user defined function are correctly surfaced by the driver to us. From 1dcec8754f5ae0d19b2598b11f7f4d7291e71999 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Fri, 30 Jan 2026 13:20:21 +0100 Subject: [PATCH 193/298] Specify protocol version for cluster to use Without this on cluster lvl we assumed protocol version is 4, even if it was 3. Test `test_imprecise_bind_values_dicts` rely on protocol specific behavior and was failing for v3. --- tests/integration/standard/test_prepared_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/standard/test_prepared_statements.py b/tests/integration/standard/test_prepared_statements.py index e6f86d835b..3f63b881ef 100644 --- a/tests/integration/standard/test_prepared_statements.py +++ b/tests/integration/standard/test_prepared_statements.py @@ -44,7 +44,7 @@ def setUpClass(cls): cls.cass_version = get_server_versions() def setUp(self): - self.cluster = TestCluster(metrics_enabled=True, allow_beta_protocol_version=True) + self.cluster = TestCluster(metrics_enabled=True, allow_beta_protocol_version=True, protocol_version=PROTOCOL_VERSION) self.session = self.cluster.connect() def tearDown(self): From 237d2694da14cdd9be235106b2a67a8f1ddabdbc Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 29 Jan 2026 16:44:57 -0400 Subject: [PATCH 194/298] Remove Python 2 compatibility code The driver requires Python 3.10+ so these compatibility shims are no longer needed: - Remove try/except for Queue vs queue (use queue directly) - Remove try/except for __builtin__ vs builtins (use builtins directly) - Remove try/except for thread vs _thread (use _thread directly) - Remove try/except for futures vs concurrent.futures (use concurrent.futures directly) - Remove try/except for unittest2 vs unittest (use unittest directly) - Replace deprecated imp.reload with importlib.reload --- tests/integration/long/test_large_data.py | 5 +---- tests/integration/standard/test_shard_aware.py | 12 +++--------- tests/integration/standard/test_use_keyspace.py | 5 +---- tests/unit/io/eventlet_utils.py | 14 ++++---------- tests/unit/test_protocol_features.py | 5 +---- tests/unit/test_shard_aware.py | 5 +---- 6 files changed, 11 insertions(+), 35 deletions(-) diff --git a/tests/integration/long/test_large_data.py b/tests/integration/long/test_large_data.py index a79a6da4f5..0a1b368bf0 100644 --- a/tests/integration/long/test_large_data.py +++ b/tests/integration/long/test_large_data.py @@ -12,10 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -try: - from Queue import Queue, Empty -except ImportError: - from queue import Queue, Empty # noqa +from queue import Queue, Empty from struct import pack import logging, sys, traceback, time diff --git a/tests/integration/standard/test_shard_aware.py b/tests/integration/standard/test_shard_aware.py index 215c69c3b4..48d1aa3609 100644 --- a/tests/integration/standard/test_shard_aware.py +++ b/tests/integration/standard/test_shard_aware.py @@ -17,15 +17,9 @@ from subprocess import run import logging -try: - from concurrent.futures import ThreadPoolExecutor, as_completed -except ImportError: - from futures import ThreadPoolExecutor, as_completed # noqa - -try: - import unittest2 as unittest -except ImportError: - import unittest # noqa +from concurrent.futures import ThreadPoolExecutor, as_completed + +import unittest import pytest from cassandra.cluster import Cluster diff --git a/tests/integration/standard/test_use_keyspace.py b/tests/integration/standard/test_use_keyspace.py index 72ddbf9ef4..25e954b956 100644 --- a/tests/integration/standard/test_use_keyspace.py +++ b/tests/integration/standard/test_use_keyspace.py @@ -2,10 +2,7 @@ import time import logging -try: - import unittest2 as unittest -except ImportError: - import unittest # noqa +import unittest from unittest.mock import patch diff --git a/tests/unit/io/eventlet_utils.py b/tests/unit/io/eventlet_utils.py index 785856be20..2a5a8c78d0 100644 --- a/tests/unit/io/eventlet_utils.py +++ b/tests/unit/io/eventlet_utils.py @@ -16,21 +16,15 @@ import os import select import socket -try: - import thread - import Queue - import __builtin__ - #For python3 compatibility -except ImportError: - import _thread as thread - import queue as Queue - import builtins as __builtin__ +import _thread as thread +import queue as Queue +import builtins as __builtin__ import threading import ssl import time import eventlet -from imp import reload +from importlib import reload def eventlet_un_patch_all(): """ diff --git a/tests/unit/test_protocol_features.py b/tests/unit/test_protocol_features.py index 89b568ea68..895c384f7e 100644 --- a/tests/unit/test_protocol_features.py +++ b/tests/unit/test_protocol_features.py @@ -1,7 +1,4 @@ -try: - import unittest2 as unittest -except ImportError: - import unittest # noqa +import unittest import logging diff --git a/tests/unit/test_shard_aware.py b/tests/unit/test_shard_aware.py index 0d13a243ab..e7d26ae207 100644 --- a/tests/unit/test_shard_aware.py +++ b/tests/unit/test_shard_aware.py @@ -12,10 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -try: - import unittest2 as unittest -except ImportError: - import unittest # noqa +import unittest import logging from unittest.mock import MagicMock From a32b145c194ea7459dc95925bf7ec6e4225af7f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:48:54 +0000 Subject: [PATCH 195/298] build(deps): bump urllib3 from 2.6.1 to 2.6.3 in /docs Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.1 to 2.6.3. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.1...2.6.3) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- docs/uv.lock | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/uv.lock b/docs/uv.lock index 23468c7170..ca041f693b 100644 --- a/docs/uv.lock +++ b/docs/uv.lock @@ -6,6 +6,7 @@ requires-python = "==3.13.*" name = "aenum" version = "3.1.16" source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/7a/61ed58e8be9e30c3fe518899cc78c284896d246d51381bab59b5db11e1f3/aenum-3.1.16.tar.gz", hash = "sha256:bfaf9589bdb418ee3a986d85750c7318d9d2839c1b1a1d6fe8fc53ec201cf140", size = 137693, upload-time = "2026-01-12T22:34:38.819Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e3/52/6ad8f63ec8da1bf40f96996d25d5b650fdd38f5975f8c813732c47388f18/aenum-3.1.16-py3-none-any.whl", hash = "sha256:9035092855a98e41b66e3d0998bd7b96280e85ceb3a04cc035636138a1943eaf", size = 165627, upload-time = "2025-04-25T03:17:58.89Z" }, ] @@ -1102,11 +1103,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.1" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/1d/0f3a93cca1ac5e8287842ed4eebbd0f7a991315089b1a0b01c7788aa7b63/urllib3-2.6.1.tar.gz", hash = "sha256:5379eb6e1aba4088bae84f8242960017ec8d8e3decf30480b3a1abdaa9671a3f", size = 432678, upload-time = "2025-12-08T15:25:26.773Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/56/190ceb8cb10511b730b564fb1e0293fa468363dbad26145c34928a60cb0c/urllib3-2.6.1-py3-none-any.whl", hash = "sha256:e67d06fe947c36a7ca39f4994b08d73922d40e6cca949907be05efa6fd75110b", size = 131138, upload-time = "2025-12-08T15:25:25.51Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] From 3cfa6800b4748ecb703ef8457b90aab7efae113b Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Wed, 28 Jan 2026 12:39:48 +0100 Subject: [PATCH 196/298] Fix test_client_routes_change_event --- .../standard/test_control_connection.py | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/tests/integration/standard/test_control_connection.py b/tests/integration/standard/test_control_connection.py index cb7820f0a6..8dc465eb48 100644 --- a/tests/integration/standard/test_control_connection.py +++ b/tests/integration/standard/test_control_connection.py @@ -137,12 +137,12 @@ def test_control_connection_port_discovery(self): assert 7000 == host.broadcast_port @xfail_scylla_version_lt(reason='scylladb/scylladb#26992 - system.client_routes is not yet supported', - oss_scylla_version="7.0", ent_scylla_version="2025.4.0") + oss_scylla_version="7.0", ent_scylla_version="2026.1.0") def test_client_routes_change_event(self): cluster = TestCluster() # Establish control connection - cluster.connect() + self.session = self.cluster.connect() flag = Event() @@ -161,7 +161,7 @@ def on_event(event): finally: flag.set() - cluster.control_connection._connection.register_watchers({"CLIENT_ROUTES_CHANGE": on_event}) + self.session.cluster.control_connection._connection.register_watchers({"CLIENT_ROUTES_CHANGE": on_event}) try: payload = [ @@ -170,22 +170,12 @@ def on_event(event): "host_id": host_ids[0], "address": "localhost", "port": 9042, - "tls_port": 0, - "alternator_port": 0, - "alternator_https_port": 0, - "rack": "string", - "datacenter": "string" }, { "connection_id": connection_ids[1], "host_id": host_ids[1], "address": "localhost", "port": 9042, - "tls_port": 0, - "alternator_port": 0, - "alternator_https_port": 0, - "rack": "string", - "datacenter": "string" } ] response = requests.post( @@ -197,7 +187,7 @@ def on_event(event): }) assert response.status_code == 200 assert flag.wait(20), "Schema change event was not received after registering watchers" - assert got_connection_ids == connection_ids - assert got_host_ids == host_ids + assert set(got_connection_ids) == set(connection_ids) + assert set(got_host_ids) == set(host_ids) finally: cluster.shutdown() From 6dc946431b152b6389fe7da1f3ce7fbf3a8544a6 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 30 Jan 2026 20:55:39 -0400 Subject: [PATCH 197/298] Document ScyllaDB 1MB page size limit and add wide table tests Add documentation about ScyllaDB's built-in 1MB page size limit which can cause fewer rows per page than requested when working with wide tables. This is particularly relevant for NumpyProtocolHandler users. Also add integration tests for wide tables (200+ columns) to verify correct behavior and the fetch_size=None workaround. Fixes #65 --- docs/scylla-specific.rst | 34 ++++++ .../standard/test_cython_protocol_handlers.py | 114 ++++++++++++++++++ 2 files changed, 148 insertions(+) diff --git a/docs/scylla-specific.rst b/docs/scylla-specific.rst index e9caaa8793..e9fe695f8f 100644 --- a/docs/scylla-specific.rst +++ b/docs/scylla-specific.rst @@ -111,6 +111,40 @@ New Error Types raise +Paging Differences +------------------ + +ScyllaDB has a built-in 1MB page size limit that Cassandra does not have. This means that even if you set a high ``fetch_size`` (e.g., 10000 rows), ScyllaDB may return fewer rows per page if the total response size exceeds 1MB. + +This behavior is particularly noticeable when: + +* Working with wide tables (many columns) +* Using ``NumpyProtocolHandler`` where you want large arrays per page +* Columns contain large values (blobs, long strings, etc.) + +For example, with a table containing 1000 columns, you might receive only 30-50 rows per page even with ``fetch_size=10000``. + +**Workaround:** If you need to receive more rows per page (up to ScyllaDB's 1MB limit), set ``default_fetch_size`` to ``None``: + +.. code:: python + + from cassandra.cluster import Cluster + from cassandra.protocol import NumpyProtocolHandler + from cassandra.query import tuple_factory + + cluster = Cluster() + session = cluster.connect(keyspace="mykeyspace") + session.row_factory = tuple_factory + session.client_protocol_handler = NumpyProtocolHandler + session.default_fetch_size = None # Let ScyllaDB control page sizes + + results = session.execute("SELECT * FROM wide_table") + +With ``default_fetch_size = None``, the driver won't request a specific page size, allowing ScyllaDB to fill pages up to its 1MB limit. This results in larger arrays when using ``NumpyProtocolHandler``. + +For more details on paging, see :ref:`query-paging`. + + Tablet Awareness ---------------- diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index 1dfb0ccdd9..f44d613c64 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -207,6 +207,120 @@ def verify_iterator_data(results): return count +class NumpyWideTableTest(unittest.TestCase): + """ + Test NumpyProtocolHandler with wide tables (many columns). + + ScyllaDB has a built-in 1MB page size limit that can cause fewer rows + per page than requested when working with wide tables. + + See: https://github.com/scylladb/python-driver/issues/65 + """ + + N_COLUMNS = 200 # Number of int columns (plus primary key columns) + N_ROWS = 100 + + @classmethod + def setUpClass(cls): + cls.cluster = TestCluster() + cls.session = cls.cluster.connect() + cls.session.execute("CREATE KEYSPACE IF NOT EXISTS test_wide_table WITH replication = " + "{ 'class' : 'SimpleStrategy', 'replication_factor': '1'}") + cls.session.set_keyspace("test_wide_table") + + # Create a wide table with many int columns + columns = ["pk int", "ck int"] + columns += ["col{0} int".format(i) for i in range(cls.N_COLUMNS)] + cls.session.execute( + "CREATE TABLE wide_table ({0}, PRIMARY KEY (pk, ck))".format(", ".join(columns)), + timeout=120 + ) + + # Insert test data + col_names = ["pk", "ck"] + ["col{0}".format(i) for i in range(cls.N_COLUMNS)] + placeholders = ", ".join(["%s"] * len(col_names)) + insert_cql = "INSERT INTO wide_table ({0}) VALUES ({1})".format( + ", ".join(col_names), placeholders + ) + + for row_idx in range(cls.N_ROWS): + values = [0, row_idx] + [row_idx * 1000 + i for i in range(cls.N_COLUMNS)] + cls.session.execute(insert_cql, values, timeout=120) + + @classmethod + def tearDownClass(cls): + drop_keyspace_shutdown_cluster("test_wide_table", cls.session, cls.cluster) + + @notprotocolv1 + @numpytest + def test_numpy_wide_table_paging(self): + """ + Test that NumpyProtocolHandler works with wide tables. + + With ScyllaDB's 1MB page size limit, wide tables may return fewer + rows per page than the fetch_size requests. This test verifies + that all data is still returned correctly across multiple pages. + """ + cluster = TestCluster( + execution_profiles={EXEC_PROFILE_DEFAULT: ExecutionProfile(row_factory=tuple_factory)} + ) + session = cluster.connect(keyspace="test_wide_table") + session.client_protocol_handler = NumpyProtocolHandler + session.default_fetch_size = 1000 # Request many rows per page + + results = session.execute("SELECT * FROM wide_table") + + # Count total rows across all pages + total_rows = 0 + page_count = 0 + for page in results: + page_count += 1 + # Get row count from first column array + arr = page.get('pk') + if arr is not None: + total_rows += len(arr) + + # Verify all rows were returned + self.assertEqual(total_rows, self.N_ROWS, + "Expected {0} rows total, got {1} across {2} pages".format( + self.N_ROWS, total_rows, page_count)) + + cluster.shutdown() + + @notprotocolv1 + @numpytest + def test_numpy_wide_table_no_fetch_size(self): + """ + Test that setting fetch_size=None allows ScyllaDB to control page sizes. + + This is the recommended workaround for getting larger pages with wide tables. + """ + cluster = TestCluster( + execution_profiles={EXEC_PROFILE_DEFAULT: ExecutionProfile(row_factory=tuple_factory)} + ) + session = cluster.connect(keyspace="test_wide_table") + session.client_protocol_handler = NumpyProtocolHandler + session.default_fetch_size = None # Let server control page sizes + + results = session.execute("SELECT * FROM wide_table") + + # Count total rows across all pages + total_rows = 0 + page_count = 0 + for page in results: + page_count += 1 + arr = page.get('pk') + if arr is not None: + total_rows += len(arr) + + # Verify all rows were returned + self.assertEqual(total_rows, self.N_ROWS, + "Expected {0} rows total, got {1} across {2} pages".format( + self.N_ROWS, total_rows, page_count)) + + cluster.shutdown() + + class NumpyNullTest(BasicSharedKeyspaceUnitTestCase): @classmethod From 85e6d1789bbe43114181f1745ded94b7f1c97198 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 30 Jan 2026 20:37:13 -0400 Subject: [PATCH 198/298] Remove DSE-specific content from documentation - Replace DataStax DSE documentation links with Scylla documentation - Remove entire DSE Authentication section (DSE Unified Auth, Proxy Login/Execute) - Update requirements.txt link to point to scylladb/python-driver pyproject.toml - Replace outdated DataStax blog post reference with link to security docs - Change dse-truststore.jks to generic truststore.jks - Update Python docs link from 2.x to 3.x - Fix typo: intructions -> instructions --- docs/installation.rst | 5 +- docs/security.rst | 106 +++--------------------------------------- 2 files changed, 8 insertions(+), 103 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index ee227618ef..8e4e54e036 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -113,7 +113,7 @@ Manual Installation You can always install the driver directly from a source checkout or tarball. When installing manually, ensure the python dependencies are already installed. You can find the list of dependencies in -`requirements.txt `_. +`pyproject.toml `_. Once the dependencies are installed, simply run:: @@ -228,5 +228,4 @@ the libev event loop by doing the following: (*Optional*) Configuring SSL ----------------------------- -Andrew Mussey has published a thorough guide on -`Using SSL with the DataStax Python driver `_. +See the :ref:`security` section for details on configuring SSL. diff --git a/docs/security.rst b/docs/security.rst index 57e2be71da..5c8645e685 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -60,14 +60,10 @@ as described in the following examples or implement your own :class:`~.connectio :class:`~.connection.EndPointFactory`. -The following examples assume you have generated your Cassandra certificate and -keystore files with these intructions: +The following examples assume you have generated your Scylla certificate and +keystore files with these instructions: -* `Setup SSL Cert `_ - -It might be also useful to learn about the different levels of identity verification to understand the examples: - -* `Using SSL in DSE drivers `_ +* `Scylla TLS/SSL Guide `_ SSL with Twisted or Eventlet ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -170,7 +166,7 @@ The cassandra configuration:: keystore: /path/to/127.0.0.1.keystore keystore_password: myStorePass require_client_auth: true - truststore: /path/to/dse-truststore.jks + truststore: /path/to/truststore.jks truststore_password: myStorePass The Python ``ssl`` APIs require the certificate in PEM format. First, create a certificate @@ -304,101 +300,11 @@ For example: cluster = Cluster(ssl_options=ssl_opts) This is only an example to show how to pass the ssl parameters. Consider reading -the `python ssl documentation `__ for -your configuration. For further reading, Andrew Mussey has published a thorough guide on -`Using SSL with the DataStax Python driver `_. +the `python ssl documentation `__ for +your configuration. SSL with Twisted ++++++++++++++++ In case the twisted event loop is used pyOpenSSL must be installed or an exception will be risen. Also to set the ``ssl_version`` and ``cert_reqs`` in ``ssl_opts`` the appropriate constants from pyOpenSSL are expected. - -DSE Authentication ------------------- -When authenticating against DSE, the Cassandra driver provides two auth providers that work both with legacy kerberos and Cassandra authenticators, -as well as the new DSE Unified Authentication. This allows client to configure this auth provider independently, -and in advance of any server upgrade. These auth providers are configured in the same way as any previous implementation:: - - from cassandra.auth import DSEGSSAPIAuthProvider - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"]) - cluster = Cluster(auth_provider=auth_provider) - session = cluster.connect() - -Implementations are :attr:`.DSEPlainTextAuthProvider`, :class:`.DSEGSSAPIAuthProvider` and :class:`.SaslAuthProvider`. - -DSE Unified Authentication -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -With DSE (>=5.1), unified Authentication allows you to: - -* Proxy Login: Authenticate using a fixed set of authentication credentials but allow authorization of resources based another user id. -* Proxy Execute: Authenticate using a fixed set of authentication credentials but execute requests based on another user id. - -Proxy Login -+++++++++++ - -Proxy login allows you to authenticate with a user but act as another one. You need to ensure the authenticated user has the permission to use the authorization of resources of the other user. ie. this example will allow the `server` user to authenticate as usual but use the authorization of `user1`: - -.. code-block:: text - - GRANT PROXY.LOGIN on role user1 to server - -then you can do the proxy authentication.... - -.. code-block:: python - - from cassandra.cluster import Cluster - from cassandra.auth import SaslAuthProvider - - sasl_kwargs = { - "service": 'dse', - "mechanism":"PLAIN", - "username": 'server', - 'password': 'server', - 'authorization_id': 'user1' - } - - auth_provider = SaslAuthProvider(**sasl_kwargs) - c = Cluster(auth_provider=auth_provider) - s = c.connect() - s.execute(...) # all requests will be executed as 'user1' - -If you are using kerberos, you can use directly :class:`.DSEGSSAPIAuthProvider` and pass the authorization_id, like this: - -.. code-block:: python - - from cassandra.cluster import Cluster - from cassandra.auth import DSEGSSAPIAuthProvider - - # Ensure the kerberos ticket of the server user is set with the kinit utility. - auth_provider = DSEGSSAPIAuthProvider(service='dse', qops=["auth"], principal="server@DATASTAX.COM", - authorization_id='user1@DATASTAX.COM') - c = Cluster(auth_provider=auth_provider) - s = c.connect() - s.execute(...) # all requests will be executed as 'user1' - - -Proxy Execute -+++++++++++++ - -Proxy execute allows you to execute requests as another user than the authenticated one. You need to ensure the authenticated user has the permission to use the authorization of resources of the specified user. ie. this example will allow the `server` user to execute requests as `user1`: - -.. code-block:: text - - GRANT PROXY.EXECUTE on role user1 to server - -then you can do a proxy execute... - -.. code-block:: python - - from cassandra.cluster import Cluster - from cassandra.auth import DSEPlainTextAuthProvider, - - auth_provider = DSEPlainTextAuthProvider('server', 'server') - - c = Cluster(auth_provider=auth_provider) - s = c.connect() - s.execute('select * from k.t;', execute_as='user1') # the request will be executed as 'user1' - -Please see the `official documentation `_ for more details on the feature and configuration process. From e4da11b5d8c5302ca1b829c5d28896216de46410 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 30 Jan 2026 20:33:45 -0400 Subject: [PATCH 199/298] Update Getting Started docs to reference Scylla instead of Cassandra --- docs/getting-started.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 76685c5fdf..cb837a7098 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -5,15 +5,15 @@ First, make sure you have the driver properly :doc:`installed `. Connecting to a Cluster ----------------------- -Before we can start executing any queries against a Cassandra cluster we need to setup +Before we can start executing any queries against a Scylla cluster we need to setup an instance of :class:`~.Cluster`. As the name suggests, you will typically have one -instance of :class:`~.Cluster` for each Cassandra cluster you want to interact +instance of :class:`~.Cluster` for each Scylla cluster you want to interact with. -First, make sure you have the Cassandra driver properly :doc:`installed `. +First, make sure you have the Scylla driver properly :doc:`installed `. -Connecting to Cassandra -+++++++++++++++++++++++ +Connecting to Scylla +++++++++++++++++++++ The simplest way to create a :class:`~.Cluster` is like this: .. code-block:: python @@ -22,7 +22,7 @@ The simplest way to create a :class:`~.Cluster` is like this: cluster = Cluster() -This will attempt to connection to a Cassandra instance on your +This will attempt to connect to a Scylla instance on your local machine (127.0.0.1). You can also specify a list of IP addresses for nodes in your cluster: @@ -121,7 +121,7 @@ way to execute a query is to use :meth:`~.Session.execute()`: for user_row in rows: print(user_row.name, user_row.age, user_row.email) -This will transparently pick a Cassandra node to execute the query against +This will transparently pick a Scylla node to execute the query against and handle any retries that are necessary if the operation fails. By default, each row in the result set will be a @@ -160,10 +160,10 @@ frequently run queries. Prepared Statements ------------------- -Prepared statements are queries that are parsed by Cassandra and then saved +Prepared statements are queries that are parsed by Scylla and then saved for later use. When the driver uses a prepared statement, it only needs to send the values of parameters to bind. This lowers network traffic -and CPU utilization within Cassandra because Cassandra does not have to +and CPU utilization within Scylla because Scylla does not have to re-parse the query each time. To prepare a query, use :meth:`.Session.prepare()`: From 5763a9d87bf3854d90d58e5360324fc59a9ae7b4 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 30 Jan 2026 11:14:11 -0400 Subject: [PATCH 200/298] Remove redundant skip decorators for broadcast_rpc_port tests The skip decorators referencing issue #121 were unnecessary because: - test_control_connection_port_discovery: Already has @greaterthanorequalcass40 which skips for Scylla (mapped to Cassandra 3.11.4) - test_single_interface: The entire class has @greaterthanorequalcass40 - test_host_addresses: Port assertions are conditional on CASSANDRA_VERSION >= 4.0 Since Scylla's CASSANDRA_VERSION defaults to 3.11.4, these version checks already prevent the port-related assertions from running on Scylla. Fixes: https://github.com/scylladb/python-driver/issues/121 --- tests/integration/standard/test_control_connection.py | 2 -- tests/integration/standard/test_metadata.py | 8 ++++---- tests/integration/standard/test_single_interface.py | 2 -- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/integration/standard/test_control_connection.py b/tests/integration/standard/test_control_connection.py index 8dc465eb48..2788a1d837 100644 --- a/tests/integration/standard/test_control_connection.py +++ b/tests/integration/standard/test_control_connection.py @@ -108,8 +108,6 @@ def test_get_control_connection_host(self): assert new_host1 != new_host2 - # TODO: enable after https://github.com/scylladb/python-driver/issues/121 is fixed - @unittest.skip('Fails on scylla due to the broadcast_rpc_port is None') @greaterthanorequalcass40 def test_control_connection_port_discovery(self): """ diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index d3ccafd76d..c30e369d83 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -57,8 +57,6 @@ def setup_module(): class HostMetaDataTests(BasicExistingKeyspaceUnitTestCase): - # TODO: enable after https://github.com/scylladb/python-driver/issues/121 is fixed - @unittest.skip('Fails on scylla due to the broadcast_rpc_port is None') @local def test_host_addresses(self): """ @@ -85,8 +83,10 @@ def test_host_addresses(self): local_host = con.host # The control connection node should have the listen address set. - listen_addrs = [host.listen_address for host in self.cluster.metadata.all_hosts()] - assert local_host in listen_addrs + # Note: Scylla does not populate listen_address in system.local + if SCYLLA_VERSION is None: + listen_addrs = [host.listen_address for host in self.cluster.metadata.all_hosts()] + assert local_host in listen_addrs # The control connection node should have the broadcast_rpc_address set. rpc_addrs = [host.broadcast_rpc_address for host in self.cluster.metadata.all_hosts()] diff --git a/tests/integration/standard/test_single_interface.py b/tests/integration/standard/test_single_interface.py index 3fd90b9708..5fd9ef45d3 100644 --- a/tests/integration/standard/test_single_interface.py +++ b/tests/integration/standard/test_single_interface.py @@ -44,8 +44,6 @@ def tearDown(self): if self.cluster is not None: self.cluster.shutdown() - # TODO: enable after https://github.com/scylladb/python-driver/issues/121 is fixed - @unittest.skip('Fails on scylla due to the broadcast_rpc_port is None') def test_single_interface(self): """ Test that we can connect to a multiple hosts bound to a single interface. From 21f0ff41650f178c5555e75a01910c8aced4253a Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sat, 31 Jan 2026 15:43:03 -0400 Subject: [PATCH 201/298] Migrate from pytz to zoneinfo pytz is deprecated. zoneinfo is in stdlib since Python 3.9 and this driver requires Python 3.10+. - Remove pytz from dev dependencies - Update docs to use zoneinfo instead of pytz - Update timezone test to use zoneinfo Fixes #664 --- docs/dates-and-times.rst | 19 +++++++++++-------- pyproject.toml | 1 - tests/integration/standard/test_types.py | 10 +++------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/docs/dates-and-times.rst b/docs/dates-and-times.rst index 7a89f77437..369ff15027 100644 --- a/docs/dates-and-times.rst +++ b/docs/dates-and-times.rst @@ -8,10 +8,10 @@ timestamps (Cassandra DateType) ------------------------------- Timestamps in Cassandra are timezone-naive timestamps encoded as millseconds since UNIX epoch. Clients working with -timestamps in this database usually find it easiest to reason about them if they are always assumed to be UTC. To quote the -pytz documentation, "The preferred way of dealing with times is to always work in UTC, converting to localtime only when -generating output to be read by humans." The driver adheres to this tenant, and assumes UTC is always in the database. The -driver attempts to make this correct on the way in, and assumes no timezone on the way out. +timestamps in this database usually find it easiest to reason about them if they are always assumed to be UTC. The +preferred way of dealing with times is to always work in UTC, converting to localtime only when generating output to +be read by humans. The driver adheres to this tenet, and assumes UTC is always in the database. The driver attempts +to make this correct on the way in, and assumes no timezone on the way out. Write Path ~~~~~~~~~~ @@ -45,12 +45,15 @@ saving time, and the defacto package for handling this is a third-party package and not make decisions for the integrator). The decision for how to handle timezones is left to the application. For the most part it is straightforward to apply -localization to the ``datetime``\s returned by queries. One prevalent method is to use pytz for localization:: +localization to the ``datetime``\s returned by queries. Use the standard library ``zoneinfo`` module (available since +Python 3.9) for localization:: - import pytz - user_tz = pytz.timezone('US/Central') + from datetime import timezone + from zoneinfo import ZoneInfo + + user_tz = ZoneInfo('US/Central') timestamp_naive = row.ts - timestamp_utc = pytz.utc.localize(timestamp_naive) + timestamp_utc = timestamp_naive.replace(tzinfo=timezone.utc) timestamp_presented = timestamp_utc.astimezone(user_tz) This is the most robust approach (likely refactored into a function). If it is deemed too cumbersome to apply for all call diff --git a/pyproject.toml b/pyproject.toml index aa742ad02b..1c195e1b77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,6 @@ auth-kerberos = [ dev = [ "pytest~=8.0", "PyYAML", - "pytz", "scales", "pure-sasl", "twisted[tls]", diff --git a/tests/integration/standard/test_types.py b/tests/integration/standard/test_types.py index 4ee9b70cde..ad69fbada9 100644 --- a/tests/integration/standard/test_types.py +++ b/tests/integration/standard/test_types.py @@ -405,14 +405,10 @@ def test_timezone_aware_datetimes_are_timestamps(self): Ensure timezone-aware datetimes are converted to timestamps correctly """ - try: - import pytz - except ImportError as exc: - raise unittest.SkipTest('pytz is not available: %r' % (exc,)) + from zoneinfo import ZoneInfo - dt = datetime(1997, 8, 29, 11, 14) - eastern_tz = pytz.timezone('US/Eastern') - eastern_tz.localize(dt) + eastern_tz = ZoneInfo('US/Eastern') + dt = datetime(1997, 8, 29, 11, 14, tzinfo=eastern_tz) s = self.session From 3050cf6661e6910590eb277c158346dbd4403b1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 03:24:49 +0000 Subject: [PATCH 202/298] build(deps): bump aiohttp from 3.13.2 to 3.13.3 in /docs --- updated-dependencies: - dependency-name: aiohttp dependency-version: 3.13.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- docs/uv.lock | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/uv.lock b/docs/uv.lock index ca041f693b..7c04fda41e 100644 --- a/docs/uv.lock +++ b/docs/uv.lock @@ -22,7 +22,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.13.2" +version = "3.13.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -33,25 +33,25 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/78/7e90ca79e5aa39f9694dcfd74f4720782d3c6828113bb1f3197f7e7c4a56/aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be", size = 732139, upload-time = "2025-10-28T20:57:02.455Z" }, - { url = "https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742", size = 490082, upload-time = "2025-10-28T20:57:04.784Z" }, - { url = "https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293", size = 489035, upload-time = "2025-10-28T20:57:06.894Z" }, - { url = "https://files.pythonhosted.org/packages/d2/04/db5279e38471b7ac801d7d36a57d1230feeee130bbe2a74f72731b23c2b1/aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811", size = 1720387, upload-time = "2025-10-28T20:57:08.685Z" }, - { url = "https://files.pythonhosted.org/packages/31/07/8ea4326bd7dae2bd59828f69d7fdc6e04523caa55e4a70f4a8725a7e4ed2/aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a", size = 1688314, upload-time = "2025-10-28T20:57:10.693Z" }, - { url = "https://files.pythonhosted.org/packages/48/ab/3d98007b5b87ffd519d065225438cc3b668b2f245572a8cb53da5dd2b1bc/aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4", size = 1756317, upload-time = "2025-10-28T20:57:12.563Z" }, - { url = "https://files.pythonhosted.org/packages/97/3d/801ca172b3d857fafb7b50c7c03f91b72b867a13abca982ed6b3081774ef/aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a", size = 1858539, upload-time = "2025-10-28T20:57:14.623Z" }, - { url = "https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e", size = 1739597, upload-time = "2025-10-28T20:57:16.399Z" }, - { url = "https://files.pythonhosted.org/packages/c4/52/7bd3c6693da58ba16e657eb904a5b6decfc48ecd06e9ac098591653b1566/aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb", size = 1555006, upload-time = "2025-10-28T20:57:18.288Z" }, - { url = "https://files.pythonhosted.org/packages/48/30/9586667acec5993b6f41d2ebcf96e97a1255a85f62f3c653110a5de4d346/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded", size = 1683220, upload-time = "2025-10-28T20:57:20.241Z" }, - { url = "https://files.pythonhosted.org/packages/71/01/3afe4c96854cfd7b30d78333852e8e851dceaec1c40fd00fec90c6402dd2/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b", size = 1712570, upload-time = "2025-10-28T20:57:22.253Z" }, - { url = "https://files.pythonhosted.org/packages/11/2c/22799d8e720f4697a9e66fd9c02479e40a49de3de2f0bbe7f9f78a987808/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8", size = 1733407, upload-time = "2025-10-28T20:57:24.37Z" }, - { url = "https://files.pythonhosted.org/packages/34/cb/90f15dd029f07cebbd91f8238a8b363978b530cd128488085b5703683594/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04", size = 1550093, upload-time = "2025-10-28T20:57:26.257Z" }, - { url = "https://files.pythonhosted.org/packages/69/46/12dce9be9d3303ecbf4d30ad45a7683dc63d90733c2d9fe512be6716cd40/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476", size = 1758084, upload-time = "2025-10-28T20:57:28.349Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c8/0932b558da0c302ffd639fc6362a313b98fdf235dc417bc2493da8394df7/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23", size = 1716987, upload-time = "2025-10-28T20:57:30.233Z" }, - { url = "https://files.pythonhosted.org/packages/5d/8b/f5bd1a75003daed099baec373aed678f2e9b34f2ad40d85baa1368556396/aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254", size = 425859, upload-time = "2025-10-28T20:57:32.105Z" }, - { url = "https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a", size = 452192, upload-time = "2025-10-28T20:57:34.166Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c", size = 734190, upload-time = "2026-01-03T17:30:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9", size = 491783, upload-time = "2026-01-03T17:30:47.466Z" }, + { url = "https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3", size = 490704, upload-time = "2026-01-03T17:30:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf", size = 1720652, upload-time = "2026-01-03T17:30:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6", size = 1692014, upload-time = "2026-01-03T17:30:52.729Z" }, + { url = "https://files.pythonhosted.org/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d", size = 1759777, upload-time = "2026-01-03T17:30:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261", size = 1861276, upload-time = "2026-01-03T17:30:56.512Z" }, + { url = "https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0", size = 1743131, upload-time = "2026-01-03T17:30:58.256Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730", size = 1556863, upload-time = "2026-01-03T17:31:00.445Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91", size = 1682793, upload-time = "2026-01-03T17:31:03.024Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3", size = 1716676, upload-time = "2026-01-03T17:31:04.842Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4", size = 1733217, upload-time = "2026-01-03T17:31:06.868Z" }, + { url = "https://files.pythonhosted.org/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998", size = 1552303, upload-time = "2026-01-03T17:31:08.958Z" }, + { url = "https://files.pythonhosted.org/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0", size = 1763673, upload-time = "2026-01-03T17:31:10.676Z" }, + { url = "https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591", size = 1721120, upload-time = "2026-01-03T17:31:12.575Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf", size = 427383, upload-time = "2026-01-03T17:31:14.382Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, ] [[package]] From fa8f6162ae188aa894fd75305e96012bf0f95aaa Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sat, 31 Jan 2026 11:38:40 -0400 Subject: [PATCH 203/298] Add frozen parameter to collection columns with FULL index support Add a `frozen` parameter to List, Set, and Map collection columns in cqlengine. When `frozen=True`, the collection is wrapped as `frozen` and indexes use `FULL(column)` instead of the implicit VALUES indexing. This enables creating frozen collections with FULL indexes: class MyModel(Model): items = columns.List(columns.Text, frozen=True, index=True) Generates: CREATE TABLE mymodel (items frozen>); CREATE INDEX ON mymodel (FULL(items)); Fixes #677 Co-Authored-By: Claude Opus 4.5 --- cassandra/cqlengine/columns.py | 46 +++++++++++++++++++++++----- cassandra/cqlengine/management.py | 6 +++- tests/unit/cqlengine/test_columns.py | 43 +++++++++++++++++++++++++- 3 files changed, 86 insertions(+), 9 deletions(-) diff --git a/cassandra/cqlengine/columns.py b/cassandra/cqlengine/columns.py index 3d85587524..509b606ccc 100644 --- a/cassandra/cqlengine/columns.py +++ b/cassandra/cqlengine/columns.py @@ -837,7 +837,30 @@ def to_database(self, value): class BaseContainerColumn(BaseCollectionColumn): - pass + """ + Base class for container columns (Set, List, Map). + + Supports optional freezing for immutable collections. + """ + + frozen = False + """ + bool flag, indicates this collection should be frozen (immutable). + Frozen collections use FULL indexes instead of VALUES indexes. + """ + + def __init__(self, types, frozen=False, **kwargs): + """ + :param types: a sequence of sub types in this collection + :param frozen: if True, the collection will be frozen (immutable) + """ + self.frozen = frozen + super(BaseContainerColumn, self).__init__(types, **kwargs) + + def _apply_frozen(self): + """Apply frozen wrapper to db_type if frozen=True.""" + if self.frozen: + self._freeze_db_type() class Set(BaseContainerColumn): @@ -849,18 +872,21 @@ class Set(BaseContainerColumn): _python_type_hashable = False - def __init__(self, value_type, strict=True, default=set, **kwargs): + def __init__(self, value_type, strict=True, default=set, frozen=False, **kwargs): """ :param value_type: a column class indicating the types of the value :param strict: sets whether non set values will be coerced to set type on validation, or raise a validation error, defaults to True + :param frozen: if True, the collection will be frozen (immutable) and + use FULL indexes instead of VALUES indexes """ self.strict = strict - super(Set, self).__init__((value_type,), default=default, **kwargs) + super(Set, self).__init__((value_type,), frozen=frozen, default=default, **kwargs) self.value_col = self.types[0] if not self.value_col._python_type_hashable: raise ValidationError("Cannot create a Set with unhashable value type (see PYTHON-494)") self.db_type = 'set<{0}>'.format(self.value_col.db_type) + self._apply_frozen() def validate(self, value): val = super(Set, self).validate(value) @@ -899,13 +925,16 @@ class List(BaseContainerColumn): _python_type_hashable = False - def __init__(self, value_type, default=list, **kwargs): + def __init__(self, value_type, default=list, frozen=False, **kwargs): """ :param value_type: a column class indicating the types of the value + :param frozen: if True, the collection will be frozen (immutable) and + use FULL indexes instead of VALUES indexes """ - super(List, self).__init__((value_type,), default=default, **kwargs) + super(List, self).__init__((value_type,), frozen=frozen, default=default, **kwargs) self.value_col = self.types[0] self.db_type = 'list<{0}>'.format(self.value_col.db_type) + self._apply_frozen() def validate(self, value): val = super(List, self).validate(value) @@ -937,12 +966,14 @@ class Map(BaseContainerColumn): _python_type_hashable = False - def __init__(self, key_type, value_type, default=dict, **kwargs): + def __init__(self, key_type, value_type, default=dict, frozen=False, **kwargs): """ :param key_type: a column class indicating the types of the key :param value_type: a column class indicating the types of the value + :param frozen: if True, the collection will be frozen (immutable) and + use FULL indexes instead of VALUES indexes """ - super(Map, self).__init__((key_type, value_type), default=default, **kwargs) + super(Map, self).__init__((key_type, value_type), frozen=frozen, default=default, **kwargs) self.key_col = self.types[0] self.value_col = self.types[1] @@ -950,6 +981,7 @@ def __init__(self, key_type, value_type, default=dict, **kwargs): raise ValidationError("Cannot create a Map with unhashable key type (see PYTHON-494)") self.db_type = 'map<{0}, {1}>'.format(self.key_col.db_type, self.value_col.db_type) + self._apply_frozen() def validate(self, value): val = super(Map, self).validate(value) diff --git a/cassandra/cqlengine/management.py b/cassandra/cqlengine/management.py index 4ac4192a80..d6dc44119a 100644 --- a/cassandra/cqlengine/management.py +++ b/cassandra/cqlengine/management.py @@ -282,7 +282,11 @@ def _sync_table(model, connection=None): qs = ['CREATE INDEX'] qs += ['ON {0}'.format(cf_name)] - qs += ['("{0}")'.format(column.db_field_name)] + # Use FULL index for frozen collections, VALUES index (implicit) for non-frozen + if isinstance(column, columns.BaseContainerColumn) and column.frozen: + qs += ['(FULL("{0}"))'.format(column.db_field_name)] + else: + qs += ['("{0}")'.format(column.db_field_name)] qs = ' '.join(qs) execute(qs, connection=connection) diff --git a/tests/unit/cqlengine/test_columns.py b/tests/unit/cqlengine/test_columns.py index cba57e88a6..136e8ba339 100644 --- a/tests/unit/cqlengine/test_columns.py +++ b/tests/unit/cqlengine/test_columns.py @@ -14,7 +14,7 @@ import unittest -from cassandra.cqlengine.columns import Column +from cassandra.cqlengine.columns import Column, List, Set, Map, Text, Integer class ColumnTest(unittest.TestCase): @@ -66,3 +66,44 @@ def test_hash(self): c0 = Column() assert id(c0) == c0.__hash__() + +class FrozenCollectionTest(unittest.TestCase): + """Test frozen parameter for collection columns (List, Set, Map).""" + + def test_list_default_not_frozen(self): + col = List(Text) + assert col.frozen is False + assert col.db_type == 'list' + + def test_list_frozen_true(self): + col = List(Text, frozen=True) + assert col.frozen is True + assert col.db_type == 'frozen>' + + def test_set_default_not_frozen(self): + col = Set(Text) + assert col.frozen is False + assert col.db_type == 'set' + + def test_set_frozen_true(self): + col = Set(Text, frozen=True) + assert col.frozen is True + assert col.db_type == 'frozen>' + + def test_map_default_not_frozen(self): + col = Map(Text, Integer) + assert col.frozen is False + assert col.db_type == 'map' + + def test_map_frozen_true(self): + col = Map(Text, Integer, frozen=True) + assert col.frozen is True + assert col.db_type == 'frozen>' + + def test_frozen_with_index(self): + """Test that frozen collections can be created with index=True.""" + col = List(Text, frozen=True, index=True) + assert col.frozen is True + assert col.index is True + assert col.db_type == 'frozen>' + From 0721ac382813375a0db0e910554163dc7ced5eb2 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 1 Feb 2026 07:17:12 -0400 Subject: [PATCH 204/298] Replace asynctest with stdlib mock The asynctest package is unmaintained since 2020 and incompatible with Python 3.12+. Replace asynctest.TestSelector with a MagicMock using selectors.BaseSelector as its spec, which provides the same mock selector functionality needed by the asyncio reactor tests. Fixes #663 --- pyproject.toml | 1 - tests/unit/io/test_asyncioreactor.py | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1c195e1b77..e524051042 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,6 @@ dev = [ "cython>=3.2", "packaging>=25.0", "futurist", - "asynctest", "pyyaml", "numpy", "objgraph", diff --git a/tests/unit/io/test_asyncioreactor.py b/tests/unit/io/test_asyncioreactor.py index c189aa3d74..f3ed942090 100644 --- a/tests/unit/io/test_asyncioreactor.py +++ b/tests/unit/io/test_asyncioreactor.py @@ -1,7 +1,6 @@ AsyncioConnection, ASYNCIO_AVAILABLE = None, False try: from cassandra.io.asyncioreactor import AsyncioConnection - import asynctest ASYNCIO_AVAILABLE = True except (ImportError, SyntaxError, AttributeError): AsyncioConnection = None @@ -10,8 +9,8 @@ from tests import is_monkey_patched, connection_class from tests.unit.io.utils import TimerCallback, TimerTestMixin -from unittest.mock import patch - +from unittest.mock import patch, MagicMock +import selectors import unittest import time @@ -56,7 +55,7 @@ def setUp(self): socket_patcher.start() old_selector = AsyncioConnection._loop._selector - AsyncioConnection._loop._selector = asynctest.TestSelector() + AsyncioConnection._loop._selector = MagicMock(spec=selectors.BaseSelector) def reset_selector(): AsyncioConnection._loop._selector = old_selector From 19cd3e4fd51b94a5cd48084eed2429bb5e59841b Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 1 Feb 2026 13:24:04 -0400 Subject: [PATCH 205/298] Fix flaky test_multi_timer_validation on Windows CI Increase timer tolerance from 150ms to 500ms to account for CI environments under heavy load, especially Windows during wheel building where timing can be significantly less precise. --- tests/unit/io/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/io/utils.py b/tests/unit/io/utils.py index 174137225a..f43224058c 100644 --- a/tests/unit/io/utils.py +++ b/tests/unit/io/utils.py @@ -128,8 +128,10 @@ def submit_and_wait_for_completion(unit_test, create_timer, start, end, incremen time.sleep(.1) # ensure they are all called back in a timely fashion + # Use a generous tolerance (500ms) to account for CI environments under heavy load, + # especially Windows during wheel building where timing can be significantly less precise for callback in completed_callbacks: - assert callback.expected_wait == pytest.approx(callback.get_wait_time(), abs=.15) + assert callback.expected_wait == pytest.approx(callback.get_wait_time(), abs=.5) def noop_if_monkey_patched(f): From ea73376a3a50b57fb1fea297b175eec06fe2e616 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sat, 31 Jan 2026 15:56:45 -0400 Subject: [PATCH 206/298] Remove scales dependency with self-contained metrics implementation The scales library had its last release in 2015 and was only tested on Python 2.7/3.3. This change replaces it with a self-contained metrics implementation that provides the same functionality: - IntStat: Thread-safe integer counter - PmfStat: Percentile/distribution statistics with reservoir sampling - Stat: Gauge statistics with callable evaluation - StatsCollection: Named collections of statistics - Global registry for metrics access via getStats() The new implementation maintains API compatibility with the existing metrics interface used by the driver. Fixes #665 --- benchmarks/base.py | 4 +- cassandra/metrics.py | 358 ++++++++++++++++++--- docs/installation.rst | 9 - docs/pyproject.toml | 1 - examples/request_init_listener.py | 10 +- pyproject.toml | 1 - tests/integration/standard/test_metrics.py | 20 +- tests/unit/test_metrics.py | 304 +++++++++++++++++ 8 files changed, 639 insertions(+), 68 deletions(-) create mode 100644 tests/unit/test_metrics.py diff --git a/benchmarks/base.py b/benchmarks/base.py index 2000b4069f..d9cd004474 100644 --- a/benchmarks/base.py +++ b/benchmarks/base.py @@ -21,7 +21,7 @@ from optparse import OptionParser import uuid -from greplin import scales +from cassandra.metrics import getStats dirname = os.path.dirname(os.path.abspath(__file__)) sys.path.append(dirname) @@ -192,7 +192,7 @@ def benchmark(thread_class): log.info("Total time: %0.2fs" % total) log.info("Average throughput: %0.2f/sec" % (options.num_ops / total)) if options.enable_metrics: - stats = scales.getStats()['cassandra'] + stats = getStats()['cassandra'] log.info("Connection errors: %d", stats['connection_errors']) log.info("Write timeouts: %d", stats['write_timeouts']) log.info("Read timeouts: %d", stats['read_timeouts']) diff --git a/cassandra/metrics.py b/cassandra/metrics.py index abfc863b55..6a1af793ec 100644 --- a/cassandra/metrics.py +++ b/cassandra/metrics.py @@ -12,19 +12,295 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Driver metrics collection module. + +This module provides metrics collection functionality without external dependencies. +It was originally based on the `scales` library but now uses a self-contained +implementation. +""" + from itertools import chain import logging - -try: - from greplin import scales -except ImportError: - raise ImportError( - "The scales library is required for metrics support: " - "https://pypi.org/project/scales/") +import math +import random +import threading log = logging.getLogger(__name__) +# Global stats registry +_stats_registry = {} +_registry_lock = threading.Lock() + + +def getStats(): + """ + Returns a copy of all registered stats. + """ + with _registry_lock: + return {name: stats._get_stats_dict() for name, stats in _stats_registry.items()} + + +class IntStat: + """ + A thread-safe integer counter statistic. + """ + __slots__ = ('name', '_value', '_lock') + + def __init__(self, name): + self.name = name + self._value = 0 + self._lock = threading.Lock() + + def __iadd__(self, other): + with self._lock: + self._value += other + return self + + def __int__(self): + return self._value + + def __repr__(self): + return f"IntStat({self.name}={self._value})" + + @property + def value(self): + return self._value + + +class Stat: + """ + A gauge statistic that evaluates a callable on access. + """ + __slots__ = ('name', '_func') + + def __init__(self, name, func): + self.name = name + self._func = func + + @property + def value(self): + return self._func() + + def __repr__(self): + return f"Stat({self.name}={self.value})" + + +class PmfStat: + """ + A probability mass function statistic that tracks timing/size distributions. + + Computes count, min, max, mean, stddev, median and various percentiles. + Uses reservoir sampling to limit memory usage for large sample counts. + """ + __slots__ = ('name', '_values', '_lock', '_count', '_min', '_max', '_sum', '_sum_sq') + + # Maximum number of values to retain for percentile calculations + _MAX_SAMPLES = 10000 + + def __init__(self, name): + self.name = name + self._values = [] + self._lock = threading.Lock() + self._count = 0 + self._min = float('inf') + self._max = float('-inf') + self._sum = 0.0 + self._sum_sq = 0.0 + + def addValue(self, value): + """Record a new value.""" + with self._lock: + self._count += 1 + self._sum += value + self._sum_sq += value * value + + if value < self._min: + self._min = value + if value > self._max: + self._max = value + + # Reservoir sampling for percentiles + if len(self._values) < self._MAX_SAMPLES: + self._values.append(value) + else: + # Replace random element with decreasing probability + idx = random.randint(0, self._count - 1) + if idx < self._MAX_SAMPLES: + self._values[idx] = value + + def _percentile(self, sorted_values, p): + """Calculate the p-th percentile from sorted values.""" + if not sorted_values: + return 0.0 + k = (len(sorted_values) - 1) * p / 100.0 + f = math.floor(k) + c = math.ceil(k) + if f == c: + return sorted_values[int(k)] + return sorted_values[int(f)] * (c - k) + sorted_values[int(c)] * (k - f) + + def _get_stats(self): + """Calculate all statistics.""" + with self._lock: + count = self._count + if count == 0: + return { + 'count': 0, + 'min': 0.0, + 'max': 0.0, + 'mean': 0.0, + 'stddev': 0.0, + 'median': 0.0, + '75percentile': 0.0, + '95percentile': 0.0, + '98percentile': 0.0, + '99percentile': 0.0, + '999percentile': 0.0, + } + + mean = self._sum / count + + # Calculate stddev using Welford's algorithm values + variance = (self._sum_sq / count) - (mean * mean) + stddev = math.sqrt(max(0, variance)) # max to handle floating point errors + + sorted_values = sorted(self._values) + + return { + 'count': count, + 'min': self._min, + 'max': self._max, + 'mean': mean, + 'stddev': stddev, + 'median': self._percentile(sorted_values, 50), + '75percentile': self._percentile(sorted_values, 75), + '95percentile': self._percentile(sorted_values, 95), + '98percentile': self._percentile(sorted_values, 98), + '99percentile': self._percentile(sorted_values, 99), + '999percentile': self._percentile(sorted_values, 99.9), + } + + def __getitem__(self, key): + return self._get_stats()[key] + + def __iter__(self): + return iter(self._get_stats()) + + def keys(self): + return self._get_stats().keys() + + def items(self): + return self._get_stats().items() + + def values(self): + return self._get_stats().values() + + def __repr__(self): + return f"PmfStat({self.name}, count={self._count})" + + +class StatsCollection: + """ + A named collection of statistics. + """ + __slots__ = ('_name', '_stats', '_int_stats', '_pmf_stats', '_gauge_stats') + + def __init__(self, name, *stats): + self._name = name + self._stats = {} + self._int_stats = {} + self._pmf_stats = {} + self._gauge_stats = {} + + for stat in stats: + self._stats[stat.name] = stat + if isinstance(stat, IntStat): + self._int_stats[stat.name] = stat + elif isinstance(stat, PmfStat): + self._pmf_stats[stat.name] = stat + elif isinstance(stat, Stat): + self._gauge_stats[stat.name] = stat + + def __getattr__(self, name): + if name.startswith('_'): + raise AttributeError(name) + try: + stats = object.__getattribute__(self, '_stats') + if name in stats: + return stats[name] + except AttributeError: + pass + raise AttributeError(f"No stat named '{name}'") + + def __setattr__(self, name, value): + if name.startswith('_'): + object.__setattr__(self, name, value) + return + # Allow rebinding stats (e.g., for augmented assignment like stats.errors += 1) + try: + stats = object.__getattribute__(self, '_stats') + if name in stats: + # For augmented assignment, value should be the same IntStat/PmfStat object + # Just verify and allow the rebind + return + except AttributeError: + pass + raise AttributeError(f"Cannot set attribute '{name}' on StatsCollection") + + def _get_stats_dict(self): + """Return dictionary representation of all stats.""" + result = {} + for name, stat in self._int_stats.items(): + result[name] = stat.value + for name, stat in self._pmf_stats.items(): + result[name] = stat._get_stats() + for name, stat in self._gauge_stats.items(): + result[name] = stat.value + return result + + +def collection(name, *stats): + """ + Create a named collection of statistics and register it globally. + """ + coll = StatsCollection(name, *stats) + with _registry_lock: + _stats_registry[name] = coll + return coll + + +def init(obj, path): + """ + Initialize class-level stats on an instance and register in the global registry. + + This allows class-level PmfStat/IntStat descriptors to be used per-instance. + """ + # Get class-level stats and create instance copies + cls = obj.__class__ + instance_stats = {} + + for attr_name in dir(cls): + attr = getattr(cls, attr_name, None) + if isinstance(attr, (PmfStat, IntStat)): + # Create a new instance of the stat for this object + if isinstance(attr, PmfStat): + new_stat = PmfStat(attr.name) + else: + new_stat = IntStat(attr.name) + instance_stats[attr_name] = new_stat + # Set on instance to shadow class attribute + object.__setattr__(obj, attr_name, new_stat) + + # Register under the given path (remove leading /) + reg_name = path.lstrip('/') + if instance_stats: + stats_coll = StatsCollection(reg_name, *instance_stats.values()) + with _registry_lock: + _stats_registry[reg_name] = stats_coll + + class Metrics(object): """ A collection of timers and counters for various performance metrics. @@ -34,7 +310,7 @@ class Metrics(object): request_timer = None """ - A :class:`greplin.scales.PmfStat` timer for requests. This is a dict-like + A :class:`~cassandra.metrics.PmfStat` timer for requests. This is a dict-like object with the following keys: * count - number of requests that have been timed @@ -52,64 +328,64 @@ class Metrics(object): connection_errors = None """ - A :class:`greplin.scales.IntStat` count of the number of times that a + A :class:`~cassandra.metrics.IntStat` count of the number of times that a request to a Cassandra node has failed due to a connection problem. """ write_timeouts = None """ - A :class:`greplin.scales.IntStat` count of write requests that resulted + A :class:`~cassandra.metrics.IntStat` count of write requests that resulted in a timeout. """ read_timeouts = None """ - A :class:`greplin.scales.IntStat` count of read requests that resulted + A :class:`~cassandra.metrics.IntStat` count of read requests that resulted in a timeout. """ unavailables = None """ - A :class:`greplin.scales.IntStat` count of write or read requests that + A :class:`~cassandra.metrics.IntStat` count of write or read requests that failed due to an insufficient number of replicas being alive to meet the requested :class:`.ConsistencyLevel`. """ other_errors = None """ - A :class:`greplin.scales.IntStat` count of all other request failures, + A :class:`~cassandra.metrics.IntStat` count of all other request failures, including failures caused by invalid requests, bootstrapping nodes, overloaded nodes, etc. """ retries = None """ - A :class:`greplin.scales.IntStat` count of the number of times a + A :class:`~cassandra.metrics.IntStat` count of the number of times a request was retried based on the :class:`.RetryPolicy` decision. """ ignores = None """ - A :class:`greplin.scales.IntStat` count of the number of times a + A :class:`~cassandra.metrics.IntStat` count of the number of times a failed request was ignored based on the :class:`.RetryPolicy` decision. """ known_hosts = None """ - A :class:`greplin.scales.IntStat` count of the number of nodes in + A :class:`~cassandra.metrics.IntStat` count of the number of nodes in the cluster that the driver is aware of, regardless of whether any connections are opened to those nodes. """ connected_to = None """ - A :class:`greplin.scales.IntStat` count of the number of nodes that + A :class:`~cassandra.metrics.IntStat` count of the number of nodes that the driver currently has at least one connection open to. """ open_connections = None """ - A :class:`greplin.scales.IntStat` count of the number connections + A :class:`~cassandra.metrics.IntStat` count of the number connections the driver currently has open. """ @@ -120,28 +396,29 @@ def __init__(self, cluster_proxy): self.stats_name = 'cassandra-{0}'.format(str(self._stats_counter)) Metrics._stats_counter += 1 - self.stats = scales.collection(self.stats_name, - scales.PmfStat('request_timer'), - scales.IntStat('connection_errors'), - scales.IntStat('write_timeouts'), - scales.IntStat('read_timeouts'), - scales.IntStat('unavailables'), - scales.IntStat('other_errors'), - scales.IntStat('retries'), - scales.IntStat('ignores'), + self.stats = collection(self.stats_name, + PmfStat('request_timer'), + IntStat('connection_errors'), + IntStat('write_timeouts'), + IntStat('read_timeouts'), + IntStat('unavailables'), + IntStat('other_errors'), + IntStat('retries'), + IntStat('ignores'), # gauges - scales.Stat('known_hosts', + Stat('known_hosts', lambda: len(cluster_proxy.metadata.all_hosts())), - scales.Stat('connected_to', + Stat('connected_to', lambda: len(set(chain.from_iterable(list(s._pools.keys()) for s in cluster_proxy.sessions)))), - scales.Stat('open_connections', + Stat('open_connections', lambda: sum(sum(p.open_count for p in list(s._pools.values())) for s in cluster_proxy.sessions))) # TODO, to be removed in 4.0 # /cassandra contains the metrics of the first cluster registered - if 'cassandra' not in scales._Stats.stats: - scales._Stats.stats['cassandra'] = scales._Stats.stats[self.stats_name] + with _registry_lock: + if 'cassandra' not in _stats_registry: + _stats_registry['cassandra'] = _stats_registry[self.stats_name] self.request_timer = self.stats.request_timer self.connection_errors = self.stats.connection_errors @@ -180,22 +457,23 @@ def get_stats(self): """ Returns the metrics for the registered cluster instance. """ - return scales.getStats()[self.stats_name] + return getStats()[self.stats_name] def set_stats_name(self, stats_name): """ Set the metrics stats name. - The stats_name is a string used to access the metris through scales: scales.getStats()[] + The stats_name is a string used to access the metrics through getStats(): getStats()[] Default is 'cassandra-'. """ if self.stats_name == stats_name: return - if stats_name in scales._Stats.stats: - raise ValueError('"{0}" already exists in stats.'.format(stats_name)) + with _registry_lock: + if stats_name in _stats_registry: + raise ValueError('"{0}" already exists in stats.'.format(stats_name)) - stats = scales._Stats.stats[self.stats_name] - del scales._Stats.stats[self.stats_name] - self.stats_name = stats_name - scales._Stats.stats[self.stats_name] = stats + stats = _stats_registry[self.stats_name] + del _stats_registry[self.stats_name] + self.stats_name = stats_name + _stats_registry[self.stats_name] = stats diff --git a/docs/installation.rst b/docs/installation.rst index 8e4e54e036..4efd87f07a 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -47,15 +47,6 @@ For snappy support:: (If using a Debian Linux derivative such as Ubuntu, it may be easier to just run ``apt-get install python-snappy``.) -(*Optional*) Metrics Support ----------------------------- -The driver has built-in support for capturing :attr:`.Cluster.metrics` about -the queries you run. However, the ``scales`` library is required to -support this:: - - pip install scales - - Speeding Up Installation ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/pyproject.toml b/docs/pyproject.toml index f6ee417aee..460e6a5609 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -18,7 +18,6 @@ dependencies = [ "sphinx-scylladb-theme>=1.8.2,<2.0.0", "sphinx-multiversion-scylla>=0.3.2,<1.0.0", "sphinx>=8.2.3,<9.0.0", - "scales>=1.0.9,<2.0.0", "six>=1.9", "tornado>=6.5,<7.0", ] diff --git a/examples/request_init_listener.py b/examples/request_init_listener.py index 2ca6df495a..e23ac80fbf 100644 --- a/examples/request_init_listener.py +++ b/examples/request_init_listener.py @@ -19,7 +19,7 @@ # this is just demonstrating a way to track a few custom attributes. from cassandra.cluster import Cluster -from greplin import scales +from cassandra.metrics import PmfStat, IntStat, init import pprint pp = pprint.PrettyPrinter(indent=2) @@ -32,11 +32,11 @@ class RequestAnalyzer(object): Also computes statistics on encoded request size. """ - requests = scales.PmfStat('request size') - errors = scales.IntStat('errors') + requests = PmfStat('request size') + errors = IntStat('errors') def __init__(self, session): - scales.init(self, '/cassandra') + init(self, '/cassandra') # each instance will be registered with a session, and receive a callback for each request generated session.add_request_init_listener(self.on_request) @@ -91,7 +91,7 @@ def __str__(self): pass print() -print(ra) # note: the counts are updated, but the stats are not because scales only updates every 20s +print(ra) # 3 requests (1 errors) # Request size statistics: # { '75percentile': 74, diff --git a/pyproject.toml b/pyproject.toml index e524051042..b19b38934c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,6 @@ auth-kerberos = [ dev = [ "pytest~=8.0", "PyYAML", - "scales", "pure-sasl", "twisted[tls]", "gevent", diff --git a/tests/integration/standard/test_metrics.py b/tests/integration/standard/test_metrics.py index 48c7b49b95..aa9690623d 100644 --- a/tests/integration/standard/test_metrics.py +++ b/tests/integration/standard/test_metrics.py @@ -25,7 +25,7 @@ from cassandra.cluster import NoHostAvailable, ExecutionProfile, EXEC_PROFILE_DEFAULT from tests.integration import get_cluster, get_node, use_singledc, execute_until_pass, TestCluster -from greplin import scales +from cassandra import metrics from tests.integration import BasicSharedKeyspaceUnitTestCaseRF3WM, BasicExistingKeyspaceUnitTestCase, local import pprint as pp @@ -223,7 +223,7 @@ def test_metrics_per_cluster(self): finally: get_node(1).resume() - # Change the scales stats_name of the cluster2 + # Change the stats_name of the cluster2 cluster2.metrics.set_stats_name('cluster2-metrics') stats_cluster1 = self.cluster.metrics.get_stats() @@ -242,7 +242,7 @@ def test_metrics_per_cluster(self): assert 0.0 == stats_cluster2['request_timer']['mean'] # Test access by stats_name - assert 0.0 == scales.getStats()['cluster2-metrics']['request_timer']['mean'] + assert 0.0 == metrics.getStats()['cluster2-metrics']['request_timer']['mean'] cluster2.shutdown() @@ -289,9 +289,9 @@ def test_duplicate_metrics_per_cluster(self): assert cluster2.metrics.get_stats()['request_timer']['count'] == 10 assert cluster3.metrics.get_stats()['request_timer']['count'] == 5 - # Check scales to ensure they are appropriately named - assert "appcluster" in scales._Stats.stats.keys() - assert "devops" in scales._Stats.stats.keys() + # Check registry to ensure they are appropriately named + assert "appcluster" in metrics._stats_registry.keys() + assert "devops" in metrics._stats_registry.keys() cluster2.shutdown() cluster3.shutdown() @@ -303,15 +303,15 @@ class RequestAnalyzer(object): Also computes statistics on encoded request size. """ - requests = scales.PmfStat('request size') - errors = scales.IntStat('errors') - successful = scales.IntStat("success") + requests = metrics.PmfStat('request size') + errors = metrics.IntStat('errors') + successful = metrics.IntStat("success") # Throw exceptions when invoked. throw_on_success = False throw_on_fail = False def __init__(self, session, throw_on_success=False, throw_on_fail=False): - scales.init(self, '/request') + metrics.init(self, '/request') # each instance will be registered with a session, and receive a callback for each request generated session.add_request_init_listener(self.on_request) self.throw_on_fail = throw_on_fail diff --git a/tests/unit/test_metrics.py b/tests/unit/test_metrics.py new file mode 100644 index 0000000000..88b1bff4ff --- /dev/null +++ b/tests/unit/test_metrics.py @@ -0,0 +1,304 @@ +# Copyright DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Unit tests for the self-contained metrics module. +""" + +import threading +import unittest + +from cassandra.metrics import ( + IntStat, Stat, PmfStat, StatsCollection, + collection, init, getStats, _stats_registry, _registry_lock +) + + +class IntStatTest(unittest.TestCase): + """Tests for IntStat class.""" + + def test_initial_value(self): + stat = IntStat('test_counter') + self.assertEqual(stat.value, 0) + self.assertEqual(int(stat), 0) + + def test_increment(self): + stat = IntStat('test_counter') + stat += 1 + self.assertEqual(stat.value, 1) + stat += 5 + self.assertEqual(stat.value, 6) + + def test_thread_safety(self): + stat = IntStat('test_counter') + num_threads = 10 + increments_per_thread = 1000 + + def increment(): + nonlocal stat + for _ in range(increments_per_thread): + stat += 1 + + threads = [threading.Thread(target=increment) for _ in range(num_threads)] + for t in threads: + t.start() + for t in threads: + t.join() + + self.assertEqual(stat.value, num_threads * increments_per_thread) + + def test_repr(self): + stat = IntStat('my_counter') + stat += 42 + self.assertEqual(repr(stat), "IntStat(my_counter=42)") + + +class StatTest(unittest.TestCase): + """Tests for Stat (gauge) class.""" + + def test_basic_gauge(self): + counter = [0] + stat = Stat('test_gauge', lambda: counter[0]) + + self.assertEqual(stat.value, 0) + counter[0] = 10 + self.assertEqual(stat.value, 10) + counter[0] = 42 + self.assertEqual(stat.value, 42) + + def test_repr(self): + stat = Stat('my_gauge', lambda: 123) + self.assertEqual(repr(stat), "Stat(my_gauge=123)") + + +class PmfStatTest(unittest.TestCase): + """Tests for PmfStat class.""" + + def test_empty_stats(self): + stat = PmfStat('test_timer') + stats = stat._get_stats() + + self.assertEqual(stats['count'], 0) + self.assertEqual(stats['min'], 0.0) + self.assertEqual(stats['max'], 0.0) + self.assertEqual(stats['mean'], 0.0) + self.assertEqual(stats['stddev'], 0.0) + self.assertEqual(stats['median'], 0.0) + + def test_single_value(self): + stat = PmfStat('test_timer') + stat.addValue(10.0) + stats = stat._get_stats() + + self.assertEqual(stats['count'], 1) + self.assertEqual(stats['min'], 10.0) + self.assertEqual(stats['max'], 10.0) + self.assertEqual(stats['mean'], 10.0) + self.assertEqual(stats['stddev'], 0.0) + self.assertEqual(stats['median'], 10.0) + + def test_multiple_values(self): + stat = PmfStat('test_timer') + for v in [1, 2, 3, 4, 5]: + stat.addValue(v) + stats = stat._get_stats() + + self.assertEqual(stats['count'], 5) + self.assertEqual(stats['min'], 1.0) + self.assertEqual(stats['max'], 5.0) + self.assertEqual(stats['mean'], 3.0) + self.assertEqual(stats['median'], 3.0) + + def test_dict_like_access(self): + stat = PmfStat('test_timer') + stat.addValue(5.0) + + self.assertEqual(stat['count'], 1) + self.assertEqual(stat['mean'], 5.0) + self.assertIn('count', stat.keys()) + self.assertIn('mean', stat.keys()) + + def test_percentiles(self): + stat = PmfStat('test_timer') + # Add values 1-100 + for v in range(1, 101): + stat.addValue(v) + stats = stat._get_stats() + + self.assertEqual(stats['count'], 100) + self.assertEqual(stats['min'], 1.0) + self.assertEqual(stats['max'], 100.0) + # Median should be around 50 + self.assertAlmostEqual(stats['median'], 50.5, delta=1) + # 75th percentile should be around 75 + self.assertAlmostEqual(stats['75percentile'], 75.25, delta=1) + # 95th percentile should be around 95 + self.assertAlmostEqual(stats['95percentile'], 95.05, delta=1) + + def test_thread_safety(self): + stat = PmfStat('test_timer') + num_threads = 10 + values_per_thread = 100 + + def add_values(): + for i in range(values_per_thread): + stat.addValue(i) + + threads = [threading.Thread(target=add_values) for _ in range(num_threads)] + for t in threads: + t.start() + for t in threads: + t.join() + + stats = stat._get_stats() + self.assertEqual(stats['count'], num_threads * values_per_thread) + + +class StatsCollectionTest(unittest.TestCase): + """Tests for StatsCollection class.""" + + def test_access_stats_by_attribute(self): + int_stat = IntStat('errors') + pmf_stat = PmfStat('latency') + + coll = StatsCollection('test', int_stat, pmf_stat) + + self.assertIs(coll.errors, int_stat) + self.assertIs(coll.latency, pmf_stat) + + def test_augmented_assignment(self): + int_stat = IntStat('errors') + coll = StatsCollection('test', int_stat) + + coll.errors += 1 + self.assertEqual(int_stat.value, 1) + coll.errors += 5 + self.assertEqual(int_stat.value, 6) + + def test_get_stats_dict(self): + int_stat = IntStat('errors') + int_stat += 3 + pmf_stat = PmfStat('latency') + pmf_stat.addValue(10.0) + gauge = Stat('connections', lambda: 5) + + coll = StatsCollection('test', int_stat, pmf_stat, gauge) + stats_dict = coll._get_stats_dict() + + self.assertEqual(stats_dict['errors'], 3) + self.assertEqual(stats_dict['connections'], 5) + self.assertIsInstance(stats_dict['latency'], dict) + self.assertEqual(stats_dict['latency']['count'], 1) + self.assertEqual(stats_dict['latency']['mean'], 10.0) + + def test_nonexistent_attribute(self): + coll = StatsCollection('test', IntStat('errors')) + with self.assertRaises(AttributeError): + _ = coll.nonexistent + + def test_cannot_set_new_attribute(self): + coll = StatsCollection('test', IntStat('errors')) + with self.assertRaises(AttributeError): + coll.new_attr = 123 + + +class CollectionFunctionTest(unittest.TestCase): + """Tests for the collection() function.""" + + def setUp(self): + # Clean up registry before each test + with _registry_lock: + keys_to_remove = [k for k in _stats_registry.keys() + if k.startswith('test_')] + for k in keys_to_remove: + del _stats_registry[k] + + def test_registers_in_global_registry(self): + coll = collection('test_coll', IntStat('counter')) + + with _registry_lock: + self.assertIn('test_coll', _stats_registry) + self.assertIs(_stats_registry['test_coll'], coll) + + def test_get_stats_returns_dict(self): + int_stat = IntStat('counter') + int_stat += 10 + collection('test_stats', int_stat) + + stats = getStats() + self.assertIn('test_stats', stats) + self.assertEqual(stats['test_stats']['counter'], 10) + + +class InitFunctionTest(unittest.TestCase): + """Tests for the init() function.""" + + def setUp(self): + # Clean up registry before each test + with _registry_lock: + keys_to_remove = [k for k in _stats_registry.keys() + if k.startswith('test') or k == 'request'] + for k in keys_to_remove: + del _stats_registry[k] + + def test_creates_instance_stats(self): + class Analyzer: + requests = PmfStat('request size') + errors = IntStat('errors') + + def __init__(self): + init(self, '/test_analyzer') + + analyzer = Analyzer() + + # Instance should have its own stats + self.assertIsInstance(analyzer.requests, PmfStat) + self.assertIsInstance(analyzer.errors, IntStat) + + # Should be different from class-level stats + self.assertIsNot(analyzer.requests, Analyzer.requests) + self.assertIsNot(analyzer.errors, Analyzer.errors) + + def test_instance_stats_are_independent(self): + class Analyzer: + errors = IntStat('errors') + + def __init__(self): + init(self, '/test_analyzer') + + a1 = Analyzer() + a2 = Analyzer() + + a1.errors += 5 + a2.errors += 10 + + self.assertEqual(a1.errors.value, 5) + self.assertEqual(a2.errors.value, 10) + + def test_strips_leading_slash(self): + class Analyzer: + errors = IntStat('errors') + + def __init__(self): + init(self, '/test_path') + + Analyzer() + + with _registry_lock: + self.assertIn('test_path', _stats_registry) + self.assertNotIn('/test_path', _stats_registry) + + +if __name__ == '__main__': + unittest.main() From e2894fb5817831cfe3eb2e10864c6be38bcf5037 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 1 Feb 2026 02:25:49 -0400 Subject: [PATCH 207/298] Fix IntStat comparison operators and metrics cleanup on shutdown - Add comparison operators (__eq__, __ne__, __lt__, __le__, __gt__, __ge__) to IntStat class for direct comparison with integers - Add __hash__ method to IntStat for use in sets/dicts - Add Metrics.shutdown() method to remove stats from global registry - Call metrics.shutdown() from Cluster.shutdown() to prevent stale weakref errors when accessing getStats() after cluster shutdown - Fix test using 'is' instead of '==' for IntStat comparison - Add unit tests for new comparison operators and shutdown functionality --- cassandra/cluster.py | 3 + cassandra/metrics.py | 43 ++++++++++++ tests/integration/standard/test_metrics.py | 4 +- tests/unit/test_metrics.py | 82 ++++++++++++++++++++++ 4 files changed, 130 insertions(+), 2 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 099043eae0..622b706330 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -1795,6 +1795,9 @@ def shutdown(self): self.executor.shutdown() + if self.metrics_enabled and self.metrics: + self.metrics.shutdown() + _discard_cluster_shutdown(self) def __enter__(self): diff --git a/cassandra/metrics.py b/cassandra/metrics.py index 6a1af793ec..7ff44107af 100644 --- a/cassandra/metrics.py +++ b/cassandra/metrics.py @@ -61,6 +61,37 @@ def __iadd__(self, other): def __int__(self): return self._value + def __eq__(self, other): + if isinstance(other, IntStat): + return self._value == other._value + return self._value == other + + def __ne__(self, other): + return not self.__eq__(other) + + def __lt__(self, other): + if isinstance(other, IntStat): + return self._value < other._value + return self._value < other + + def __le__(self, other): + if isinstance(other, IntStat): + return self._value <= other._value + return self._value <= other + + def __gt__(self, other): + if isinstance(other, IntStat): + return self._value > other._value + return self._value > other + + def __ge__(self, other): + if isinstance(other, IntStat): + return self._value >= other._value + return self._value >= other + + def __hash__(self): + return hash(self._value) + def __repr__(self): return f"IntStat({self.name}={self._value})" @@ -477,3 +508,15 @@ def set_stats_name(self, stats_name): del _stats_registry[self.stats_name] self.stats_name = stats_name _stats_registry[self.stats_name] = stats + + def shutdown(self): + """ + Remove this metrics instance from the global registry. + Called when the cluster is shutdown to prevent stale references. + """ + with _registry_lock: + if self.stats_name in _stats_registry: + del _stats_registry[self.stats_name] + # Also clean up the legacy 'cassandra' entry if it points to our stats + if _stats_registry.get('cassandra') is self.stats: + del _stats_registry['cassandra'] diff --git a/tests/integration/standard/test_metrics.py b/tests/integration/standard/test_metrics.py index aa9690623d..7b502d91c3 100644 --- a/tests/integration/standard/test_metrics.py +++ b/tests/integration/standard/test_metrics.py @@ -355,10 +355,10 @@ def setUpClass(cls): def wait_for_count(self, ra, expected_count, error=False): for _ in range(10): if not error: - if ra.successful is expected_count: + if ra.successful == expected_count: return True else: - if ra.errors is expected_count: + if ra.errors == expected_count: return True time.sleep(.01) return False diff --git a/tests/unit/test_metrics.py b/tests/unit/test_metrics.py index 88b1bff4ff..3a8d1d2432 100644 --- a/tests/unit/test_metrics.py +++ b/tests/unit/test_metrics.py @@ -63,6 +63,35 @@ def test_repr(self): stat += 42 self.assertEqual(repr(stat), "IntStat(my_counter=42)") + def test_equality(self): + stat = IntStat('test') + stat += 5 + self.assertEqual(stat, 5) + self.assertEqual(5, stat) + self.assertNotEqual(stat, 3) + self.assertNotEqual(stat, 10) + + def test_comparison_operators(self): + stat = IntStat('test') + stat += 5 + self.assertTrue(stat > 0) + self.assertTrue(stat >= 5) + self.assertTrue(stat < 10) + self.assertTrue(stat <= 5) + self.assertFalse(stat > 5) + self.assertFalse(stat < 5) + + def test_comparison_with_intstat(self): + stat1 = IntStat('test1') + stat2 = IntStat('test2') + stat1 += 5 + stat2 += 5 + self.assertEqual(stat1, stat2) + stat2 += 1 + self.assertNotEqual(stat1, stat2) + self.assertTrue(stat1 < stat2) + self.assertTrue(stat2 > stat1) + class StatTest(unittest.TestCase): """Tests for Stat (gauge) class.""" @@ -300,5 +329,58 @@ def __init__(self): self.assertNotIn('/test_path', _stats_registry) +class MetricsShutdownTest(unittest.TestCase): + """Tests for Metrics shutdown functionality.""" + + def setUp(self): + # Clean up registry before each test + with _registry_lock: + keys_to_remove = [k for k in _stats_registry.keys() + if k.startswith('cassandra')] + for k in keys_to_remove: + del _stats_registry[k] + + def test_shutdown_removes_from_registry(self): + import weakref + from cassandra.metrics import Metrics + + class MockCluster: + def __init__(self): + self.metadata = type('obj', (object,), {'all_hosts': lambda: []})() + self.sessions = [] + + cluster = MockCluster() + proxy = weakref.proxy(cluster) + + metrics = Metrics(proxy) + stats_name = metrics.stats_name + + with _registry_lock: + self.assertIn(stats_name, _stats_registry) + + metrics.shutdown() + + with _registry_lock: + self.assertNotIn(stats_name, _stats_registry) + + def test_shutdown_is_idempotent(self): + import weakref + from cassandra.metrics import Metrics + + class MockCluster: + def __init__(self): + self.metadata = type('obj', (object,), {'all_hosts': lambda: []})() + self.sessions = [] + + cluster = MockCluster() + proxy = weakref.proxy(cluster) + + metrics = Metrics(proxy) + + # Should not raise even if called multiple times + metrics.shutdown() + metrics.shutdown() + + if __name__ == '__main__': unittest.main() From d19b099836d05db925cc2be9af1c2f745c263797 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 2 Feb 2026 19:17:47 -0400 Subject: [PATCH 208/298] Change header to unit/test_metrics.py It used to mention that it Datastax copyright, but it was implmemented by us (ScyllaDB). So we need to make it right. --- tests/unit/test_metrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_metrics.py b/tests/unit/test_metrics.py index 3a8d1d2432..07e851188c 100644 --- a/tests/unit/test_metrics.py +++ b/tests/unit/test_metrics.py @@ -1,4 +1,4 @@ -# Copyright DataStax, Inc. +# Copyright ScyllaDB, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From b4e4b59e9c327ecba88a10f81404ee686a4ace84 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 9 Feb 2026 14:03:30 -0400 Subject: [PATCH 209/298] Release 3.29.8: changelog, version and documentation --- CHANGELOG.rst | 34 ++++++++++++++++++++++++++++++++++ cassandra/__init__.py | 2 +- docs/conf.py | 3 ++- docs/installation.rst | 4 ++-- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 82c84ccc51..0c4aa63669 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,37 @@ +3.29.8 +====== +February 09, 2026 + +Features +-------- +* Add frozen parameter to collection columns with FULL index support +* Include original error in ConnectionShutdown messages + +Bug Fixes +--------- +* Fix IntStat comparison operators and metrics cleanup on shutdown +* Fix NumPy 2.0 compatibility in numpy_parser +* Fix race condition during host IP address update +* Fix infinite retry when single host fails with server error +* Don't mark node down when control connection fails to connect +* Call on_add before distance to properly initialize lbp +* Don't check if host is in initial contact points when setting default local_dc +* Pull version information from system.local when version info is not present +* Fix missing call to superclass ``__init__`` during object initialization + +Others +------ +* Remove scales dependency with self-contained metrics implementation +* Migrate from pytz to zoneinfo +* Remove Python 2 compatibility code +* Optimize write path in protocol.py to reduce copies +* TokenAwarePolicy: remove redundant check if a table is using tablets +* Don't create Host instances with random host_id +* Use endpoint instead of Host in _try_connect +* Remove support for protocols <3 from cython files +* Return empty query plan if there are no live hosts +* Replace asynctest with stdlib mock + 3.29.7 ====== December 08, 2025 diff --git a/cassandra/__init__.py b/cassandra/__init__.py index 88fbb11f88..5567c0b9bd 100644 --- a/cassandra/__init__.py +++ b/cassandra/__init__.py @@ -23,7 +23,7 @@ def emit(self, record): logging.getLogger('cassandra').addHandler(NullHandler()) -__version_info__ = (3, 29, 7) +__version_info__ = (3, 29, 8) __version__ = '.'.join(map(str, __version_info__)) diff --git a/docs/conf.py b/docs/conf.py index 1b0361db39..403908c29e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,10 +28,11 @@ '3.29.5-scylla', '3.29.6-scylla', '3.29.7-scylla', + '3.29.8-scylla', ] BRANCHES = ['master'] # Set the latest version. -LATEST_VERSION = '3.29.7-scylla' +LATEST_VERSION = '3.29.8-scylla' # Set which versions are not released yet. UNSTABLE_VERSIONS = ['master'] # Set which versions are deprecated diff --git a/docs/installation.rst b/docs/installation.rst index 4efd87f07a..4207c46092 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -26,7 +26,7 @@ To check if the installation was successful, you can run:: python -c 'import cassandra; print(cassandra.__version__)' -It should print something like "3.29.7". +It should print something like "3.29.8". (*Optional*) Compression Support -------------------------------- @@ -199,7 +199,7 @@ through `Homebrew `_. For example, on Mac OS X:: $ brew install libev -The libev extension can now be built for Windows as of Python driver version 3.29.7. You can +The libev extension can now be built for Windows as of Python driver version 3.29.8. You can install libev using any Windows package manager. For example, to install using `vcpkg `_: $ vcpkg install libev From 454f727426907f8de8a77ac089539a60c8f31936 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 13 Feb 2026 16:41:23 -0400 Subject: [PATCH 210/298] Fix flaky test_idle_heartbeat and CI workflow YAML syntax The test_idle_heartbeat test could fail with a KeyError when shard-aware reconnection replaced connections during the sleep interval. Skip connections not present in the original snapshot. Also fix a trailing whitespace/dash in integration-tests.yml that caused a YAML syntax issue in the paths-ignore list. --- .github/workflows/integration-tests.yml | 2 +- tests/integration/standard/test_cluster.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 210c2d4e2b..048dbd1352 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -29,7 +29,7 @@ on: - .github/dependabot.yml - .github/pull_request_template.md - "*.md" - - .github/workflows/docs-* - + - .github/workflows/docs-* workflow_dispatch: jobs: diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 1208edb9d2..243d7b06d2 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -746,8 +746,11 @@ def test_idle_heartbeat(self): connections = [c for holders in cluster.get_connection_holders() for c in holders.get_connections()] - # make sure requests were sent on all connections + # make sure requests were sent on all connections that existed before the sleep + # (shard-aware reconnection may replace connections during the sleep interval) for c in connections: + if id(c) not in connection_request_ids: + continue expected_ids = connection_request_ids[id(c)] expected_ids.rotate(-1) with c.lock: From f3486373063b8b2e1bc6ff67ea283a328d83f4c7 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Fri, 13 Feb 2026 17:40:58 -0400 Subject: [PATCH 211/298] Fix test_idle_heartbeat: wait for shard connections and fix idle assertion Two issues caused this test to be flaky: 1. wait_for_all_pools only waits for the first connection per host. Shard-aware connections to remaining shards are opened asynchronously. When these complete during the test's sleep interval, they replace existing connections causing KeyError on the request_ids snapshot. Fix: add a helper that polls until all shard connections are established, called after connect(). 2. execute_concurrent sent only len(hosts) queries, but with shard-aware routing each query hits one specific shard, leaving other shards' connections idle. The assertion that ALL connections are non-idle then fails. Fix: send more queries (2x num_connections) and relax assertion to check that at least some non-control connections became non-idle. --- tests/integration/standard/test_cluster.py | 40 +++++++++++++++++----- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 243d7b06d2..bf62f5df48 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -725,12 +725,34 @@ def _warning_are_issued_when_auth(self, auth_provider): assert auth_warning >= 4 assert auth_warning == mock_handler.get_message_count("debug", "Got ReadyMessage on new connection") + def _wait_for_all_shard_connections(self, cluster, timeout=30): + """Wait until all shard-aware connections are fully established.""" + from cassandra.pool import HostConnection + deadline = time.time() + timeout + while time.time() < deadline: + all_connected = True + for holder in cluster.get_connection_holders(): + if not isinstance(holder, HostConnection): + continue + if holder.host.sharding_info and len(holder._connections) < holder.host.sharding_info.shards_count: + all_connected = False + break + if all_connected: + return + time.sleep(0.1) + raise RuntimeError("Timed out waiting for all shard connections to be established") + def test_idle_heartbeat(self): interval = 2 cluster = TestCluster(idle_heartbeat_interval=interval, monitor_reporting_enabled=False) session = cluster.connect(wait_for_all_pools=True) + # wait_for_all_pools only waits for the first connection per host; + # shard-aware connections to remaining shards are opened in background. + # Wait for them to stabilize so they don't get replaced during the test. + self._wait_for_all_shard_connections(cluster) + # This test relies on impl details of connection req id management to see if heartbeats # are being sent. May need update if impl is changed connection_request_ids = {} @@ -746,11 +768,8 @@ def test_idle_heartbeat(self): connections = [c for holders in cluster.get_connection_holders() for c in holders.get_connections()] - # make sure requests were sent on all connections that existed before the sleep - # (shard-aware reconnection may replace connections during the sleep interval) + # make sure requests were sent on all connections for c in connections: - if id(c) not in connection_request_ids: - continue expected_ids = connection_request_ids[id(c)] expected_ids.rotate(-1) with c.lock: @@ -759,14 +778,19 @@ def test_idle_heartbeat(self): # assert idle status assert all(c.is_idle for c in connections) - # send messages on all connections - statements_and_params = [("SELECT release_version FROM system.local WHERE key='local'", ())] * len(cluster.metadata.all_hosts()) + # send enough messages to ensure all connections are used + # (with shard-aware routing, each query only hits one shard per host, + # so we need more queries than just len(hosts) to cover all connections) + num_connections = len([c for c in connections if not c.is_control_connection]) + statements_and_params = [("SELECT release_version FROM system.local WHERE key='local'", ())] * max(num_connections * 2, len(cluster.metadata.all_hosts())) results = execute_concurrent(session, statements_and_params) for success, result in results: assert success - # assert not idle status - assert not any(c.is_idle if not c.is_control_connection else False for c in connections) + # assert at least some non-control connections are no longer idle + # (shard-aware routing may not distribute queries to every connection) + non_idle = [c for c in connections if not c.is_control_connection and not c.is_idle] + assert len(non_idle) > 0 # holders include session pools and cc holders = cluster.get_connection_holders() From bf4fb029f9b8b6ac74a04f76deb6169c15081e1a Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sat, 21 Feb 2026 18:29:42 -0400 Subject: [PATCH 212/298] Fix segmentation fault in libev prepare_callback during shutdown This commit fixes a race condition that causes segmentation faults when the Python driver shuts down while libev callbacks are still executing. Changes: 1. Add null checks in prepare_callback() to handle destroyed objects 2. Stop prepare watcher before cleanup to prevent race conditions 3. Increase thread join timeout to allow proper shutdown synchronization The issue occurred when metrics cleanup during shutdown raced with libev callbacks executing in background threads, causing access to freed Python objects. Fixes crash at address 0x2f998 in prepare_callback accessing destroyed libevwrapper_Prepare objects. --- cassandra/io/libevreactor.py | 7 ++++++- cassandra/io/libevwrapper.c | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/cassandra/io/libevreactor.py b/cassandra/io/libevreactor.py index d7b365e451..c3f8f967ee 100644 --- a/cassandra/io/libevreactor.py +++ b/cassandra/io/libevreactor.py @@ -116,6 +116,10 @@ def _cleanup(self): if not self._thread: return + # Stop the prepare watcher first to prevent race conditions + if self._preparer: + self._preparer.stop() + for conn in self._live_conns | self._new_conns | self._closed_conns: conn.close() for watcher in (conn._write_watcher, conn._read_watcher): @@ -125,8 +129,9 @@ def _cleanup(self): self.notify() # wake the timer watcher # PYTHON-752 Thread might have just been created and not started + # Use longer timeout to allow proper cleanup with self._lock_thread: - self._thread.join(timeout=1.0) + self._thread.join(timeout=5.0) if self._thread.is_alive(): log.warning( diff --git a/cassandra/io/libevwrapper.c b/cassandra/io/libevwrapper.c index f32504fa34..95a3857bf4 100644 --- a/cassandra/io/libevwrapper.c +++ b/cassandra/io/libevwrapper.c @@ -354,6 +354,10 @@ static void prepare_callback(struct ev_loop *loop, ev_prepare *watcher, int reve PyObject *result = NULL; PyGILState_STATE gstate; + if (!self || !self->callback) { + return; // Skip callback if object is being destroyed + } + gstate = PyGILState_Ensure(); result = PyObject_CallFunction(self->callback, "O", self); if (!result) { From cc6df48b8528cc19c31526b4be3e67fdae1cdf6e Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sat, 21 Feb 2026 18:46:19 -0400 Subject: [PATCH 213/298] Add null checks to io_callback and timer_callback in libev wrapper This extends the segmentation fault fix to cover all libev callbacks that access Python objects during shutdown. Changes: 1. Add null checks in io_callback() before accessing self->callback 2. Add null checks in timer_callback() before accessing self->callback These prevent race conditions similar to the prepare_callback issue where libev callbacks could execute after Python objects are destroyed during driver shutdown. --- cassandra/io/libevwrapper.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cassandra/io/libevwrapper.c b/cassandra/io/libevwrapper.c index 95a3857bf4..fc25f9ceba 100644 --- a/cassandra/io/libevwrapper.c +++ b/cassandra/io/libevwrapper.c @@ -118,7 +118,13 @@ IO_dealloc(libevwrapper_IO *self) { static void io_callback(struct ev_loop *loop, ev_io *watcher, int revents) { libevwrapper_IO *self = watcher->data; PyObject *result; - PyGILState_STATE gstate = PyGILState_Ensure(); + PyGILState_STATE gstate; + + if (!self || !self->callback) { + return; // Skip callback if object is being destroyed + } + + gstate = PyGILState_Ensure(); if (revents & EV_ERROR && errno) { result = PyObject_CallFunction(self->callback, "Obi", self, revents, errno); } else { @@ -477,6 +483,10 @@ static void timer_callback(struct ev_loop *loop, ev_timer *watcher, int revents) PyObject *result = NULL; PyGILState_STATE gstate; + if (!self || !self->callback) { + return; // Skip callback if object is being destroyed + } + gstate = PyGILState_Ensure(); result = PyObject_CallFunction(self->callback, NULL); if (!result) { From 0b1802b7c44a83a5a4ff1e6ddfee7244a1b6ea62 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Mon, 9 Feb 2026 17:56:40 +0200 Subject: [PATCH 214/298] tests: enable vector integration tests on Scylla 2025.4+ Vector type is supported on Scylla 2025.4 and above. Enable the integration tests. Tested locally against both 2025.4.2 and 2026.1 and they pass. Tested locally against 2025.1, where it is skipped, as it should be. Signed-off-by: Yaniv Kaul --- tests/integration/__init__.py | 8 ++++++++ tests/integration/standard/test_types.py | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index b4eab35875..dfac2dc1d9 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -279,6 +279,11 @@ def xfail_scylla_version(filter: Callable[[Version], bool], reason: str, *args, greaterthanorequalcass3_11 = unittest.skipUnless(CASSANDRA_VERSION >= Version('3.11'), 'Cassandra version 3.11 or greater required') greaterthanorequalcass40 = unittest.skipUnless(CASSANDRA_VERSION >= Version('4.0'), 'Cassandra version 4.0 or greater required') greaterthanorequalcass50 = unittest.skipUnless(CASSANDRA_VERSION >= Version('5.0-beta'), 'Cassandra version 5.0 or greater required') +def _has_vector_type(): + if SCYLLA_VERSION is not None: + return Version(get_scylla_version(SCYLLA_VERSION)) >= Version('2025.4') + return CASSANDRA_VERSION >= Version('5.0-beta') + lessthanorequalcass40 = unittest.skipUnless(CASSANDRA_VERSION <= Version('4.0'), 'Cassandra version less or equal to 4.0 required') lessthancass40 = unittest.skipUnless(CASSANDRA_VERSION < Version('4.0'), 'Cassandra version less than 4.0 required') lessthancass30 = unittest.skipUnless(CASSANDRA_VERSION < Version('3.0'), 'Cassandra version less then 3.0 required') @@ -297,6 +302,9 @@ def xfail_scylla_version(filter: Callable[[Version], bool], reason: str, *args, reason='Scylla does not support composite types') requires_custom_payload = pytest.mark.skipif(SCYLLA_VERSION is not None or PROTOCOL_VERSION < 4, reason='Scylla does not support custom payloads. Cassandra requires native protocol v4.0+') +requires_vector_type = unittest.skipUnless( + _has_vector_type(), + 'Cassandra >= 5.0 or Scylla >= 2025.4 required') xfail_scylla = lambda reason, *args, **kwargs: pytest.mark.xfail(SCYLLA_VERSION is not None, reason=reason, *args, **kwargs) incorrect_test = lambda reason='This test seems to be incorrect and should be fixed', *args, **kwargs: pytest.mark.xfail(reason=reason, *args, **kwargs) diff --git a/tests/integration/standard/test_types.py b/tests/integration/standard/test_types.py index ad69fbada9..1d66ce1ed9 100644 --- a/tests/integration/standard/test_types.py +++ b/tests/integration/standard/test_types.py @@ -40,7 +40,8 @@ from tests.integration import use_singledc, execute_until_pass, notprotocolv1, \ BasicSharedKeyspaceUnitTestCase, greaterthancass21, lessthancass30, \ - greaterthanorequalcass3_10, TestCluster, requires_composite_type, greaterthanorequalcass50 + greaterthanorequalcass3_10, TestCluster, requires_composite_type, \ + requires_vector_type from tests.integration.datatype_utils import update_datatypes, PRIMITIVE_DATATYPES, COLLECTION_TYPES, PRIMITIVE_DATATYPES_KEYS, \ get_sample, get_all_samples, get_collection_sample import pytest @@ -984,7 +985,7 @@ def run_inserts_at_version(self, proto_ver): finally: session.cluster.shutdown() -@greaterthanorequalcass50 +@requires_vector_type class TypeTestsVector(BasicSharedKeyspaceUnitTestCase): def _get_first_j(self, rs): From 1206f350f708304bcc0e94c1ce66944b748b407b Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 22 Feb 2026 15:35:22 -0400 Subject: [PATCH 215/298] Remove deprecated setup_requires in favor of PEP 517 build-system.requires setup.py passed setup_requires=['Cython>=3.0.11,<4'] to setup(), which triggers a deprecation warning on setuptools>=70: setuptools.installer and fetch_build_eggs are deprecated. Requirements should be satisfied by a PEP 517 installer. Since pyproject.toml already declares Cython in [build-system].requires, the setup_requires is redundant. Remove it along with the supporting pre_build_check() function, the egg_info bypass, and the CASS_DRIVER_ALLOWED_CYTHON_VERSION env var handling. Users who need a specific Cython version can use UV_CONSTRAINT, PIP_CONSTRAINT, or pre-install it before building. Fixes #694 --- pyproject.toml | 1 - setup.py | 71 -------------------------------------------------- 2 files changed, 72 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b19b38934c..7f60ed0b2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,7 +110,6 @@ cache-keys = [ { env = "CASS_DRIVER_NO_CYTHON" }, { env = "CASS_DRIVER_BUILD_CONCURRENCY" }, { env = "CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST" }, - { env = "CASS_DRIVER_ALLOWED_CYTHON_VERSION" }, # used by setuptools_scm { git = { commit = true, tags = true } }, diff --git a/setup.py b/setup.py index 340ded87ed..52e04a63e5 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,6 @@ # limitations under the License. import os -import shutil import sys import json import warnings @@ -201,8 +200,6 @@ def eval_env_var_as_array(varname): try_murmur3 = try_extensions and "--no-murmur3" not in sys.argv try_libev = try_extensions and "--no-libev" not in sys.argv and not is_pypy and not os.environ.get('CASS_DRIVER_NO_LIBEV') try_cython = try_extensions and "--no-cython" not in sys.argv and not is_pypy and not os.environ.get('CASS_DRIVER_NO_CYTHON') -try_cython &= 'egg_info' not in sys.argv # bypass setup_requires for pip egg_info calls, which will never have --install-option"--no-cython" coming fomr pip - sys.argv = [a for a in sys.argv if a not in ("--no-murmur3", "--no-libev", "--no-cython", "--no-extensions")] build_concurrency = int(os.environ.get('CASS_DRIVER_BUILD_CONCURRENCY', '0')) @@ -358,80 +355,12 @@ def fix_extension_class(ext: Extension) -> Extension: return ext -def pre_build_check(): - """ - Try to verify build tools - """ - if os.environ.get('CASS_DRIVER_NO_PRE_BUILD_CHECK'): - return True - - try: - from setuptools._distutils.ccompiler import new_compiler - from setuptools._distutils.sysconfig import customize_compiler - from setuptools.dist import Distribution - - # base build_ext just to emulate compiler option setup - be = build_ext(Distribution()) - be.initialize_options() - be.finalize_options() - - # First, make sure we have a Python include directory - have_python_include = any(os.path.isfile(os.path.join(p, 'Python.h')) for p in be.include_dirs) - if not have_python_include: - sys.stderr.write("Did not find 'Python.h' in %s.\n" % (be.include_dirs,)) - return False - - compiler = new_compiler(compiler=be.compiler) - customize_compiler(compiler) - - try: - # We must be able to initialize the compiler if it has that method - if hasattr(compiler, "initialize"): - compiler.initialize() - except: - return False - - executables = [] - if compiler.compiler_type in ('unix', 'cygwin'): - executables = [compiler.executables[exe][0] for exe in ('compiler_so', 'linker_so')] - elif compiler.compiler_type == 'nt': - executables = [getattr(compiler, exe) for exe in ('cc', 'linker')] - - if executables: - for exe in executables: - if not shutil.which(exe): - sys.stderr.write("Failed to find %s for compiler type %s.\n" % (exe, compiler.compiler_type)) - return False - - except Exception as exc: - sys.stderr.write('%s\n' % str(exc)) - sys.stderr.write("Failed pre-build check. Attempting anyway.\n") - - # if we are unable to positively id the compiler type, or one of these assumptions fails, - # just proceed as we would have without the check - return True - - def run_setup(extensions): kw = {'cmdclass': {'doc': DocCommand}} kw['cmdclass']['build_ext'] = build_extensions kw['ext_modules'] = [Extension('DUMMY', [])] # dummy extension makes sure build_ext is called for install - if try_cython: - # precheck compiler before adding to setup_requires - # we don't actually negate try_cython because: - # 1.) build_ext eats errors at compile time, letting the install complete while producing useful feedback - # 2.) there could be a case where the python environment has cython installed but the system doesn't have build tools - if pre_build_check(): - cython_dep = 'Cython>=3.0.11,<4' - user_specified_cython_version = os.environ.get('CASS_DRIVER_ALLOWED_CYTHON_VERSION') - if user_specified_cython_version is not None: - cython_dep = 'Cython==%s' % (user_specified_cython_version,) - kw['setup_requires'] = [cython_dep] - else: - sys.stderr.write("Bypassing Cython setup requirement\n") - setup(**kw) run_setup(None) From 29ac4e10aadb1707f7629be0b9913bc8d26290c1 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 22 Feb 2026 17:33:37 -0400 Subject: [PATCH 216/298] Fix RecursionError in execute_concurrent on synchronous errbacks When a ResponseFuture already has _final_exception set by the time add_callbacks() is called (e.g. the request timeout expired synchronously inside send_request()), the errback fires inline creating an unbounded recursion: _execute -> add_callbacks -> _on_error -> _put_result -> _execute_next -> _execute -> ... The existing _exec_depth guard only protected the except-Exception path (when execute_async itself raises), not this synchronous-callback path. Replace the depth counter with a re-entrancy guard: when _execute is re-entered from a synchronous callback, the work is appended to a pending list and drained iteratively by the outermost invocation. Fixes: #712 --- cassandra/concurrent.py | 46 +++++++++++++++++++------------- tests/unit/test_concurrent.py | 49 +++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 18 deletions(-) diff --git a/cassandra/concurrent.py b/cassandra/concurrent.py index d6345ca452..b96d0b12d4 100644 --- a/cassandra/concurrent.py +++ b/cassandra/concurrent.py @@ -92,8 +92,6 @@ def execute_concurrent(session, statements_and_parameters, concurrency=100, rais class _ConcurrentExecutor(object): - max_error_recursion = 100 - def __init__(self, session, statements_and_params, execution_profile): self.session = session self._enum_statements = enumerate(iter(statements_and_params)) @@ -103,7 +101,7 @@ def __init__(self, session, statements_and_params, execution_profile): self._results_queue = [] self._current = 0 self._exec_count = 0 - self._exec_depth = 0 + self._executing = False def execute(self, concurrency, fail_fast): self._fail_fast = fail_fast @@ -127,22 +125,34 @@ def _execute_next(self): pass def _execute(self, idx, statement, params): - self._exec_depth += 1 + # When execute_async completes synchronously (e.g. immediate timeout), + # the errback fires inline: _on_error -> _put_result -> _execute_next + # -> _execute. Without protection this recurses once per remaining + # statement and blows the stack. + # + # ``_executing`` marks that we are already inside this method higher up + # the call stack. When a synchronous callback re-enters, we just stash + # the pending work in ``_pending_executions`` and let the outermost + # invocation drain it in a loop -- no recursion. + if self._executing: + self._pending_executions.append((idx, statement, params)) + return + + self._executing = True + self._pending_executions = [(idx, statement, params)] try: - future = self.session.execute_async(statement, params, timeout=None, execution_profile=self._execution_profile) - args = (future, idx) - future.add_callbacks( - callback=self._on_success, callback_args=args, - errback=self._on_error, errback_args=args) - except Exception as exc: - # If we're not failing fast and all executions are raising, there is a chance of recursing - # here as subsequent requests are attempted. If we hit this threshold, schedule this result/retry - # and let the event loop thread return. - if self._exec_depth < self.max_error_recursion: - self._put_result(exc, idx, False) - else: - self.session.submit(self._put_result, exc, idx, False) - self._exec_depth -= 1 + while self._pending_executions: + p_idx, p_statement, p_params = self._pending_executions.pop(0) + try: + future = self.session.execute_async(p_statement, p_params, timeout=None, execution_profile=self._execution_profile) + args = (future, p_idx) + future.add_callbacks( + callback=self._on_success, callback_args=args, + errback=self._on_error, errback_args=args) + except Exception as exc: + self._put_result(exc, p_idx, False) + finally: + self._executing = False def _on_success(self, result, future, idx): future.clear_callbacks() diff --git a/tests/unit/test_concurrent.py b/tests/unit/test_concurrent.py index 9c85b1ccac..d3888aa9de 100644 --- a/tests/unit/test_concurrent.py +++ b/tests/unit/test_concurrent.py @@ -258,3 +258,52 @@ def test_recursion_limited(self): for r in results: assert not r[0] assert isinstance(r[1], TypeError) + + def test_no_recursion_on_synchronous_errback(self): + """ + Verify that execute_concurrent does not blow the stack when every + future completes with an error *before* add_callbacks is called + (i.e. the errback fires synchronously inside add_callbacks). + + This exercises a different code path from test_recursion_limited: + that test covers execute_async raising an exception, while this one + covers execute_async returning a future whose errback fires inline. + """ + count = sys.getrecursionlimit() + error = Exception("immediate failure") + + class AlreadyFailedFuture: + """A future that already has _final_exception set.""" + _query_trace = None + _col_names = None + _col_types = None + has_more_pages = False + + def add_callback(self, fn, *args, **kwargs): + pass + + def add_errback(self, fn, *args, **kwargs): + # Fire errback synchronously, mimicking a future that + # completed before add_callbacks was called. + fn(error, *args, **kwargs) + + def add_callbacks(self, callback, errback, + callback_args=(), callback_kwargs=None, + errback_args=(), errback_kwargs=None): + self.add_callback(callback, *callback_args, **(callback_kwargs or {})) + self.add_errback(errback, *errback_args, **(errback_kwargs or {})) + + def clear_callbacks(self): + pass + + mock_session = Mock() + mock_session.execute_async.return_value = AlreadyFailedFuture() + + statements_and_params = [("SELECT 1", ())] * count + results = execute_concurrent(mock_session, statements_and_params, + raise_on_first_error=False) + + assert len(results) == count + for success, result in results: + assert not success + assert result is error From 9de3793249b326146ba33ce728540321b5e6a3af Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 22 Feb 2026 17:36:12 -0400 Subject: [PATCH 217/298] Fix floating-point precision loss for timestamps far from epoch Replace float arithmetic (1e3, /1000.0) with integer arithmetic (1000, //1000) in DateType serialize/deserialize and encoder to prevent millisecond precision loss for dates far from the Unix epoch (e.g. year 2300+). Add datetime_from_ms_timestamp() that operates on integer milliseconds directly, avoiding the lossy float-seconds intermediate representation. The Cython variant uses the fast timedelta_new() C API to avoid performance regression on the hot deserialization path. Fixes GH-532 --- cassandra/cqltypes.py | 10 +++++----- cassandra/cython_utils.pxd | 1 + cassandra/cython_utils.pyx | 19 +++++++++++++++++++ cassandra/deserializers.pyx | 6 +++--- cassandra/encoder.py | 2 +- cassandra/util.py | 10 ++++++++++ tests/unit/cython/test_utils.py | 4 ++++ tests/unit/cython/utils_testhelper.pyx | 15 ++++++++++++++- tests/unit/test_time_util.py | 13 +++++++++++++ tests/unit/test_types.py | 17 ++++++++++++++++- 10 files changed, 86 insertions(+), 11 deletions(-) diff --git a/cassandra/cqltypes.py b/cassandra/cqltypes.py index e36c48563c..d33e5fceb8 100644 --- a/cassandra/cqltypes.py +++ b/cassandra/cqltypes.py @@ -636,24 +636,24 @@ def interpret_datestring(val): except ValueError: continue # scale seconds to millis for the raw value - return (calendar.timegm(tval) + offset) * 1e3 + return (calendar.timegm(tval) + offset) * 1000 else: raise ValueError("can't interpret %r as a date" % (val,)) @staticmethod def deserialize(byts, protocol_version): - timestamp = int64_unpack(byts) / 1000.0 - return util.datetime_from_timestamp(timestamp) + timestamp_ms = int64_unpack(byts) + return util.datetime_from_ms_timestamp(timestamp_ms) @staticmethod def serialize(v, protocol_version): try: # v is datetime timestamp_seconds = calendar.timegm(v.utctimetuple()) - timestamp = timestamp_seconds * 1e3 + getattr(v, 'microsecond', 0) / 1e3 + timestamp = timestamp_seconds * 1000 + getattr(v, 'microsecond', 0) // 1000 except AttributeError: try: - timestamp = calendar.timegm(v.timetuple()) * 1e3 + timestamp = calendar.timegm(v.timetuple()) * 1000 except AttributeError: # Ints and floats are valid timestamps too if type(v) not in _number_types: diff --git a/cassandra/cython_utils.pxd b/cassandra/cython_utils.pxd index 4a1e71dba5..7469657b04 100644 --- a/cassandra/cython_utils.pxd +++ b/cassandra/cython_utils.pxd @@ -1,2 +1,3 @@ from libc.stdint cimport int64_t cdef datetime_from_timestamp(double timestamp) +cdef datetime_from_ms_timestamp(int64_t timestamp_ms) diff --git a/cassandra/cython_utils.pyx b/cassandra/cython_utils.pyx index 7539f33f31..f3421063da 100644 --- a/cassandra/cython_utils.pyx +++ b/cassandra/cython_utils.pyx @@ -60,3 +60,22 @@ cdef datetime_from_timestamp(double timestamp): microseconds += tmp return DATETIME_EPOC + timedelta_new(days, seconds, microseconds) + + +cdef datetime_from_ms_timestamp(int64_t timestamp_ms): + """ + Creates a datetime from a timestamp in milliseconds using integer + arithmetic to preserve precision for large values. + """ + cdef int64_t total_seconds = timestamp_ms // 1000 + cdef int microseconds = ((timestamp_ms % 1000) * 1000) + # For negative timestamps, ensure microseconds is non-negative + if microseconds < 0: + total_seconds -= 1 + microseconds += 1000000 + cdef int days = (total_seconds // 86400) + cdef int seconds = (total_seconds % 86400) + if seconds < 0: + days -= 1 + seconds += 86400 + return DATETIME_EPOC + timedelta_new(days, seconds, microseconds) diff --git a/cassandra/deserializers.pyx b/cassandra/deserializers.pyx index 97d249d02f..98e8676bbc 100644 --- a/cassandra/deserializers.pyx +++ b/cassandra/deserializers.pyx @@ -17,7 +17,7 @@ from libc.stdint cimport int32_t, uint16_t include 'cython_marshal.pyx' from cassandra.buffer cimport Buffer, to_bytes, slice_buffer -from cassandra.cython_utils cimport datetime_from_timestamp +from cassandra.cython_utils cimport datetime_from_timestamp, datetime_from_ms_timestamp from cython.view cimport array as cython_array from cassandra.tuple cimport tuple_new, tuple_set @@ -135,8 +135,8 @@ cdef class DesCounterColumnType(DesLongType): cdef class DesDateType(Deserializer): cdef deserialize(self, Buffer *buf, int protocol_version): - cdef double timestamp = unpack_num[int64_t](buf) / 1000.0 - return datetime_from_timestamp(timestamp) + cdef int64_t timestamp_ms = unpack_num[int64_t](buf) + return datetime_from_ms_timestamp(timestamp_ms) cdef class TimestampType(DesDateType): diff --git a/cassandra/encoder.py b/cassandra/encoder.py index e834550fd3..d803c087ba 100644 --- a/cassandra/encoder.py +++ b/cassandra/encoder.py @@ -142,7 +142,7 @@ def cql_encode_datetime(self, val): with millisecond precision. """ timestamp = calendar.timegm(val.utctimetuple()) - return str(int(timestamp * 1e3 + getattr(val, 'microsecond', 0) / 1e3)) + return str(timestamp * 1000 + getattr(val, 'microsecond', 0) // 1000) def cql_encode_date(self, val): """ diff --git a/cassandra/util.py b/cassandra/util.py index 12886d05ab..593c264033 100644 --- a/cassandra/util.py +++ b/cassandra/util.py @@ -62,6 +62,16 @@ def datetime_from_timestamp(timestamp): return dt +def datetime_from_ms_timestamp(timestamp_ms): + """ + Creates a timezone-agnostic datetime from a timestamp in milliseconds, + using integer arithmetic to preserve precision for large values. + + :param timestamp_ms: a unix timestamp, in milliseconds (integer) + """ + return DATETIME_EPOC + datetime.timedelta(milliseconds=timestamp_ms) + + def utc_datetime_from_ms_timestamp(timestamp): """ Creates a UTC datetime from a timestamp in milliseconds. See diff --git a/tests/unit/cython/test_utils.py b/tests/unit/cython/test_utils.py index 02e4f0590f..b9b5cea554 100644 --- a/tests/unit/cython/test_utils.py +++ b/tests/unit/cython/test_utils.py @@ -24,3 +24,7 @@ class UtilsTest(unittest.TestCase): @cythontest def test_datetime_from_timestamp(self): utils_testhelper.test_datetime_from_timestamp() + + @cythontest + def test_datetime_from_ms_timestamp(self): + utils_testhelper.test_datetime_from_ms_timestamp() diff --git a/tests/unit/cython/utils_testhelper.pyx b/tests/unit/cython/utils_testhelper.pyx index e997caa89e..cf05c1806e 100644 --- a/tests/unit/cython/utils_testhelper.pyx +++ b/tests/unit/cython/utils_testhelper.pyx @@ -14,10 +14,23 @@ import datetime -from cassandra.cython_utils cimport datetime_from_timestamp +from cassandra.cython_utils cimport datetime_from_timestamp, datetime_from_ms_timestamp def test_datetime_from_timestamp(): assert datetime_from_timestamp(1454781157.123456) == datetime.datetime(2016, 2, 6, 17, 52, 37, 123456) # PYTHON-452 assert datetime_from_timestamp(2177403010.123456) == datetime.datetime(2038, 12, 31, 10, 10, 10, 123456) + + +def test_datetime_from_ms_timestamp(): + # epoch + assert datetime_from_ms_timestamp(0) == datetime.datetime(1970, 1, 1) + # positive with millisecond precision + assert datetime_from_ms_timestamp(1454781157123) == datetime.datetime(2016, 2, 6, 17, 52, 37, 123000) + # large positive far from epoch (GH-532) + assert datetime_from_ms_timestamp(10413792000001) == datetime.datetime(2300, 1, 1, 0, 0, 0, 1000) + # negative timestamp + assert datetime_from_ms_timestamp(-770172256000) == datetime.datetime(1945, 8, 5, 23, 15, 44) + # large negative with millisecond precision + assert datetime_from_ms_timestamp(-11676095999999) == datetime.datetime(1600, 1, 1, 0, 0, 0, 1000) diff --git a/tests/unit/test_time_util.py b/tests/unit/test_time_util.py index 6c2c46d180..d87a3fe2ad 100644 --- a/tests/unit/test_time_util.py +++ b/tests/unit/test_time_util.py @@ -36,6 +36,19 @@ def test_datetime_from_timestamp(self): assert util.datetime_from_timestamp(2177403010.123456) == datetime.datetime(2038, 12, 31, 10, 10, 10, 123456) + def test_datetime_from_ms_timestamp(self): + # epoch + assert util.datetime_from_ms_timestamp(0) == datetime.datetime(1970, 1, 1) + # positive with millisecond precision + assert util.datetime_from_ms_timestamp(1000) == datetime.datetime(1970, 1, 1, 0, 0, 1) + assert util.datetime_from_ms_timestamp(1454781157123) == datetime.datetime(2016, 2, 6, 17, 52, 37, 123000) + # large positive far from epoch (GH-532) - must not lose precision + assert util.datetime_from_ms_timestamp(10413792000001) == datetime.datetime(2300, 1, 1, 0, 0, 0, 1000) + # negative timestamp + assert util.datetime_from_ms_timestamp(-770172256000) == datetime.datetime(1945, 8, 5, 23, 15, 44) + # large negative with millisecond precision + assert util.datetime_from_ms_timestamp(-11676095999999) == datetime.datetime(1600, 1, 1, 0, 0, 0, 1000) + def test_times_from_uuid1(self): node = uuid.getnode() now = time.time() diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index a5bd028b26..7a8c584f75 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -24,7 +24,7 @@ from cassandra.cqltypes import ( CassandraType, DateRangeType, DateType, DecimalType, EmptyValue, LongType, SetType, UTF8Type, - cql_typename, int8_pack, int64_pack, lookup_casstype, + cql_typename, int8_pack, int64_pack, int64_unpack, lookup_casstype, lookup_casstype_simple, parse_casstype_args, int32_pack, Int32Type, ListType, MapType, VectorType, FloatType @@ -241,6 +241,21 @@ def test_datetype(self): expected = 2177403010.123 assert DateType.deserialize(int64_pack(int(1000 * expected)), 0) == datetime.datetime(2038, 12, 31, 10, 10, 10, 123000, tzinfo=datetime.timezone.utc).replace(tzinfo=None) + # Large timestamp precision (GH-532) - timestamps far from epoch must + # not lose precision due to floating-point conversions. + # 2300-01-01 00:00:00.001 UTC + ts_ms = 10413792000001 + deserialized = DateType.deserialize(int64_pack(ts_ms), 0) + assert deserialized == datetime.datetime(2300, 1, 1, 0, 0, 0, 1000) + # Round-trip: serialize the deserialized datetime back to milliseconds + assert int64_unpack(DateType.serialize(deserialized, 0)) == ts_ms + + # Negative large timestamp: 1600-01-01 00:00:00.001 UTC + ts_ms_neg = -11676096000000 + 1 # -11676095999999 + deserialized_neg = DateType.deserialize(int64_pack(ts_ms_neg), 0) + assert deserialized_neg == datetime.datetime(1600, 1, 1, 0, 0, 0, 1000) + assert int64_unpack(DateType.serialize(deserialized_neg, 0)) == ts_ms_neg + def test_collection_null_support(self): """ Test that null values in collection are decoded properly. From 1ec7f660d1875f6bc5d76c265a7a84a2ccd8f926 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:45:19 +0000 Subject: [PATCH 218/298] chore(deps): update dependency hatchling to v1.29.0 --- docs/pyproject.toml | 4 ++-- docs/uv.lock | 19 ++++--------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 460e6a5609..59c425229a 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -24,14 +24,14 @@ dependencies = [ [dependency-groups] # Add any dev-only tools here; example shown -dev = ["hatchling==1.28.0"] +dev = ["hatchling==1.29.0"] [tool.uv.sources] # Keep the driver editable from the parent directory scylla-driver = { path = "../", editable = true } [build-system] -requires = ["hatchling==1.28.0"] +requires = ["hatchling==1.29.0"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] diff --git a/docs/uv.lock b/docs/uv.lock index 7c04fda41e..d6b5359d21 100644 --- a/docs/uv.lock +++ b/docs/uv.lock @@ -351,7 +351,7 @@ wheels = [ [[package]] name = "hatchling" -version = "1.28.0" +version = "1.29.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, @@ -359,9 +359,9 @@ dependencies = [ { name = "pluggy" }, { name = "trove-classifiers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/8e/e480359492affde4119a131da729dd26da742c2c9b604dff74836e47eef9/hatchling-1.28.0.tar.gz", hash = "sha256:4d50b02aece6892b8cd0b3ce6c82cb218594d3ec5836dbde75bf41a21ab004c8", size = 55365, upload-time = "2025-11-27T00:31:13.766Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/9c/b4cfe330cd4f49cff17fd771154730555fa4123beb7f292cf0098b4e6c20/hatchling-1.29.0.tar.gz", hash = "sha256:793c31816d952cee405b83488ce001c719f325d9cda69f1fc4cd750527640ea6", size = 55656, upload-time = "2026-02-23T19:42:06.539Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/a5/48cb7efb8b4718b1a4c0c331e3364a3a33f614ff0d6afd2b93ee883d3c47/hatchling-1.28.0-py3-none-any.whl", hash = "sha256:dc48722b68b3f4bbfa3ff618ca07cdea6750e7d03481289ffa8be1521d18a961", size = 76075, upload-time = "2025-11-27T00:31:12.544Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/44032265776062a89171285ede55a0bdaadc8ac00f27f0512a71a9e3e1c8/hatchling-1.29.0-py3-none-any.whl", hash = "sha256:50af9343281f34785fab12da82e445ed987a6efb34fd8c2fc0f6e6630dbcc1b0", size = 76356, upload-time = "2026-02-23T19:42:05.197Z" }, ] [[package]] @@ -632,7 +632,6 @@ dependencies = [ { name = "pygments" }, { name = "recommonmark" }, { name = "redirects-cli" }, - { name = "scales" }, { name = "six" }, { name = "sphinx" }, { name = "sphinx-autobuild" }, @@ -655,7 +654,6 @@ requires-dist = [ { name = "pygments", specifier = ">=2.19.2,<3.0.0" }, { name = "recommonmark", specifier = "==0.7.1" }, { name = "redirects-cli", specifier = "~=0.1.3" }, - { name = "scales", specifier = ">=1.0.9,<2.0.0" }, { name = "six", specifier = ">=1.9" }, { name = "sphinx", specifier = ">=8.2.3,<9.0.0" }, { name = "sphinx-autobuild", specifier = ">=2025.0.0,<2026.0.0" }, @@ -666,7 +664,7 @@ requires-dist = [ ] [package.metadata.requires-dev] -dev = [{ name = "hatchling", specifier = "==1.28.0" }] +dev = [{ name = "hatchling", specifier = "==1.29.0" }] [[package]] name = "pyyaml" @@ -750,15 +748,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, ] -[[package]] -name = "scales" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/85/b4a3933f227889b536a76c7ed5a0708ae5f63fe20f81d09a725228349e81/scales-1.0.9.tar.gz", hash = "sha256:8b6930f7d4bf115192290b44c757af5e254e3fcfcb75ff9a51f5c96a404e2753", size = 21889, upload-time = "2015-02-28T18:49:39.538Z" } - [[package]] name = "setuptools" version = "80.9.0" From 9c53d78788f36f5723f3286a9f777acee782024a Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 6 Mar 2026 10:53:06 +0200 Subject: [PATCH 219/298] (improvement) cluster: cache parsed tablet routing type in ResponseFuture The _set_result() method re-parses the same complex TupleType string on every query result that includes tablet routing info. Cache the parsed type as a class attribute on ResponseFuture so lookup_casstype() is only called once for the lifetime of the process. --- cassandra/cluster.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 622b706330..51d0b2d88b 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -4345,6 +4345,7 @@ class ResponseFuture(object): _spec_execution_plan = NoSpeculativeExecutionPlan() _continuous_paging_session = None _host = None + _TABLET_ROUTING_CTYPE = None _warned_timeout = False @@ -4642,7 +4643,10 @@ def _set_result(self, host, connection, pool, response): if self._custom_payload and self.session.cluster.control_connection._tablets_routing_v1 and 'tablets-routing-v1' in self._custom_payload: protocol = self.session.cluster.protocol_version info = self._custom_payload.get('tablets-routing-v1') - ctype = types.lookup_casstype('TupleType(LongType, LongType, ListType(TupleType(UUIDType, Int32Type)))') + ctype = ResponseFuture._TABLET_ROUTING_CTYPE + if ctype is None: + ctype = types.lookup_casstype('TupleType(LongType, LongType, ListType(TupleType(UUIDType, Int32Type)))') + ResponseFuture._TABLET_ROUTING_CTYPE = ctype tablet_routing_info = ctype.from_binary(info, protocol) first_token = tablet_routing_info[0] last_token = tablet_routing_info[1] From 9aa5ddceaae9fad29ddd778016d4913f77e612ac Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Thu, 12 Mar 2026 19:04:16 +0100 Subject: [PATCH 220/298] Add optional query_params parameter to QueryMessage --- cassandra/protocol.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cassandra/protocol.py b/cassandra/protocol.py index f37633a756..4628c7ee0e 100644 --- a/cassandra/protocol.py +++ b/cassandra/protocol.py @@ -611,9 +611,10 @@ class QueryMessage(_QueryMessage): name = 'QUERY' def __init__(self, query, consistency_level, serial_consistency_level=None, - fetch_size=None, paging_state=None, timestamp=None, continuous_paging_options=None, keyspace=None): + fetch_size=None, paging_state=None, timestamp=None, continuous_paging_options=None, keyspace=None, + query_params=None): self.query = query - super(QueryMessage, self).__init__(None, consistency_level, serial_consistency_level, fetch_size, + super(QueryMessage, self).__init__(query_params, consistency_level, serial_consistency_level, fetch_size, paging_state, timestamp, False, continuous_paging_options, keyspace) def send_body(self, f, protocol_version): From 8bba6ebd361e4df959cc4f02dcbcb67201c6a526 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Fri, 13 Mar 2026 08:10:59 +0100 Subject: [PATCH 221/298] Introduce skip_scylla_version_lt for integration tests --- tests/integration/__init__.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index dfac2dc1d9..a53e7aafa6 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -715,6 +715,27 @@ def xfail_scylla_version_lt(reason, oss_scylla_version, ent_scylla_version, *arg return pytest.mark.xfail(current_version < Version(oss_scylla_version), reason=reason, *args, **kwargs) + +def skip_scylla_version_lt(reason, scylla_version): + """ + Skip tests on scylla versions older than the specified thresholds. + :param reason: message explaining why the test is skipped + :param scylla_version: str, version from which test supposed to work + """ + if not (reason.startswith("scylladb/scylladb#") or reason.startswith("scylladb/scylla-enterprise#")): + raise ValueError('reason should start with scylladb/scylladb# or scylladb/scylla-enterprise# to reference issue in scylla repo') + + if not isinstance(scylla_version, str): + raise ValueError('scylla_version should be a str') + + if SCYLLA_VERSION is None: + return pytest.mark.skipif(False, reason="It is just a NoOP Decor, should not skip anything") + + current_version = Version(get_scylla_version(SCYLLA_VERSION)) + + return pytest.mark.skipif(current_version < Version(scylla_version), reason=reason) + + class UpDownWaiter(object): def __init__(self, host): From bc864c1b4e7e030c22aef539b622865e5e0fea95 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Tue, 17 Mar 2026 10:31:29 +0100 Subject: [PATCH 222/298] Add client routes data types and route store Introduce the data layer for Private Link client routes support: - ClientRoutesChangeType enum for CLIENT_ROUTES_CHANGE event types - ClientRouteProxy dataclass and ClientRoutesConfig for user-facing configuration - _Route frozen dataclass for immutable route records - _RouteStore for thread-safe route storage with atomic update/merge and preferred route selection that avoids unnecessary connection_id migration when multiple routes exist for the same host --- cassandra/client_routes.py | 192 +++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 cassandra/client_routes.py diff --git a/cassandra/client_routes.py b/cassandra/client_routes.py new file mode 100644 index 0000000000..f26aeef152 --- /dev/null +++ b/cassandra/client_routes.py @@ -0,0 +1,192 @@ +# Copyright 2026 ScyllaDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Client Routes support for Private Link and similar network configurations. + +This module implements support for dynamic address translation via the +system.client_routes table and CLIENT_ROUTES_CHANGE events. +""" + +from __future__ import absolute_import + +from dataclasses import dataclass +import enum +import logging +import threading +import uuid +from typing import Dict, List, Optional, Set + +log = logging.getLogger(__name__) + + +class ClientRoutesChangeType(enum.Enum): + """ + Types of CLIENT_ROUTES_CHANGE events. + + Currently the protocol defines only UPDATE_NODES. + New variants will be added here if the protocol is extended. + """ + UPDATE_NODES = "UPDATE_NODES" + + +@dataclass +class ClientRouteProxy: + """ + :param connection_id: String identifying the connection (required) + :param connection_addr_override:: Optional string address for initial connection + """ + + connection_id: str + connection_addr_override: Optional[str] = None + + def __post_init__(self): + if self.connection_id is None: + raise ValueError("connection_id is required") + +class ClientRoutesConfig: + """ + Configuration for client routes (Private Link support). + + :param proxies: List of :class:`ClientRouteProxy` objects + (REQUIRED, at least one) + :param advanced_shard_awareness: Whether to enable advanced shard awareness + (default: ``False``) + """ + + proxies: List[ClientRouteProxy] + advanced_shard_awareness: bool + + def __init__(self, proxies: List[ClientRouteProxy], advanced_shard_awareness: bool = False): + """ + :param proxies: List of ClientRouteProxy objects + :param advanced_shard_awareness: Enable advanced shard awareness (default False) + """ + if not proxies: + raise ValueError("At least one proxy must be specified") + + if not isinstance(proxies, (list, tuple)): + raise TypeError("proxies must be a list or tuple") + + for proxy in proxies: + if not isinstance(proxy, ClientRouteProxy): + raise TypeError("All proxies must be ClientRouteProxy instances") + + self.proxies = proxies + self.advanced_shard_awareness = advanced_shard_awareness + + def __repr__(self) -> str: + return (f"ClientRoutesConfig(proxies={self.proxies}, " + f"advanced_shard_awareness={self.advanced_shard_awareness})") + + +@dataclass(frozen=True) +class _Route: + connection_id: str + host_id: uuid.UUID + address: str # ipv4, ipv6 or DNS hostname from system.client_routes + port: int + +class _RouteStore: + """ + Thread-safe storage for routes. Reads are safe under CPython's GIL; + writes are serialized with a lock. + + This uses atomic pointer swaps for updates, allowing lock-free reads + while serializing writes. + """ + + _routes_by_host_id: Dict[uuid.UUID, _Route] + _lock: threading.Lock + + def __init__(self) -> None: + self._routes_by_host_id = {} + self._lock = threading.Lock() + + def get_by_host_id(self, host_id: uuid.UUID) -> Optional[_Route]: + """ + Get route for a host ID (lock-free read). + + :param host_id: UUID of the host + :return: _Route or None + """ + return self._routes_by_host_id.get(host_id) + + def get_all(self) -> List[_Route]: + """ + Get all routes as a list (lock-free read). + + :return: List of _Route + """ + return list(self._routes_by_host_id.values()) + + def _select_preferred_routes(self, new_routes: List[_Route]) -> List[_Route]: + """ + When multiple routes exist for the same host_id (different connection_ids), + prefer the connection_id already in use. Only migrate to a different + connection_id when the previously used one is no longer available. + + Must be called under self._lock. + """ + by_host: Dict[uuid.UUID, List[_Route]] = {} + for route in new_routes: + by_host.setdefault(route.host_id, []).append(route) + + selected = [] + for host_id, candidates in by_host.items(): + if len(candidates) == 1: + selected.append(candidates[0]) + continue + + existing = self._routes_by_host_id.get(host_id) + if existing: + preferred = [c for c in candidates if c.connection_id == existing.connection_id] + if preferred: + selected.append(preferred[0]) + continue + + selected.append(candidates[0]) + + return selected + + def update(self, routes: List[_Route]) -> None: + """ + Replace all routes atomically. + + :param routes: List of _Route objects + """ + with self._lock: + preferred = self._select_preferred_routes(routes) + self._routes_by_host_id = {route.host_id: route for route in preferred} + + def merge(self, new_routes: List[_Route], affected_host_ids: Set[uuid.UUID]) -> None: + """ + Merge new routes with existing ones atomically. + + Routes for affected_host_ids are replaced entirely: existing routes + for those hosts are dropped and replaced with whatever is in new_routes. + This handles deletions from system.client_routes (affected host present + but no new route for it). + + :param new_routes: List of _Route objects to merge + :param affected_host_ids: Set of host IDs affected by the change. + """ + with self._lock: + preferred = self._select_preferred_routes(new_routes) + new_by_host = {r.host_id: r for r in preferred} + + updated = {hid: r for hid, r in self._routes_by_host_id.items() + if hid not in affected_host_ids} + updated.update(new_by_host) + self._routes_by_host_id = updated From ac91295c85a8d176254d33bf79a62760ebf9056d Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Tue, 17 Mar 2026 10:32:17 +0100 Subject: [PATCH 223/298] Add client routes handler for Private Link support Add _ClientRoutesHandler which manages the full lifecycle of dynamic address translation via system.client_routes: - initialize(): loads all routes at startup and on control connection reconnect - handle_client_routes_change(): processes CLIENT_ROUTES_CHANGE events with targeted merge or full refresh depending on event data - _query_all_routes_for_connections(): complete refresh query using connection_id IN (...) - _query_routes_for_change_event(): targeted query grouping by connection_id with host_id IN (...) per group - _execute_routes_query(): common query execution and result parsing with proxy address override support - resolve_host(): host_id to (address, port) resolution with DNS lookup --- cassandra/client_routes.py | 261 ++++++++++++++++++++++++++++++++++++- 1 file changed, 260 insertions(+), 1 deletion(-) diff --git a/cassandra/client_routes.py b/cassandra/client_routes.py index f26aeef152..80b2477a6d 100644 --- a/cassandra/client_routes.py +++ b/cassandra/client_routes.py @@ -24,9 +24,17 @@ from dataclasses import dataclass import enum import logging +import socket import threading import uuid -from typing import Dict, List, Optional, Set +from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Set, Tuple + +from cassandra import ConsistencyLevel +from cassandra.protocol import QueryMessage +from cassandra.query import dict_factory + +if TYPE_CHECKING: + from cassandra.connection import Connection log = logging.getLogger(__name__) @@ -190,3 +198,254 @@ def merge(self, new_routes: List[_Route], affected_host_ids: Set[uuid.UUID]) -> if hid not in affected_host_ids} updated.update(new_by_host) self._routes_by_host_id = updated + + +class _ClientRoutesHandler: + """ + Handles dynamic address translation for Private Link via system.client_routes. + + Lifecycle: + 1. Construction: Create with configuration + 2. Initialization: Read system.client_routes after control connection established + 3. Steady state: Listen for CLIENT_ROUTES_CHANGE events and update routes + 4. Translation: Translate addresses using Host ID lookup + """ + + config: 'ClientRoutesConfig' + ssl_enabled: bool + _routes: _RouteStore + _connection_ids: Set[str] + _proxy_addresses_override: Dict[str, str] + + def __init__(self, config: 'ClientRoutesConfig', ssl_enabled: bool = False): + """ + :param config: ClientRoutesConfig instance + :param ssl_enabled: Whether TLS is enabled (determines port selection) + """ + if not isinstance(config, ClientRoutesConfig): + raise TypeError("config must be a ClientRoutesConfig instance") + + self.config = config + self.ssl_enabled = ssl_enabled + self._routes = _RouteStore() + self._connection_ids = {dep.connection_id for dep in config.proxies} + # Precalculate proxy address mappings for efficient lookup + self._proxy_addresses_override = { + proxy.connection_id: proxy.connection_addr_override + for proxy in config.proxies + if proxy.connection_addr_override + } + + def initialize(self, connection: 'Connection', timeout: float) -> None: + """ + Load all routes from system.client_routes. + + Called once at startup and again whenever the control connection + is re-established. Reads all configured connection IDs and + replaces the in-memory route store atomically. + + Raises on failure so the caller can decide how to react (e.g. + abort startup or schedule a reconnect). + + :param connection: The Connection instance to execute queries on + :param timeout: Query timeout in seconds + """ + log.info("[client routes] Loading routes for %d proxies", len(self.config.proxies)) + + routes = self._query_all_routes_for_connections(connection, timeout, self._connection_ids) + self._routes.update(routes) + + def handle_client_routes_change(self, connection: 'Connection', timeout: float, + change_type: 'ClientRoutesChangeType', + connection_ids: Sequence[str], host_ids: Sequence[str]) -> None: + """ + Handle CLIENT_ROUTES_CHANGE event. + + Currently the protocol defines only :attr:`ClientRoutesChangeType.UPDATE_NODES`. + New variants will be added to the enum if the protocol is extended. + + :param connection: The Connection instance to execute queries on + :param timeout: Query timeout in seconds + :param change_type: A :class:`ClientRoutesChangeType` value + :param connection_ids: Affected connection ID strings; empty means all. + :param host_ids: Affected host ID strings; empty means all. + """ + + full_refresh = False + if not connection_ids or not host_ids: + log.warning( + "[client routes] CLIENT_ROUTES_CHANGE has no connection_ids or host_ids, doing full refresh") + full_refresh = True + elif len(connection_ids) != len(host_ids): + log.warning("[client routes] CLIENT_ROUTES_CHANGE has mismatched lengths (conn: %d, host: %d), doing full refresh", + len(connection_ids), len(host_ids)) + full_refresh = True + + if full_refresh: + routes = self._query_all_routes_for_connections(connection, timeout, self._connection_ids) + self._routes.update(routes) + return + + host_uuids = [uuid.UUID(hid) for hid in host_ids] + pairs = [(cid, hid) for cid, hid in zip(connection_ids, host_uuids) + if cid in self._connection_ids] + + if not pairs: + return + + routes = self._query_routes_for_change_event(connection, timeout, pairs) + self._routes.merge(routes, affected_host_ids=set(host_uuids)) + + def _query_all_routes_for_connections(self, connection: 'Connection', timeout: float, + connection_ids: Set[str]) -> List[_Route]: + """ + Query all routes for the given connection IDs (complete refresh). + + Used when control connection reconnects or as a fallback when + CLIENT_ROUTES_CHANGE event has malformed data. + + :param connection: Connection to execute query on + :param timeout: Query timeout in seconds + :param connection_ids: Set of connection ID strings + :return: List of _Route + """ + if not connection_ids: + return [] + + placeholders = ', '.join('?' for _ in connection_ids) + query = f"SELECT connection_id, host_id, address, port, tls_port FROM system.client_routes WHERE connection_id IN ({placeholders})" + params = [cid.encode('utf-8') for cid in connection_ids] + + log.debug("[client routes] Querying all routes for connection_ids=%s", connection_ids) + return self._execute_routes_query(connection, timeout, query, params) + + def _query_routes_for_change_event(self, connection: 'Connection', timeout: float, + route_pairs: List[Tuple[str, uuid.UUID]]) -> List[_Route]: + """ + Query specific routes affected by a CLIENT_ROUTES_CHANGE event. + + Takes a list of (connection_id, host_id) pairs that represent the exact + routes affected by an operation. This provides precise updates without + fetching unrelated routes. + + If the pairs list is empty or None, falls back to a complete refresh + of all routes for safety. + + :param connection: Connection to execute query on + :param timeout: Query timeout in seconds + :param route_pairs: List of (connection_id, host_id) tuples + :return: List of _Route + """ + unique_pairs = list(dict.fromkeys(route_pairs)) + + conn_ids = list(dict.fromkeys(cid for cid, _ in unique_pairs)) + host_ids = list(dict.fromkeys(hid for _, hid in unique_pairs)) + + log.debug("[client routes] Querying route pairs from CLIENT_ROUTES_CHANGE " + "(first 5 of %d): %s", len(unique_pairs), unique_pairs[:5]) + + conn_ph = ', '.join('?' for _ in conn_ids) + host_ph = ', '.join('?' for _ in host_ids) + query = ( + "SELECT connection_id, host_id, address, port, tls_port " + "FROM system.client_routes " + f"WHERE connection_id IN ({conn_ph}) AND host_id IN ({host_ph})" + ) + params: List = [cid.encode('utf-8') for cid in conn_ids] + params.extend(hid.bytes for hid in host_ids) + + return self._execute_routes_query(connection, timeout, query, params) + + def _execute_routes_query(self, connection: 'Connection', timeout: float, + query: str, params: List) -> List[_Route]: + """ + Execute a routes query and parse results. + + Common helper for both complete refresh and change event queries. + + :param connection: Connection to execute query on + :param timeout: Query timeout in seconds + :param query: CQL query string + :param params: Query parameters + :return: List of _Route + """ + log.debug("[client routes] Executing query: %s with %d parameters", query, len(params)) + + query_msg = QueryMessage(query=query, consistency_level=ConsistencyLevel.ONE, + query_params=params if params else None) + result = connection.wait_for_response( + query_msg, timeout=timeout + ) + + routes = [] + broken = 0 + rows = dict_factory(result.column_names, result.parsed_rows) + for row in rows: + try: + absent = [] + port = row['tls_port'] if self.ssl_enabled else row['port'] + connection_id = row['connection_id'] + host_id = row['host_id'] + address = row['address'] + + if not port: + absent.append("tls_port" if self.ssl_enabled else "port") + if not connection_id: + absent.append("connection_id") + if not host_id: + absent.append("host_id") + if not address: + absent.append("address") + + if absent: + log.error("[client routes] read a route %s, that has no values for the following fields: %s", row, ",".join(absent)) + broken += 1 + continue + + final_address = self._proxy_addresses_override.get(connection_id, address) + + routes.append(_Route( + connection_id=connection_id, + host_id=host_id, + address=final_address, + port=port, + )) + except Exception as e: + log.warning("[client routes] Failed to parse route row: %s", e) + broken += 1 + + if broken and not routes: + raise RuntimeError( + "[client routes] All %d route rows failed validation; " + "refusing to return empty result that would wipe the route store" % broken + ) + + return routes + + def resolve_host(self, host_id: uuid.UUID) -> Optional[Tuple[str, int]]: + """ + Resolve a host_id to an (address, port) pair. + + Looks up the current route and selects the appropriate port. + + :param host_id: Host UUID to resolve + :return: Tuple of (address, port) or None if no route mapping exists + """ + route = self._routes.get_by_host_id(host_id) + if route is None: + return None + + if not route.port: + raise ValueError("Mapping for host %s has no port" % host_id) + + try: + result = socket.getaddrinfo(route.address, route.port, + socket.AF_UNSPEC, socket.SOCK_STREAM) + if not result: + raise socket.gaierror("No addresses found for %s" % route.address) + resolved_ip = result[0][4][0] + return resolved_ip, route.port + except socket.gaierror as e: + log.warning('[client routes] Could not resolve hostname "%s" (host_id=%s): %s', + route.address, host_id, e) + raise From 1e0e6ca1004f372c825fb88f6dc64183e80baa5f Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Tue, 10 Mar 2026 15:45:43 +0100 Subject: [PATCH 224/298] Add ClientRoutesEndPoint and ClientRoutesEndPointFactory - ClientRoutesEndPointFactory: creates endpoints from system.peers rows by extracting host_id, deferring address translation and DNS resolution until connection time - ClientRoutesEndPoint: endpoint that resolves via _ClientRoutesHandler on each connection attempt, ensuring immediate reaction to route changes and CLIENT_ROUTES_CHANGE events --- cassandra/connection.py | 120 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 2 deletions(-) diff --git a/cassandra/connection.py b/cassandra/connection.py index 87f860f32b..72b273ec37 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -25,12 +25,14 @@ from threading import Thread, Event, RLock, Condition import time import ssl +import uuid import weakref import random import itertools -from typing import Optional, Union +from typing import Any, Dict, Optional, Tuple, Union from cassandra.application_info import ApplicationInfoBase +from cassandra.client_routes import _ClientRoutesHandler from cassandra.protocol_features import ProtocolFeatures if 'gevent.monkey' in sys.modules: @@ -230,7 +232,7 @@ class DefaultEndPointFactory(EndPointFactory): port = None """ If no port is discovered in the row, this is the default port - used for endpoint creation. + used for endpoint creation. """ def __init__(self, port=None): @@ -328,6 +330,50 @@ def create_from_sni(self, sni): return SniEndPoint(self._proxy_address, sni, self._port) +class ClientRoutesEndPointFactory(EndPointFactory): + """ + EndPointFactory for Client Routes (Private Link) support. + + Creates ClientRoutesEndPoint instances that defer both address translation + (host_id -> hostname lookup) and DNS resolution until connection time. + This ensures immediate reaction to infrastructure changes. + """ + + client_routes_handler: _ClientRoutesHandler + default_port: int + + def __init__(self, client_routes_handler: _ClientRoutesHandler, default_port: int = None) -> None: + """ + :param client_routes_handler: _ClientRoutesHandler instance to lookup routes + :param default_port: Default port if none found in row + """ + self.client_routes_handler = client_routes_handler + self.default_port = default_port + + def create(self, row: Dict[str, Any]) -> 'ClientRoutesEndPoint': + """ + Create a ClientRoutesEndPoint from a system.peers row. + + Stores only the host_id and handler reference. Both translation + (route lookup) and DNS resolution happen later in resolve(). + """ + from cassandra.metadata import _NodeInfo + host_id = row.get("host_id") + + if host_id is None: + raise ValueError("No host_id to create ClientRoutesEndPoint") + + addr = _NodeInfo.get_broadcast_rpc_address(row) + port = _NodeInfo.get_broadcast_rpc_port(row) or _NodeInfo.get_broadcast_port(row) or self.default_port + + return ClientRoutesEndPoint( + host_id=host_id, + handler=self.client_routes_handler, + original_address=addr, + original_port=port, + ) + + @total_ordering class UnixSocketEndPoint(EndPoint): """ @@ -369,6 +415,76 @@ def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self._unix_socket_path) +@total_ordering +class ClientRoutesEndPoint(EndPoint): + """ + Client Routes (Private Link) EndPoint implementation. + + Defers both address translation (route lookup) and DNS resolution + until resolve() is called at connection time. This ensures immediate + reaction to infrastructure changes and CLIENT_ROUTES_CHANGE events. + """ + + _host_id: uuid.UUID + _handler: _ClientRoutesHandler + _original_address: str + _original_port: int + + def __init__(self, host_id: uuid.UUID, handler: _ClientRoutesHandler, original_address: str, original_port: int = None) -> None: + """ + :param host_id: Host UUID for route lookup + :param handler: _ClientRoutesHandler instance + :param original_address: Original address from system.peers (for identification) + :param original_port: Original port if route doesn't specify one + """ + self._host_id = host_id + self._handler = handler + self._original_address = original_address + self._original_port = original_port + + @property + def address(self) -> str: + """Returns the original address (updated by resolve()).""" + return self._original_address + + @property + def port(self) -> Optional[int]: + return self._original_port + + @property + def host_id(self) -> uuid.UUID: + return self._host_id + + def resolve(self) -> Tuple[str, int]: + """ + Resolve endpoint by delegating to the handler. + Falls back to original address/port if no route mapping is available. + """ + result = self._handler.resolve_host(self._host_id) + if result is None: + return self._original_address, self._original_port + return result + + def __eq__(self, other): + return (isinstance(other, ClientRoutesEndPoint) and + self._host_id == other._host_id and + self._original_address == other._original_address) + + def __hash__(self): + return hash((self._host_id, self._original_address)) + + def __lt__(self, other): + return ((self._host_id, self._original_address) < + (other._host_id, other._original_address)) + + def __str__(self): + return str("%s (host_id=%s)" % (self._original_address, self._host_id)) + + def __repr__(self): + return "<%s: host_id=%s, original_addr=%s>" % ( + self.__class__.__name__, self._host_id, self._original_address) + + class _Frame(object): def __init__(self, version, flags, stream, opcode, body_offset, end_pos): self.version = version From 32023021bb16ada4c8425cf06bdf685f0169aefc Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Tue, 10 Mar 2026 15:49:20 +0100 Subject: [PATCH 225/298] Integrate client routes handler into Cluster and ControlConnection Cluster: - Add client_routes_config parameter with mutual exclusivity check against endpoint_factory - Create _ClientRoutesHandler and ClientRoutesEndPointFactory when client_routes_config is provided ControlConnection: - Register CLIENT_ROUTES_CHANGE event watcher when handler is present - Forward events to handler via _handle_client_routes_change - Trigger full route re-read on control connection reconnection --- cassandra/cluster.py | 103 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 5 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 51d0b2d88b..8da9df6a55 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -29,7 +29,7 @@ from itertools import groupby, count, chain import json import logging -from typing import Optional, Union +from typing import Any, Dict, Optional, Union from warnings import warn from random import random import re @@ -48,7 +48,8 @@ SchemaTargetType, DriverException, ProtocolVersion, UnresolvableContactPoints, DependencyException) from cassandra.auth import _proxy_execute_key, PlainTextAuthProvider -from cassandra.connection import (ConnectionException, ConnectionShutdown, +from cassandra.client_routes import ClientRoutesChangeType, ClientRoutesConfig, _ClientRoutesHandler +from cassandra.connection import (ClientRoutesEndPointFactory, ConnectionException, ConnectionShutdown, ConnectionHeartbeat, ProtocolVersionUnsupported, EndPoint, DefaultEndPoint, DefaultEndPointFactory, SniEndPointFactory, ConnectionBusy, locally_supported_compressions) @@ -1215,7 +1216,8 @@ def __init__(self, shard_aware_options=None, metadata_request_timeout: Optional[float] = None, column_encryption_policy=None, - application_info:Optional[ApplicationInfoBase]=None + application_info:Optional[ApplicationInfoBase]=None, + client_routes_config:Optional[ClientRoutesConfig]=None ): """ ``executor_threads`` defines the number of threads in a pool for handling asynchronous tasks such as @@ -1280,6 +1282,45 @@ def __init__(self, if column_encryption_policy is not None: self.column_encryption_policy = column_encryption_policy + if client_routes_config is not None and endpoint_factory is not None: + raise ValueError("client_routes_config and endpoint_factory are mutually exclusive") + + self._client_routes_handler = None + if client_routes_config is not None: + if not isinstance(client_routes_config, ClientRoutesConfig): + raise TypeError("client_routes_config must be a ClientRoutesConfig instance") + + # SSL hostname verification is incompatible with client routes: + # connections go through NLB proxies whose addresses won't match + # server certificates. + _check_hostname_enabled = False + if ssl_context is not None and ssl_context.check_hostname: + _check_hostname_enabled = True + if ssl_options is not None and ssl_options.get('check_hostname', False): + _check_hostname_enabled = True + if _check_hostname_enabled: + raise ValueError( + "SSL hostname verification (check_hostname=True) is currently incompatible " + "with client_routes_config. When using client routes, connections " + "go through NLB proxies whose addresses won't match server " + "certificates. Disable hostname verification by setting " + "ssl_context.check_hostname = False." + ) + + ssl_enabled = ssl_context is not None or ssl_options is not None + self._client_routes_handler = _ClientRoutesHandler(client_routes_config, ssl_enabled=ssl_enabled) + + if contact_points is _NOT_SET or not self._contact_points_explicit: + seed_addrs = [dep.connection_addr_override for dep in client_routes_config.proxies + if dep.connection_addr_override] + if seed_addrs: + self.contact_points = seed_addrs + self._contact_points_explicit = True + log.info("[client routes] Using %d deployment connection addresses as contact points", + len(seed_addrs)) + + if self._client_routes_handler is not None: + endpoint_factory = ClientRoutesEndPointFactory(self._client_routes_handler, self.port) self.endpoint_factory = endpoint_factory or DefaultEndPointFactory(port=self.port) self.endpoint_factory.configure(self) @@ -1437,6 +1478,10 @@ def __init__(self, self.monitor_reporting_interval = monitor_reporting_interval self.shard_aware_options = ShardAwareOptions(opts=shard_aware_options) + if (client_routes_config is not None + and not client_routes_config.advanced_shard_awareness): + self.shard_aware_options.disable_shardaware_port = True + self._listeners = set() self._listener_lock = Lock() @@ -3612,11 +3657,21 @@ def _try_connect(self, endpoint): # this object (after a dereferencing a weakref) self_weakref = weakref.ref(self, partial(_clear_watcher, weakref.proxy(connection))) try: - connection.register_watchers({ + watchers = { "TOPOLOGY_CHANGE": partial(_watch_callback, self_weakref, '_handle_topology_change'), "STATUS_CHANGE": partial(_watch_callback, self_weakref, '_handle_status_change'), "SCHEMA_CHANGE": partial(_watch_callback, self_weakref, '_handle_schema_change') - }, register_timeout=self._timeout) + } + + if self._cluster._client_routes_handler is not None: + watchers["CLIENT_ROUTES_CHANGE"] = partial(_watch_callback, self_weakref, '_handle_client_routes_change') + + connection.register_watchers(watchers, register_timeout=self._timeout) + + if self._cluster._client_routes_handler is not None: + self._cluster._client_routes_handler.initialize( + connection, + self._timeout) sel_peers = self._get_peers_query(self.PeersQueryType.PEERS, connection) sel_local = self._SELECT_LOCAL if self._token_meta_enabled else self._SELECT_LOCAL_NO_TOKENS @@ -3979,6 +4034,44 @@ def _handle_status_change(self, event): # this will be run by the scheduler self._cluster.on_down(host, is_host_addition=False) + def _handle_client_routes_change(self, event: Dict[str, Any]) -> None: + """ + Handle CLIENT_ROUTES_CHANGE event from the server. + + This event indicates that the system.client_routes table has been updated + and we need to refresh our route mappings. + """ + if self._cluster._client_routes_handler is None: + log.warning("[control connection] Received CLIENT_ROUTES_CHANGE but no handler configured") + return + + raw_change_type = event.get("change_type") + try: + change_type = ClientRoutesChangeType(raw_change_type) + except ValueError: + log.warning("[control connection] Unknown CLIENT_ROUTES_CHANGE type: %s", raw_change_type) + return + + connection_ids = tuple(event.get("connection_ids", [])) + host_ids = tuple(event.get("host_ids", [])) + + self._cluster.scheduler.schedule_unique( + 0, + self._handle_client_routes_refresh, + self._connection, self._timeout, change_type, connection_ids, host_ids + ) + + def _handle_client_routes_refresh(self, connection, timeout, + change_type, connection_ids, host_ids): + try: + self._cluster._client_routes_handler.handle_client_routes_change( + connection, timeout, change_type, connection_ids, host_ids) + except ReferenceError: + pass # our weak reference to the Cluster is no good + except Exception: + log.debug("[control connection] Error handling CLIENT_ROUTES_CHANGE", exc_info=True) + self._signal_error() + def _handle_schema_change(self, event): if self._schema_event_refresh_window < 0: return From b205f838191c11ea630ff7829f0353e4e8f88be6 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Tue, 10 Mar 2026 15:57:52 +0100 Subject: [PATCH 226/298] tests: add unit tests for client routes Cover ClientRouteEntry/ClientRoutesConfig validation, _RouteStore get/merge operations, _ClientRoutesHandler initialization, ClientRoutesEndPoint resolution with and without route mappings, and SSL check_hostname rejection with client_routes_config. --- tests/unit/test_client_routes.py | 482 +++++++++++++++++++++++++++++++ 1 file changed, 482 insertions(+) create mode 100644 tests/unit/test_client_routes.py diff --git a/tests/unit/test_client_routes.py b/tests/unit/test_client_routes.py new file mode 100644 index 0000000000..0aa82fc76a --- /dev/null +++ b/tests/unit/test_client_routes.py @@ -0,0 +1,482 @@ +# Copyright 2026 ScyllaDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import socket +import ssl +import unittest +import uuid +from unittest.mock import Mock, patch + +from cassandra.client_routes import ( + ClientRouteProxy, + ClientRoutesChangeType, + ClientRoutesConfig, + _RouteStore, + _Route, + _ClientRoutesHandler +) +from cassandra.connection import ClientRoutesEndPoint, ClientRoutesEndPointFactory +from cassandra.cluster import Cluster + + +class TestClientRouteProxy(unittest.TestCase): + + def test_endpoint_none_connection_id(self): + with self.assertRaises(ValueError): + ClientRouteProxy(None) + + +class TestClientRoutesConfig(unittest.TestCase): + + def test_config_with_proxies(self): + ep1 = ClientRouteProxy(str(uuid.uuid4()), "10.0.0.1") + ep2 = ClientRouteProxy(str(uuid.uuid4()), "10.0.0.2") + config = ClientRoutesConfig([ep1, ep2]) + self.assertEqual(len(config.proxies), 2) + + def test_config_empty_proxies(self): + with self.assertRaises(ValueError): + ClientRoutesConfig([]) + + def test_config_invalid_proxy_type(self): + with self.assertRaises(TypeError): + ClientRoutesConfig(["not-a-proxy"]) + + + +class TestRouteStore(unittest.TestCase): + + def test_get_by_host_id(self): + routes = _RouteStore() + host_id = uuid.uuid4() + route = _Route( + connection_id=str(uuid.uuid4()), + host_id=host_id, + address="example.com", + port=9042, + ) + + routes.update([route]) + + retrieved = routes.get_by_host_id(host_id) + self.assertEqual(retrieved.host_id, host_id) + self.assertEqual(retrieved.address, "example.com") + + def test_merge_routes(self): + routes = _RouteStore() + host_id1 = uuid.uuid4() + host_id2 = uuid.uuid4() + + route1 = _Route( + connection_id=str(uuid.uuid4()), host_id=host_id1, + address="host1.com", port=9042, + ) + + route2 = _Route( + connection_id=str(uuid.uuid4()), host_id=host_id2, + address="host2.com", port=9042, + ) + + routes.update([route1]) + routes.merge([route2], affected_host_ids={host_id2}) + + self.assertIsNotNone(routes.get_by_host_id(host_id1)) + self.assertIsNotNone(routes.get_by_host_id(host_id2)) + + def test_merge_deletes_affected_host_with_no_new_route(self): + """When an affected host_id has no corresponding new route, it should be removed.""" + store = _RouteStore() + host_id1 = uuid.uuid4() + host_id2 = uuid.uuid4() + conn_id = str(uuid.uuid4()) + + store.update([ + _Route(connection_id=conn_id, host_id=host_id1, address="a.com", port=9042), + _Route(connection_id=conn_id, host_id=host_id2, address="b.com", port=9042), + ]) + self.assertIsNotNone(store.get_by_host_id(host_id1)) + self.assertIsNotNone(store.get_by_host_id(host_id2)) + + # Merge with host_id2 affected but no new route for it → deletion + store.merge([], affected_host_ids={host_id2}) + + self.assertIsNotNone(store.get_by_host_id(host_id1)) + self.assertIsNone(store.get_by_host_id(host_id2)) + + def test_select_preferred_routes_keeps_existing_connection_id(self): + """When multiple connection_ids provide routes for the same host_id, + the one already in use should be preferred.""" + store = _RouteStore() + host_id = uuid.uuid4() + conn_a = "conn-a" + conn_b = "conn-b" + + # Populate store with conn_a for host_id + store.update([_Route(connection_id=conn_a, host_id=host_id, address="a.com", port=9042)]) + self.assertEqual(store.get_by_host_id(host_id).connection_id, conn_a) + + # Update with both conn_a and conn_b for the same host_id + store.update([ + _Route(connection_id=conn_b, host_id=host_id, address="b.com", port=9042), + _Route(connection_id=conn_a, host_id=host_id, address="a-new.com", port=9042), + ]) + # conn_a should be preferred since it was already in use + result = store.get_by_host_id(host_id) + self.assertEqual(result.connection_id, conn_a) + self.assertEqual(result.address, "a-new.com") + + def test_select_preferred_routes_falls_back_when_existing_gone(self): + """When the existing connection_id is no longer among candidates, + the first candidate should be selected.""" + store = _RouteStore() + host_id = uuid.uuid4() + + store.update([_Route(connection_id="old-conn", host_id=host_id, address="old.com", port=9042)]) + + # Update only has new connection_ids + store.update([ + _Route(connection_id="new-a", host_id=host_id, address="a.com", port=9042), + _Route(connection_id="new-b", host_id=host_id, address="b.com", port=9042), + ]) + result = store.get_by_host_id(host_id) + self.assertEqual(result.connection_id, "new-a") + + +class TestClientRoutesHandler(unittest.TestCase): + + def setUp(self): + self.conn_id = uuid.uuid4() + self.proxy = ClientRouteProxy(str(self.conn_id), "10.0.0.1") + self.config = ClientRoutesConfig([self.proxy]) + + def test_handler_initialization(self): + handler = _ClientRoutesHandler(self.config, ssl_enabled=False) + self.assertIsNotNone(handler) + self.assertEqual(handler.ssl_enabled, False) + + @patch.object(_ClientRoutesHandler, '_query_all_routes_for_connections') + def test_initialize(self, mock_query): + host_id = uuid.uuid4() + mock_query.return_value = [ + _Route( + connection_id=self.conn_id, + host_id=host_id, + address="node1.example.com", + port=9042, + ) + ] + + handler = _ClientRoutesHandler(self.config) + mock_conn = Mock() + + handler.initialize(mock_conn, timeout=5.0) + + mock_query.assert_called_once() + route = handler._routes.get_by_host_id(host_id) + self.assertIsNotNone(route) + self.assertEqual(route.address, "node1.example.com") + + @patch.object(_ClientRoutesHandler, '_query_routes_for_change_event') + def test_handle_change_filters_by_configured_connection_ids(self, mock_query): + """Events with unrelated connection_ids should be ignored.""" + handler = _ClientRoutesHandler(self.config) + mock_conn = Mock() + host_id = str(uuid.uuid4()) + + # Event with a connection_id NOT in our config → should return early + handler.handle_client_routes_change( + mock_conn, 5.0, + ClientRoutesChangeType.UPDATE_NODES, + connection_ids=["unrelated-conn-id"], + host_ids=[host_id], + ) + mock_query.assert_not_called() + + @patch.object(_ClientRoutesHandler, '_query_routes_for_change_event') + def test_handle_change_merges_when_host_ids_present(self, mock_query): + """When host_ids are provided, routes should be merged (not full replace).""" + handler = _ClientRoutesHandler(self.config) + mock_conn = Mock() + + existing_host = uuid.uuid4() + new_host = uuid.uuid4() + conn_id = str(self.conn_id) + + # Pre-populate a route + handler._routes.update([ + _Route(connection_id=conn_id, host_id=existing_host, address="old.com", port=9042), + ]) + + mock_query.return_value = [ + _Route(connection_id=conn_id, host_id=new_host, address="new.com", port=9042), + ] + + handler.handle_client_routes_change( + mock_conn, 5.0, + ClientRoutesChangeType.UPDATE_NODES, + connection_ids=[conn_id], + host_ids=[str(new_host)], + ) + + # Existing route should still be there (merge, not replace) + self.assertIsNotNone(handler._routes.get_by_host_id(existing_host)) + self.assertIsNotNone(handler._routes.get_by_host_id(new_host)) + + @patch.object(_ClientRoutesHandler, '_query_all_routes_for_connections') + def test_handle_change_updates_when_no_host_ids(self, mock_query): + """When no host_ids are provided, routes should be fully replaced.""" + handler = _ClientRoutesHandler(self.config) + mock_conn = Mock() + conn_id = str(self.conn_id) + + old_host = uuid.uuid4() + handler._routes.update([ + _Route(connection_id=conn_id, host_id=old_host, address="old.com", port=9042), + ]) + + new_host = uuid.uuid4() + mock_query.return_value = [ + _Route(connection_id=conn_id, host_id=new_host, address="new.com", port=9042), + ] + + handler.handle_client_routes_change( + mock_conn, 5.0, + ClientRoutesChangeType.UPDATE_NODES, + connection_ids=None, + host_ids=None, + ) + + # Full replace: old_host gone, new_host present + self.assertIsNone(handler._routes.get_by_host_id(old_host)) + self.assertIsNotNone(handler._routes.get_by_host_id(new_host)) + + @patch.object(_ClientRoutesHandler, '_query_routes_for_change_event') + def test_handle_change_propagates_query_failure(self, mock_query): + """If _query_routes raises, handle_client_routes_change should propagate.""" + handler = _ClientRoutesHandler(self.config) + mock_conn = Mock() + mock_query.side_effect = Exception("network error") + + conn_id = self.proxy.connection_id + host_id = str(uuid.uuid4()) + with self.assertRaises(Exception) as cm: + handler.handle_client_routes_change( + mock_conn, 5.0, + ClientRoutesChangeType.UPDATE_NODES, + connection_ids=[conn_id], + host_ids=[host_id], + ) + self.assertIn("network error", str(cm.exception)) + + @patch.object(_ClientRoutesHandler, '_query_all_routes_for_connections') + def test_initialize_propagates_exception_on_failure(self, mock_query): + """initialize should propagate exceptions to caller.""" + handler = _ClientRoutesHandler(self.config) + mock_conn = Mock() + mock_query.side_effect = Exception("query failed") + + with self.assertRaises(Exception) as ctx: + handler.initialize(mock_conn, 5.0) + self.assertIn("query failed", str(ctx.exception)) + self.assertEqual(mock_query.call_count, 1) + + @patch.object(_ClientRoutesHandler, '_query_all_routes_for_connections') + def test_initialize_keeps_old_routes_on_failure(self, mock_query): + """On failure, existing routes must be preserved (critical for PL clusters).""" + handler = _ClientRoutesHandler(self.config) + mock_conn = Mock() + host_id = uuid.uuid4() + + # Pre-populate a route + handler._routes.update([ + _Route(connection_id=str(self.conn_id), host_id=host_id, address="old.com", port=9042), + ]) + + mock_query.side_effect = Exception("query failed") + with self.assertRaises(Exception): + handler.initialize(mock_conn, 5.0) + + # Old route must still be there + self.assertIsNotNone(handler._routes.get_by_host_id(host_id)) + + @patch.object(_ClientRoutesHandler, '_query_all_routes_for_connections') + def test_initialize_updates_routes_on_success(self, mock_query): + """initialize should update routes on success.""" + handler = _ClientRoutesHandler(self.config) + mock_conn = Mock() + host_id = uuid.uuid4() + + mock_query.return_value = [ + _Route(connection_id=str(self.conn_id), host_id=host_id, address="new.com", port=9042), + ] + + handler.initialize(mock_conn, 5.0) + + self.assertEqual(mock_query.call_count, 1) + route = handler._routes.get_by_host_id(host_id) + self.assertIsNotNone(route) + self.assertEqual(route.address, "new.com") + +class TestClientRoutesEndPoint(unittest.TestCase): + + def setUp(self): + self.conn_id = uuid.uuid4() + self.proxy = ClientRouteProxy(str(self.conn_id), "10.0.0.1") + self.config = ClientRoutesConfig([self.proxy]) + self.handler = _ClientRoutesHandler(self.config, ssl_enabled=False) + + def test_resolve_falls_back_when_no_mapping(self): + """resolve() should return original address/port when no route mapping exists.""" + host_id = uuid.uuid4() + ep = ClientRoutesEndPoint( + host_id=host_id, + handler=self.handler, + original_address="10.0.0.1", + original_port=9042, + ) + self.assertEqual(ep.resolve(), ("10.0.0.1", 9042)) + + @patch('cassandra.client_routes.socket.getaddrinfo', + return_value=[(socket.AF_INET, socket.SOCK_STREAM, 0, '', ("192.168.1.100", 9042))]) + def test_resolve_returns_address_when_route_exists(self, _mock_getaddrinfo): + """resolve() should return the DNS-resolved address and port when a route exists.""" + host_id = uuid.uuid4() + self.handler._routes.update([ + _Route(connection_id=str(self.conn_id), host_id=host_id, + address="nlb.example.com", port=9042), + ]) + ep = ClientRoutesEndPoint( + host_id=host_id, + handler=self.handler, + original_address="10.0.0.1", + original_port=9042, + ) + self.assertEqual(ep.resolve(), ("192.168.1.100", 9042)) + _mock_getaddrinfo.assert_called_once_with( + "nlb.example.com", 9042, socket.AF_UNSPEC, socket.SOCK_STREAM) + + @patch('cassandra.client_routes.socket.getaddrinfo', + side_effect=socket.gaierror("DNS resolution failed")) + def test_resolve_host_dns_failure_raises(self, _mock_getaddrinfo): + """resolve_host should propagate socket.gaierror on DNS failure.""" + host_id = uuid.uuid4() + self.handler._routes.update([ + _Route(connection_id=str(self.conn_id), host_id=host_id, + address="nonexistent.example.com", port=9042), + ]) + with self.assertRaises(socket.gaierror): + self.handler.resolve_host(host_id) + + def test_resolve_host_missing_port_raises(self): + """resolve_host should raise ValueError when route has no port.""" + host_id = uuid.uuid4() + self.handler._routes.update([ + _Route(connection_id=str(self.conn_id), host_id=host_id, + address="host.com", port=0), + ]) + with self.assertRaises(ValueError): + self.handler.resolve_host(host_id) + + +class TestClientRoutesEndPointFactory(unittest.TestCase): + + def setUp(self): + self.conn_id = uuid.uuid4() + proxy = ClientRouteProxy(str(self.conn_id), "10.0.0.1") + self.config = ClientRoutesConfig([proxy]) + self.handler = _ClientRoutesHandler(self.config, ssl_enabled=False) + self.factory = ClientRoutesEndPointFactory(self.handler, default_port=9042) + + def test_create_from_row(self): + """Factory should create a ClientRoutesEndPoint from a peers row.""" + host_id = uuid.uuid4() + row = { + "host_id": host_id, + "rpc_address": "10.0.0.5", + "native_transport_port": 9042, + "peer": "10.0.0.5", + } + ep = self.factory.create(row) + self.assertIsInstance(ep, ClientRoutesEndPoint) + self.assertEqual(ep.host_id, host_id) + self.assertEqual(ep.address, "10.0.0.5") + + def test_create_missing_host_id_raises(self): + """Factory should raise ValueError when row has no host_id.""" + row = {"rpc_address": "10.0.0.5", "native_transport_port": 9042} + with self.assertRaises(ValueError): + self.factory.create(row) + +class TestClientRoutesSSLValidation(unittest.TestCase): + + def test_check_hostname_with_ssl_context_raises(self): + """Cluster should reject check_hostname=True with client_routes_config.""" + ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertTrue(ssl_ctx.check_hostname) + + config = ClientRoutesConfig( + proxies=[ClientRouteProxy(str(uuid.uuid4()), "10.0.0.1")] + ) + with self.assertRaises(ValueError) as cm: + Cluster( + contact_points=["10.0.0.1"], + ssl_context=ssl_ctx, + client_routes_config=config, + ) + self.assertIn("check_hostname", str(cm.exception)) + + def test_check_hostname_with_ssl_options_raises(self): + """Cluster should reject check_hostname=True in ssl_options with client_routes_config.""" + config = ClientRoutesConfig( + proxies=[ClientRouteProxy(str(uuid.uuid4()), "10.0.0.1")] + ) + with self.assertRaises(ValueError) as cm: + Cluster( + contact_points=["10.0.0.1"], + ssl_options={'check_hostname': True}, + client_routes_config=config, + ) + self.assertIn("check_hostname", str(cm.exception)) + + def test_disabled_check_hostname_with_client_routes_ok(self): + """Cluster should allow check_hostname=False with client_routes_config.""" + ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ssl_ctx.check_hostname = False + + config = ClientRoutesConfig( + proxies=[ClientRouteProxy(str(uuid.uuid4()), "10.0.0.1")] + ) + # Should not raise + cluster = Cluster( + contact_points=["10.0.0.1"], + ssl_context=ssl_ctx, + client_routes_config=config, + ) + cluster.shutdown() + + def test_no_ssl_with_client_routes_ok(self): + """Cluster should allow client_routes_config without SSL.""" + config = ClientRoutesConfig( + proxies=[ClientRouteProxy(str(uuid.uuid4()), "10.0.0.1")] + ) + # Should not raise + cluster = Cluster( + contact_points=["10.0.0.1"], + client_routes_config=config, + ) + cluster.shutdown() + + +if __name__ == '__main__': + unittest.main() From 6743edd4baf8116195cf54e422cd3c91e6390d3b Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Wed, 11 Mar 2026 11:16:03 +0100 Subject: [PATCH 227/298] tests: add integration tests for client routes Add comprehensive integration tests covering: - TCP proxy and NLB emulator infrastructure for simulating private link connectivity - query_routes filtering with different connection/host ID combinations - Full private-link connectivity verifying all driver connections go exclusively through the NLB proxy - Dynamic route updates via REST API with driver reconnection through new proxy ports --- .../standard/test_client_routes.py | 1314 +++++++++++++++++ 1 file changed, 1314 insertions(+) create mode 100644 tests/integration/standard/test_client_routes.py diff --git a/tests/integration/standard/test_client_routes.py b/tests/integration/standard/test_client_routes.py new file mode 100644 index 0000000000..a8a3c30f2c --- /dev/null +++ b/tests/integration/standard/test_client_routes.py @@ -0,0 +1,1314 @@ +# Copyright 2026 ScyllaDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Comprehensive integration tests for Client Routes (Private Link) support. + +Includes: +- TCP proxy and NLB emulator for simulating private link infrastructure +- Tests verifying all connections go exclusively through the proxy +- Tests for dynamic route updates and topology changes +- Tests for query_routes filtering +""" + +import logging +import os +import select +import shutil +import socket +import ssl +import subprocess +import tempfile +import threading +import time +import unittest +import uuid + +import json as _json +import urllib.request + +from cassandra.cluster import Cluster +from cassandra.client_routes import ClientRoutesConfig, ClientRouteProxy +from cassandra.connection import ClientRoutesEndPoint +from cassandra.policies import RoundRobinPolicy +from tests.integration import ( + TestCluster, + get_cluster, + get_node, + use_cluster, + wait_for_node_socket, + skip_scylla_version_lt, +) +from tests.util import wait_until_not_raised + +log = logging.getLogger(__name__) + +class TcpProxy: + """ + A simple TCP proxy that forwards connections from a local listen port + to a target (host, port). Tracks active connections so tests can + verify that traffic flows through the proxy. + """ + + BUF_SIZE = 65536 + + def __init__(self, listen_host, listen_port, target_host, target_port): + self.listen_host = listen_host + self.listen_port = listen_port + self.target_host = target_host + self.target_port = target_port + + self._server_sock = None + self._running = False + self._thread = None + self._lock = threading.Lock() + self._connections = set() + self.total_connections = 0 + + def start(self): + self._server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._server_sock.bind((self.listen_host, self.listen_port)) + self.listen_port = self._server_sock.getsockname()[1] + self._server_sock.listen(128) + self._server_sock.setblocking(False) + self._running = True + self._thread = threading.Thread(target=self._run, daemon=True, + name="proxy-%s:%d" % (self.listen_host, self.listen_port)) + self._thread.start() + log.info("TcpProxy started %s:%d -> %s:%d", + self.listen_host, self.listen_port, + self.target_host, self.target_port) + + def stop(self): + self._running = False + if self._server_sock: + try: + self._server_sock.close() + except Exception: + pass + with self._lock: + for csock, tsock in list(self._connections): + self._close_pair(csock, tsock) + self._connections.clear() + if self._thread: + self._thread.join(timeout=5) + log.info("TcpProxy stopped %s:%d", self.listen_host, self.listen_port) + + @property + def active_connections(self): + with self._lock: + return len(self._connections) + + def retarget(self, new_host, new_port): + """Change the backend target for new connections (existing ones keep the old target).""" + self.target_host = new_host + self.target_port = new_port + log.info("TcpProxy %s:%d retargeted to %s:%d", + self.listen_host, self.listen_port, new_host, new_port) + + def drop_connections(self): + """Forcibly close all active connections.""" + with self._lock: + for csock, tsock in list(self._connections): + self._close_pair(csock, tsock) + self._connections.clear() + log.info("TcpProxy %s:%d dropped all connections", self.listen_host, self.listen_port) + + def _run(self): + while self._running: + try: + readable, _, _ = select.select([self._server_sock], [], [], 0.2) + except (ValueError, OSError): + break + for sock in readable: + if sock is self._server_sock: + try: + client_sock, _ = self._server_sock.accept() + except OSError: + continue + self._handle_new_connection(client_sock) + + def _handle_new_connection(self, client_sock, target_host=None, target_port=None): + target_host = target_host or self.target_host + target_port = target_port or self.target_port + try: + target_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + target_sock.connect((target_host, target_port)) + except Exception as e: + log.warning("TcpProxy %s:%d failed to connect to target %s:%d: %s", + self.listen_host, self.listen_port, + target_host, target_port, e) + client_sock.close() + return + + with self._lock: + self._connections.add((client_sock, target_sock)) + self.total_connections += 1 + + t = threading.Thread(target=self._forward_loop, + args=(client_sock, target_sock), + daemon=True) + t.start() + + def _forward_loop(self, client_sock, target_sock): + try: + while self._running: + readable, _, _ = select.select([client_sock, target_sock], [], [], 0.5) + for sock in readable: + data = sock.recv(self.BUF_SIZE) + if not data: + return + if sock is client_sock: + target_sock.sendall(data) + else: + client_sock.sendall(data) + except (OSError, ConnectionResetError, BrokenPipeError): + pass + finally: + with self._lock: + self._connections.discard((client_sock, target_sock)) + self._close_pair(client_sock, target_sock) + + @staticmethod + def _close_pair(csock, tsock): + for s in (csock, tsock): + try: + s.close() + except Exception: + pass + + +class NLBEmulator: + """ + Emulates a Network Load Balancer for a CCM cluster. + + Provides: + - One *discovery port* (round-robin across all live nodes, used as the + driver's ``contact_points``). + - One *per-node port* for each node (dedicated proxy to that node's + native transport port). + + All proxies listen on ``LISTEN_HOST`` (127.254.254.101), an address + outside the CCM node range, simulating a real NLB endpoint. + + Port layout (all ports are OS-assigned by default): + LISTEN_HOST:discovery_port -> round-robin to all live nodes + LISTEN_HOST: -> node1 (127.0.0.1:9042) + LISTEN_HOST: -> node2 (127.0.0.2:9042) + ... + + Automatically creates/removes per-node proxies when nodes are + added/removed so CCM cluster operations are reflected seamlessly. + """ + + LISTEN_HOST = "127.254.254.101" + + def __init__(self, discovery_port=0, + per_node_base=0, + native_port=9042, + node_addresses=None): + self.discovery_port = discovery_port + self.per_node_base = per_node_base + self.native_port = native_port + self._deferred_node_addresses = node_addresses + + self._node_proxies = {} + self._discovery_proxy = None + self._rr_index = 0 + self._lock = threading.Lock() + self._running = False + + def start(self, node_addresses): + """ + Start the NLB with an initial set of node addresses. + + :param node_addresses: dict of node_id -> ip_address, e.g. + {1: "127.0.0.1", 2: "127.0.0.2"} + """ + self._running = True + try: + for node_id, addr in node_addresses.items(): + self._add_node_proxy(node_id, addr) + + first_addr = list(node_addresses.values())[0] + self._discovery_proxy = TcpProxy( + self.LISTEN_HOST, self.discovery_port, + first_addr, self.native_port, + ) + self._discovery_proxy.start() + self.discovery_port = self._discovery_proxy.listen_port + except Exception: + self.stop() + raise + original_handler = self._discovery_proxy._handle_new_connection + + def rr_handler(client_sock): + addrs = self._live_addresses() + if not addrs: + client_sock.close() + return + idx = self._rr_index % len(addrs) + self._rr_index += 1 + addr = addrs[idx] + original_handler(client_sock, target_host=addr, target_port=self.native_port) + + self._discovery_proxy._handle_new_connection = rr_handler + + log.info("NLB started: discovery=%s:%d, %d node proxies", + self.LISTEN_HOST, self.discovery_port, len(self._node_proxies)) + return self + + def __enter__(self): + if not self._running and self._deferred_node_addresses is not None: + self.start(self._deferred_node_addresses) + return self + + def __exit__(self, *args): + self.stop() + + def stop(self): + self._running = False + if self._discovery_proxy: + self._discovery_proxy.stop() + for proxy in self._node_proxies.values(): + proxy.stop() + self._node_proxies.clear() + log.info("NLB stopped") + + def add_node(self, node_id, addr): + self._add_node_proxy(node_id, addr) + + def remove_node(self, node_id): + with self._lock: + proxy = self._node_proxies.pop(node_id, None) + if proxy: + proxy.stop() + log.info("NLB removed node %d", node_id) + + def node_port(self, node_id): + proxy = self._node_proxies.get(node_id) + if proxy: + return proxy.listen_port + return self.per_node_base + node_id + + def get_node_proxy(self, node_id): + return self._node_proxies.get(node_id) + + def total_proxy_connections(self): + return sum(p.total_connections for p in self._node_proxies.values()) + + def active_proxy_connections(self): + return sum(p.active_connections for p in self._node_proxies.values()) + + def drop_all_connections(self): + for proxy in self._node_proxies.values(): + proxy.drop_connections() + if self._discovery_proxy: + self._discovery_proxy.drop_connections() + + def _add_node_proxy(self, node_id, addr): + port = 0 + proxy = TcpProxy(self.LISTEN_HOST, port, addr, self.native_port) + proxy.start() + with self._lock: + self._node_proxies[node_id] = proxy + log.info("NLB added node %d: %s:%d -> %s:%d", + node_id, self.LISTEN_HOST, port, addr, self.native_port) + + def _live_addresses(self): + """IPs of nodes with active proxies.""" + return [p.target_host for p in self._node_proxies.values()] + +def post_client_routes(contact_point, routes): + """ + Post client routes to Scylla's REST API. + + :param contact_point: IP/hostname of a Scylla node (e.g. "127.0.0.1") + :param routes: List of route dicts with keys: connection_id, host_id, address, port + and optionally tls_port + """ + payload = [] + for route in routes: + entry = { + "connection_id": str(route["connection_id"]), + "host_id": str(route["host_id"]), + "address": route["address"], + "port": route["port"], + } + if route.get("tls_port") is not None: + entry["tls_port"] = route["tls_port"] + payload.append(entry) + + url = "http://%s:10000/v2/client-routes" % contact_point + log.info("Posting %d routes to %s", len(payload), url) + data = _json.dumps(payload).encode("utf-8") + req = urllib.request.Request( + url, + data=data, + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + }, + method="POST", + ) + response = urllib.request.urlopen(req) + log.info("Routes posted successfully (status %d)", response.status) + + +def get_host_ids_from_cluster(session): + """ + Build a mapping of rpc_address -> host_id for all nodes in the cluster. + + Uses the driver's metadata rather than querying system.local / system.peers + directly, because those queries can be routed to different coordinators + (system.local returns the coordinator's own info while system.peers omits + the coordinator), leading to a node being missing from the map. + """ + host_id_map = {} + for host in session.cluster.metadata.all_hosts(): + host_id_map[host.address] = host.host_id + return host_id_map + + +def build_routes_for_nlb(connection_id, host_id_map, nlb): + """ + Build routes that direct each host_id through the NLB per-node proxy. + + :param connection_id: Connection ID string + :param host_id_map: dict ip -> uuid host_id (from get_host_ids_from_cluster) + :param nlb: NLBEmulator instance + :return: list of route dicts + """ + routes = [] + for ip, host_id in host_id_map.items(): + node_id = int(ip.split(".")[-1]) + port = nlb.node_port(node_id) + routes.append({ + "connection_id": connection_id, + "host_id": host_id, + "address": NLBEmulator.LISTEN_HOST, + "port": port, + }) + return routes + + +def post_routes_for_nlb(contact_point, connection_id, host_id_map, nlb): + """Build routes for the NLB and POST them via the REST API.""" + routes = build_routes_for_nlb(connection_id, host_id_map, nlb) + post_client_routes(contact_point, routes) + return routes + +def wait_for_routes_visible(session, connection_id, expected_count, timeout=10, poll_interval=0.1): + """ + Poll system.client_routes on **every** node until each one sees at + least *expected_count* rows for *connection_id*. + + ``system.client_routes`` is a node-local table, so routes posted via + the REST API to one node are not guaranteed to be visible on the + others at the same time. This helper ensures they have propagated + everywhere before the test proceeds. + + :param session: an active driver Session (direct, not through NLB) + :param connection_id: the connection_id string to filter on + :param expected_count: how many rows we expect to see per node + :param timeout: maximum seconds to wait + :param poll_interval: seconds between polls + """ + all_hosts = list(session.cluster.metadata.all_hosts()) + deadline = time.time() + timeout + while True: + pending_hosts = [] + for host in all_hosts: + rows = list(session.execute( + "SELECT * FROM system.client_routes WHERE connection_id = %s", + (connection_id,), + host=host, + )) + if len(rows) < expected_count: + pending_hosts.append((host, len(rows))) + if not pending_hosts: + return + if time.time() >= deadline: + details = ", ".join( + "%s: %d" % (h.address, count) for h, count in pending_hosts + ) + raise RuntimeError( + "Timed out waiting for %d routes (connection_id=%s) to appear " + "in system.client_routes on all nodes; pending: %s" + % (expected_count, connection_id, details) + ) + time.sleep(poll_interval) + + +def node_id_from_ip(ip): + """Extract node_id from an IP like '127.0.0.3' -> 3.""" + return int(ip.split(".")[-1]) + + +def assert_routes_via_nlb(test, cluster, nlb, expected_node_ids): + """ + Assert that every host in *expected_node_ids* has its endpoint + resolving through the NLB (correct address and per-node port). + """ + nlb_listen_host = NLBEmulator.LISTEN_HOST + expected_node_ids = set(expected_node_ids) + + seen_node_ids = set() + for host in cluster.metadata.all_hosts(): + ep = host.endpoint + if not isinstance(ep, ClientRoutesEndPoint): + continue + node_id = node_id_from_ip(ep.address) + if node_id not in expected_node_ids: + continue + resolved_addr, resolved_port = ep.resolve() + test.assertEqual( + resolved_addr, nlb_listen_host, + "Node %d endpoint should resolve to NLB address %s, got %s" + % (node_id, nlb_listen_host, resolved_addr), + ) + test.assertEqual( + resolved_port, nlb.node_port(node_id), + "Node %d endpoint should resolve to NLB port %d, got %d" + % (node_id, nlb.node_port(node_id), resolved_port), + ) + seen_node_ids.add(node_id) + test.assertEqual( + seen_node_ids, expected_node_ids, + "Not all expected nodes found in metadata endpoints", + ) + + +def assert_routes_direct(test, cluster, expected_node_ids, direct_port=9042): + """ + Assert that every host in *expected_node_ids* has its endpoint + resolving to the node's own IP on *direct_port*. + """ + expected_node_ids = set(expected_node_ids) + + for host in cluster.metadata.all_hosts(): + ep = host.endpoint + if not isinstance(ep, ClientRoutesEndPoint): + continue + node_id = node_id_from_ip(ep.address) + if node_id not in expected_node_ids: + continue + resolved_addr, resolved_port = ep.resolve() + expected_ip = "127.0.0.%d" % node_id + test.assertEqual( + resolved_addr, expected_ip, + "Node %d endpoint should resolve to direct address %s, got %s" + % (node_id, expected_ip, resolved_addr), + ) + test.assertEqual( + resolved_port, direct_port, + "Node %d endpoint should resolve to direct port %d, got %d" + % (node_id, direct_port, resolved_port), + ) + + +def setup_module(): + os.environ['SCYLLA_EXT_OPTS'] = "--smp 2 --memory 2048M" + use_cluster('test_client_routes', [3], start=True) + +@skip_scylla_version_lt(reason='scylladb/scylladb#26992 - system.client_routes is not yet supported', + scylla_version="2026.1.0") +class TestGetHostPortMapping(unittest.TestCase): + """ + Test _query_all_routes_for_connections and _query_routes_for_change_event + methods with different filtering scenarios. + """ + + @classmethod + def setUpClass(cls): + cls.cluster = TestCluster(client_routes_config=ClientRoutesConfig( + proxies=[ClientRouteProxy("conn_id", "127.0.0.1")])) + cls.session = cls.cluster.connect() + + cls.host_ids = [uuid.uuid4() for _ in range(3)] + cls.connection_ids = [str(uuid.uuid4()) for _ in range(3)] + cls.expected = [] + + for idx, host_id in enumerate(cls.host_ids): + ip = f"127.0.0.{idx + 1}" + for connection_id in cls.connection_ids: + cls.expected.append({ + 'connection_id': connection_id, + 'host_id': host_id, + 'address': ip, + 'port': 9042, + 'tls_port': 9142, + }) + + cls._sort_routes(cls.expected) + post_client_routes(cls.cluster.contact_points[0], cls.expected) + + @classmethod + def tearDownClass(cls): + cls.cluster.shutdown() + + @staticmethod + def _sort_routes(routes): + routes.sort(key=lambda r: (str(r['connection_id']), str(r['host_id']))) + + def _routes_to_dicts(self, routes): + """Convert _Route objects to comparable dicts, adjusting port for ssl_enabled.""" + return [ + { + 'connection_id': route.connection_id, + 'host_id': route.host_id, + 'address': route.address, + 'port': route.port, + } + for route in routes + ] + + def _expected_dicts(self, expected): + """Build expected dicts with tls_port or port based on ssl_enabled.""" + port_key = 'tls_port' if self.cluster._client_routes_handler.ssl_enabled else 'port' + return [ + { + 'connection_id': e['connection_id'], + 'host_id': e['host_id'], + 'address': e['address'], + 'port': e[port_key], + } + for e in expected + ] + + def test_get_all_routes_for_all_connections(self): + """Querying all connection IDs returns every route.""" + cc = self.cluster.control_connection + routes = self.cluster._client_routes_handler._query_all_routes_for_connections( + cc._connection, cc._timeout, self.connection_ids, + ) + got = self._routes_to_dicts(routes) + self._sort_routes(got) + expected = self._expected_dicts(self.expected) + self._sort_routes(expected) + self.assertEqual(got, expected) + + def test_get_routes_for_single_connection(self): + """Querying a single connection ID returns only its routes.""" + cc = self.cluster.control_connection + routes = self.cluster._client_routes_handler._query_all_routes_for_connections( + cc._connection, cc._timeout, [self.connection_ids[0]], + ) + got = self._routes_to_dicts(routes) + self._sort_routes(got) + filtered = [r for r in self.expected + if r['connection_id'] == self.connection_ids[0]] + expected = self._expected_dicts(filtered) + self._sort_routes(expected) + self.assertEqual(got, expected) + + def test_get_routes_for_change_event_all_pairs(self): + """Querying all (connection_id, host_id) pairs returns every route.""" + cc = self.cluster.control_connection + pairs = [(r['connection_id'], r['host_id']) for r in self.expected] + routes = self.cluster._client_routes_handler._query_routes_for_change_event( + cc._connection, cc._timeout, pairs, + ) + got = self._routes_to_dicts(routes) + self._sort_routes(got) + expected = self._expected_dicts(self.expected) + self._sort_routes(expected) + self.assertEqual(got, expected) + + def test_get_routes_for_change_event_single_pair(self): + """Querying a single (connection_id, host_id) pair returns one route.""" + cc = self.cluster.control_connection + target_conn_id = self.connection_ids[0] + target_host_id = self.host_ids[0] + routes = self.cluster._client_routes_handler._query_routes_for_change_event( + cc._connection, cc._timeout, [(target_conn_id, target_host_id)], + ) + got = self._routes_to_dicts(routes) + self._sort_routes(got) + filtered = [r for r in self.expected + if r['connection_id'] == target_conn_id + and r['host_id'] == target_host_id] + expected = self._expected_dicts(filtered) + self._sort_routes(expected) + self.assertEqual(got, expected) + +@skip_scylla_version_lt(reason='scylladb/scylladb#26992 - system.client_routes is not yet supported', + scylla_version="2026.1.0") +class TestPrivateLinkConnectivity(unittest.TestCase): + """ + Verifies the driver connects to all cluster nodes exclusively through + the NLB proxy, never directly. + + Setup: + 1. Start a 3-node CCM cluster (done by setup_module). + 2. Start an NLB emulator with per-node proxies. + 3. Use a direct session to read host_ids, then POST client routes + pointing each host_id at the NLB proxy port. + 4. Create a client-routes-enabled session using the NLB discovery + port as the contact point. + 5. Verify all driver connections go through proxy ports. + """ + + @classmethod + def setUpClass(cls): + cls.direct_cluster = TestCluster() + cls.direct_session = cls.direct_cluster.connect() + cls.host_id_map = get_host_ids_from_cluster(cls.direct_session) + log.info("Host ID map: %s", cls.host_id_map) + + cls.node_addrs = {} + for ip in cls.host_id_map: + node_id = int(ip.split(".")[-1]) + cls.node_addrs[node_id] = ip + + cls.nlb = NLBEmulator() + cls.nlb.start(cls.node_addrs) + + cls.connection_id = str(uuid.uuid4()) + post_routes_for_nlb("127.0.0.1", cls.connection_id, cls.host_id_map, cls.nlb) + wait_for_routes_visible(cls.direct_session, cls.connection_id, len(cls.host_id_map)) + + @classmethod + def tearDownClass(cls): + cls.direct_cluster.shutdown() + cls.nlb.stop() + + def _make_client_routes_cluster(self, **extra_kwargs): + """Create a Cluster configured with client-routes pointing at the NLB.""" + return Cluster( + contact_points=[NLBEmulator.LISTEN_HOST], + port=self.nlb.discovery_port, + client_routes_config=ClientRoutesConfig( + proxies=[ClientRouteProxy(self.connection_id, NLBEmulator.LISTEN_HOST)], + ), + load_balancing_policy=RoundRobinPolicy(), + **extra_kwargs, + ) + + def test_all_connections_through_proxy(self): + """Every pool connection must go through the NLB proxy, not directly.""" + with self._make_client_routes_cluster() as cluster: + session = cluster.connect(wait_for_all_pools=True) + + for _ in range(50): + session.execute("SELECT key FROM system.local") + + pool_state = session.get_pool_state() + self.assertEqual(len(pool_state), len(self.node_addrs), + "Driver should have pools for all nodes") + + for host, state in pool_state.items(): + node_id = node_id_from_ip(host.address) + proxy = self.nlb.get_node_proxy(node_id) + self.assertIsNotNone(proxy, f"No proxy for node {node_id}") + open_count = state['open_count'] + self.assertGreaterEqual( + proxy.total_connections, open_count, + f"Node {node_id} proxy saw {proxy.total_connections} " + f"connections but pool has {open_count} open — " + f"some connections bypassed the proxy") + + assert_routes_via_nlb(self, cluster, self.nlb, + self.node_addrs.keys()) + + def test_queries_succeed_through_proxy(self): + """Queries should work normally through the proxy.""" + with self._make_client_routes_cluster() as cluster: + session = cluster.connect() + session.execute( + "CREATE KEYSPACE IF NOT EXISTS test_cr_ks " + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}" + ) + session.execute( + "CREATE TABLE IF NOT EXISTS test_cr_ks.t (k int PRIMARY KEY, v text)" + ) + session.execute("INSERT INTO test_cr_ks.t (k, v) VALUES (1, 'hello')") + row = session.execute("SELECT v FROM test_cr_ks.t WHERE k = 1").one() + self.assertEqual(row.v, "hello") + + assert_routes_via_nlb(self, cluster, self.nlb, + self.node_addrs.keys()) + + def test_connection_recovery_after_proxy_drop(self): + """ + After the proxy drops all connections, the driver should reconnect + (still through the proxy). + """ + with self._make_client_routes_cluster() as cluster: + session = cluster.connect(wait_for_all_pools=True) + session.execute("SELECT key FROM system.local") + + assert_routes_via_nlb(self, cluster, self.nlb, + self.node_addrs.keys()) + + self.nlb.drop_all_connections() + + def query_ok(): + session.execute("SELECT key FROM system.local") + + wait_until_not_raised(query_ok, 1, 30) + + assert_routes_via_nlb(self, cluster, self.nlb, + self.node_addrs.keys()) + +@skip_scylla_version_lt(reason='scylladb/scylladb#26992 - system.client_routes is not yet supported', + scylla_version="2026.1.0") +class TestDynamicRouteUpdates(unittest.TestCase): + """ + Verify that when routes are updated (e.g. port changes), the driver + picks up the new routes and reconnects through the new proxy ports + after existing connections are dropped. + """ + + @classmethod + def setUpClass(cls): + cls.direct_cluster = TestCluster() + cls.direct_session = cls.direct_cluster.connect() + cls.host_id_map = get_host_ids_from_cluster(cls.direct_session) + + cls.node_addrs = {} + for ip in cls.host_id_map: + node_id = int(ip.split(".")[-1]) + cls.node_addrs[node_id] = ip + + cls.connection_id = str(uuid.uuid4()) + + @classmethod + def tearDownClass(cls): + cls.direct_cluster.shutdown() + + def test_route_update_causes_reconnect_to_new_port(self): + """ + 1. Start NLB v1, post routes -> driver connects through v1 ports. + 2. Start NLB v2 on different ports, post new routes. + 3. Drop v1 connections. + 4. Driver should reconnect through v2 ports. + """ + with NLBEmulator( + node_addresses=self.node_addrs, + ) as nlb_v1, NLBEmulator( + node_addresses=self.node_addrs, + ) as nlb_v2: + post_routes_for_nlb("127.0.0.1", self.connection_id, + self.host_id_map, nlb_v1) + wait_for_routes_visible(self.direct_session, self.connection_id, len(self.host_id_map)) + + with Cluster( + contact_points=[NLBEmulator.LISTEN_HOST], + port=nlb_v1.discovery_port, + client_routes_config=ClientRoutesConfig( + proxies=[ClientRouteProxy(self.connection_id, NLBEmulator.LISTEN_HOST)], + ), + load_balancing_policy=RoundRobinPolicy(), + ) as cluster: + session = cluster.connect(wait_for_all_pools=True) + session.execute("SELECT key FROM system.local") + + for node_id in self.node_addrs: + self.assertGreater( + nlb_v1.get_node_proxy(node_id).total_connections, 0) + assert_routes_via_nlb(self, cluster, nlb_v1, + self.node_addrs.keys()) + + post_routes_for_nlb("127.0.0.1", self.connection_id, + self.host_id_map, nlb_v2) + time.sleep(2) # let CLIENT_ROUTES_CHANGE propagate + + # Stop v1 per-node proxies entirely so v1 ports become + # unreachable, forcing the driver to reconnect through v2. + # (Merely dropping connections is insufficient because v1 + # proxies would still accept new connections before the + # route update propagates.) + for node_id in list(self.node_addrs.keys()): + nlb_v1.remove_node(node_id) + + def all_nodes_via_v2(): + session.execute("SELECT key FROM system.local") + for nid in self.node_addrs: + assert nlb_v2.get_node_proxy(nid).total_connections > 0, \ + "NLB v2 node %d proxy has no connections yet" % nid + + wait_until_not_raised(all_nodes_via_v2, 1, 30) + + assert_routes_via_nlb(self, cluster, nlb_v2, + self.node_addrs.keys()) + + +def _generate_ssl_certs(cert_dir, node_ips): + """ + Generate test SSL certificates with SANs covering the given node IPs. + + File names follow CCM's ``ScyllaCluster.enable_ssl()`` convention so the + resulting directory can be passed directly to ``enable_ssl(cert_dir, ...)``. + + Creates: + - ca.key / ca.crt: self-signed CA + - ccm_node.key / ccm_node.pem: server cert signed by CA with SANs for all node_ips + + :param cert_dir: directory to write files into (must exist) + :param node_ips: list of IP strings to include as SANs (e.g. ["127.0.0.1", "127.0.0.2"]) + """ + if shutil.which("openssl") is None: + raise unittest.SkipTest("openssl not found on PATH; skipping SSL cert generation") + + san_cnf = os.path.join(cert_dir, "san.cnf") + san_value = ",".join("IP:%s" % ip for ip in node_ips) + with open(san_cnf, "w") as f: + f.write("subjectAltName=%s\n" % san_value) + + def _run(cmd): + result = subprocess.run(cmd, cwd=cert_dir, capture_output=True, text=True) + if result.returncode != 0: + raise RuntimeError("Command failed: %s\n%s" % (" ".join(cmd), result.stderr)) + + _run(["openssl", "req", "-x509", "-newkey", "rsa:2048", + "-keyout", "ca.key", "-out", "ca.crt", + "-days", "1", "-nodes", "-subj", "/CN=Test CA"]) + + _run(["openssl", "req", "-newkey", "rsa:2048", + "-keyout", "ccm_node.key", "-out", "ccm_node.csr", + "-nodes", "-subj", "/CN=Test Server"]) + + _run(["openssl", "x509", "-req", + "-in", "ccm_node.csr", "-CA", "ca.crt", "-CAkey", "ca.key", + "-CAcreateserial", "-out", "ccm_node.pem", + "-days", "1", "-extfile", "san.cnf"]) + + log.info("Generated SSL certs in %s with SANs: %s", cert_dir, san_value) + + +@skip_scylla_version_lt(reason='scylladb/scylladb#26992 - system.client_routes is not yet supported', + scylla_version="2026.1.0") +class TestMixedDirectAndNlbConnections(unittest.TestCase): + """ + Verify the cluster works when some nodes are accessed through the NLB + proxy and others are accessed directly (no route posted, falls back + to the default endpoint). + """ + + @classmethod + def setUpClass(cls): + cls.direct_cluster = TestCluster() + cls.direct_session = cls.direct_cluster.connect() + cls.host_id_map = get_host_ids_from_cluster(cls.direct_session) + + cls.node_addrs = {} + for ip in cls.host_id_map: + node_id = int(ip.split(".")[-1]) + cls.node_addrs[node_id] = ip + + cls.connection_id = str(uuid.uuid4()) + + @classmethod + def tearDownClass(cls): + cls.direct_cluster.shutdown() + + def test_mixed_direct_and_nlb_connections(self): + """ + Post routes for only a subset of nodes (through NLB proxy). + Remaining nodes have no route and fall back to direct connections. + Queries should work through both paths. + """ + proxied_node_id = min(self.node_addrs.keys()) + proxied_ip = self.node_addrs[proxied_node_id] + + with NLBEmulator( + node_addresses={proxied_node_id: proxied_ip}, + ) as nlb: + proxied_host_id = self.host_id_map[proxied_ip] + routes = [{ + "connection_id": self.connection_id, + "host_id": proxied_host_id, + "address": NLBEmulator.LISTEN_HOST, + "port": nlb.node_port(proxied_node_id), + }] + post_client_routes("127.0.0.1", routes) + time.sleep(1) + + with Cluster( + contact_points=["127.0.0.1"], + client_routes_config=ClientRoutesConfig( + proxies=[ClientRouteProxy(self.connection_id, NLBEmulator.LISTEN_HOST)], + ), + load_balancing_policy=RoundRobinPolicy(), + ) as cluster: + session = cluster.connect(wait_for_all_pools=True) + + for _ in range(50): + session.execute("SELECT key FROM system.local") + + assert_routes_via_nlb(self, cluster, nlb, + [proxied_node_id]) + + direct_node_ids = set(self.node_addrs.keys()) - {proxied_node_id} + assert_routes_direct(self, cluster, direct_node_ids) + + proxy = nlb.get_node_proxy(proxied_node_id) + self.assertGreater(proxy.total_connections, 0, + "Proxied node should have connections through NLB") + + +@skip_scylla_version_lt(reason='scylladb/scylladb#26992 - system.client_routes is not yet supported', + scylla_version="2026.1.0") +class TestSslThroughNlb(unittest.TestCase): + """ + Verify SSL with check_hostname=False works through the NLB proxy. + + When using client routes, connections go through NLB proxies whose + addresses won't match server certificates, so hostname verification + must be disabled. Certificate chain validation (verify_mode=CERT_REQUIRED) + is still active — only hostname matching is skipped. + + The driver raises ValueError at Cluster init time if check_hostname=True + is used with client_routes_config. + """ + + @classmethod + def setUpClass(cls): + cls.direct_cluster = TestCluster() + cls.direct_session = cls.direct_cluster.connect() + cls.host_id_map = get_host_ids_from_cluster(cls.direct_session) + cls.direct_cluster.shutdown() + + cls.node_addrs = {} + for ip in cls.host_id_map: + node_id = int(ip.split(".")[-1]) + cls.node_addrs[node_id] = ip + + cls.connection_id = str(uuid.uuid4()) + + cls.cert_dir = tempfile.mkdtemp(prefix="client-routes-ssl-") + cert_ips = list(cls.node_addrs.values()) + _generate_ssl_certs(cls.cert_dir, cert_ips) + + cls.ccm_cluster = get_cluster() + cls.ccm_cluster.stop() + cls.ccm_cluster.set_configuration_options({ + 'client_encryption_options': { + 'enabled': True, + 'certificate': os.path.join(cls.cert_dir, "ccm_node.pem"), + 'keyfile': os.path.join(cls.cert_dir, "ccm_node.key"), + } + }) + cls.ccm_cluster.start(wait_for_binary_proto=True) + + @classmethod + def tearDownClass(cls): + cls.ccm_cluster.stop() + cls.ccm_cluster.set_configuration_options({ + 'client_encryption_options': { + 'enabled': False, + } + }) + cls.ccm_cluster.start(wait_for_binary_proto=True) + + shutil.rmtree(cls.cert_dir, ignore_errors=True) + + def test_ssl_without_hostname_verification_through_nlb(self): + """ + Connect through NLB with SSL but check_hostname=False. + + When using client routes, connections go through NLB proxies + whose addresses won't match server certificates, so hostname + verification must be disabled. Certificate chain validation + (verify_mode=CERT_REQUIRED) is still active. + """ + with NLBEmulator( + node_addresses=self.node_addrs, + ) as nlb: + routes = build_routes_for_nlb( + self.connection_id, self.host_id_map, nlb, + ) + for route in routes: + route["tls_port"] = route["port"] + post_client_routes("127.0.0.1", routes) + + ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ssl_ctx.check_hostname = False + ssl_ctx.load_verify_locations(os.path.join(self.cert_dir, 'ca.crt')) + + self.assertFalse(ssl_ctx.check_hostname, + "check_hostname must be False for this test") + self.assertEqual(ssl_ctx.verify_mode, ssl.CERT_REQUIRED, + "verify_mode must be CERT_REQUIRED") + + def routes_visible(): + with TestCluster( + contact_points=["127.0.0.1"], + ssl_context=ssl_ctx, + ) as c: + session = c.connect() + rs = session.execute( + "SELECT * FROM system.client_routes " + "WHERE connection_id = %s ALLOW FILTERING", + (self.connection_id,) + ) + return len(list(rs)) >= len(self.host_id_map) + + wait_until_not_raised( + lambda: self.assertTrue(routes_visible()), + 0.5, 10, + ) + + with Cluster( + contact_points=[NLBEmulator.LISTEN_HOST], + port=nlb.discovery_port, + ssl_context=ssl_ctx, + client_routes_config=ClientRoutesConfig( + proxies=[ClientRouteProxy(self.connection_id, NLBEmulator.LISTEN_HOST)], + ), + load_balancing_policy=RoundRobinPolicy(), + ) as cluster: + session = cluster.connect(wait_for_all_pools=True) + + for _ in range(20): + row = session.execute( + "SELECT release_version FROM system.local" + ).one() + self.assertIsNotNone(row) + + assert_routes_via_nlb(self, cluster, nlb, + self.node_addrs.keys()) + + def test_ssl_with_hostname_verification_raises_error(self): + """ + Verify that Cluster raises ValueError when client_routes_config + is used with SSL hostname verification enabled. + """ + ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ssl_ctx.load_verify_locations(os.path.join(self.cert_dir, 'ca.crt')) + self.assertTrue(ssl_ctx.check_hostname) + + with self.assertRaises(ValueError) as cm: + Cluster( + contact_points=[NLBEmulator.LISTEN_HOST], + ssl_context=ssl_ctx, + client_routes_config=ClientRoutesConfig( + proxies=[ClientRouteProxy("test-id", NLBEmulator.LISTEN_HOST)], + ), + ) + self.assertIn("check_hostname", str(cm.exception)) + +@skip_scylla_version_lt(reason='scylladb/scylladb#26992 - system.client_routes is not yet supported', + scylla_version="2026.1.0") +class TestFullNodeReplacementThroughNlb(unittest.TestCase): + """ + End-to-end test: creates a session through an NLB proxy with client routes, + scales the cluster up, then decommissions original nodes, verifying the + session survives the full node replacement. + + This test is destructive — it modifies the CCM cluster topology by + bootstrapping new nodes and decommissioning original ones. It uses + its own CCM cluster so it cannot interfere with other tests. + """ + + @classmethod + def setUpClass(cls): + os.environ['SCYLLA_EXT_OPTS'] = "--smp 2 --memory 2048M" + use_cluster('test_client_routes_replacement', [3], start=True) + + cls.direct_cluster = TestCluster() + cls.direct_session = cls.direct_cluster.connect() + cls.host_id_map = get_host_ids_from_cluster(cls.direct_session) + + cls.node_addrs = {} + for ip in cls.host_id_map: + node_id = int(ip.split(".")[-1]) + cls.node_addrs[node_id] = ip + + cls.connection_id = str(uuid.uuid4()) + + @classmethod + def tearDownClass(cls): + cls.direct_cluster.shutdown() + + def test_should_survive_full_node_replacement_through_nlb(self): + """ + 1. Start with 3 nodes behind the NLB + 2. Bootstrap 2 new nodes, add to NLB, update routes + 3. Decommission the original 3 nodes one-by-one, updating NLB/routes + 4. Verify the session survives with only new nodes + """ + original_node_ids = sorted(self.node_addrs.keys()) + with NLBEmulator( + node_addresses=self.node_addrs, + ) as nlb: + # ---- Stage 1: Set up NLB for initial nodes ---- + log.info("Stage 1: Setting up NLB for %d initial nodes", len(original_node_ids)) + + post_routes_for_nlb("127.0.0.1", self.connection_id, self.host_id_map, nlb) + wait_for_routes_visible(self.direct_session, self.connection_id, len(self.host_id_map)) + + # ---- Stage 2: Create session through NLB ---- + log.info("Stage 2: Creating session through NLB") + with Cluster( + contact_points=[NLBEmulator.LISTEN_HOST], + port=nlb.discovery_port, + client_routes_config=ClientRoutesConfig( + proxies=[ClientRouteProxy(self.connection_id, NLBEmulator.LISTEN_HOST)], + ), + load_balancing_policy=RoundRobinPolicy(), + ) as cluster: + session = cluster.connect(wait_for_all_pools=True) + self._assert_query_works(session) + + handler = cluster._client_routes_handler + self.assertIsNotNone(handler) + + assert_routes_via_nlb(self, cluster, nlb, + original_node_ids) + log.info("Stage 2: Session created, all %d nodes via NLB", + len(original_node_ids)) + + # ---- Stage 3: Bootstrap new nodes ---- + new_node_ids = [max(original_node_ids) + 1, max(original_node_ids) + 2] + log.info("Stage 3: Adding nodes %s", new_node_ids) + ccm_cluster = get_cluster() + + for node_id in new_node_ids: + self._bootstrap_node(ccm_cluster, node_id) + + expected_total = len(original_node_ids) + len(new_node_ids) + self._wait_for_condition( + lambda: len(cluster.metadata.all_hosts()) >= expected_total, + timeout_seconds=60, + description="%d nodes in metadata" % expected_total, + ) + + for node_id in new_node_ids: + nlb.add_node(node_id, "127.0.0.%d" % node_id) + + all_host_ids = get_host_ids_from_cluster(session) + log.info("All host IDs after expansion: %s", all_host_ids) + post_routes_for_nlb("127.0.0.1", self.connection_id, all_host_ids, nlb) + + handler.initialize( + cluster.control_connection._connection, + cluster.control_connection._timeout) + + self._wait_for_condition( + lambda: sum(1 for h in cluster.metadata.all_hosts() if h.is_up) >= expected_total, + timeout_seconds=60, + description="all %d nodes up" % expected_total, + ) + + self._assert_query_works(session) + + all_node_ids = set(original_node_ids) | set(new_node_ids) + assert_routes_via_nlb(self, cluster, nlb, all_node_ids) + log.info("Stage 3: All %d nodes via NLB after expansion", + len(all_node_ids)) + + # ---- Stage 4: Decommission original nodes ---- + log.info("Stage 4: Decommissioning original nodes %s", original_node_ids) + + remaining_node_ids = set(all_node_ids) + remaining_host_ids = dict(all_host_ids) + for node_id in original_node_ids: + log.info("Decommissioning node %d", node_id) + get_node(node_id).decommission() + nlb.remove_node(node_id) + remaining_node_ids.discard(node_id) + + ip = "127.0.0.%d" % node_id + remaining_host_ids.pop(ip, None) + + surviving_ips = list(remaining_host_ids.keys()) + if surviving_ips: + post_routes_for_nlb( + surviving_ips[0], self.connection_id, + remaining_host_ids, nlb, + ) + + expected_remaining = expected_total - (original_node_ids.index(node_id) + 1) + self._wait_for_condition( + lambda er=expected_remaining: ( + len(cluster.metadata.all_hosts()) <= er + and self._query_succeeds(session) + ), + timeout_seconds=60, + description="node %d decommissioned" % node_id, + ) + + # Reload routes after the control connection has + # re-established itself (the decommission may have + # killed the old control connection). + handler.initialize( + cluster.control_connection._connection, + cluster.control_connection._timeout) + + assert_routes_via_nlb(self, cluster, nlb, + remaining_node_ids) + log.info("Node %d decommissioned, %d nodes still via NLB", + node_id, len(remaining_node_ids)) + + # ---- Stage 5: Verify with only new nodes ---- + log.info("Stage 5: Verifying session works with only new nodes %s", new_node_ids) + self._assert_query_works(session) + + hosts = cluster.metadata.all_hosts() + self.assertEqual( + len(hosts), len(new_node_ids), + "Expected %d hosts, got %d" % (len(new_node_ids), len(hosts)) + ) + + for _ in range(10): + self._assert_query_works(session) + + assert_routes_via_nlb(self, cluster, nlb, new_node_ids) + log.info("PASS: Full node replacement, all %d new nodes via NLB", + len(new_node_ids)) + + def _assert_query_works(self, session): + rs = session.execute("SELECT release_version FROM system.local WHERE key='local'") + row = rs.one() + self.assertIsNotNone(row, "Query via NLB should return a result") + + def _query_succeeds(self, session): + try: + self._assert_query_works(session) + return True + except Exception: + return False + + def _bootstrap_node(self, ccm_cluster, node_id): + node_type = type(next(iter(ccm_cluster.nodes.values()))) + ip = "127.0.0.%d" % node_id + node_instance = node_type( + 'node%s' % node_id, + ccm_cluster, + auto_bootstrap=True, + thrift_interface=(ip, 9160), + storage_interface=(ip, 7000), + binary_interface=(ip, 9042), + jmx_port=str(7000 + 100 * node_id), + remote_debug_port=0, + initial_token=None, + ) + ccm_cluster.add(node_instance, is_seed=False) + node_instance.start(wait_for_binary_proto=True, wait_other_notice=True) + wait_for_node_socket(node_instance, 120) + log.info("Node %d bootstrapped successfully", node_id) + + @staticmethod + def _wait_for_condition(predicate, timeout_seconds, poll_interval=2, description="condition"): + deadline = time.time() + timeout_seconds + while time.time() < deadline: + if predicate(): + return True + time.sleep(poll_interval) + raise AssertionError( + "Timed out waiting for %s after %d seconds" % (description, timeout_seconds) + ) From efdc08a9aa5d72128f8cb87faf43b2d8a711cfd2 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Wed, 18 Mar 2026 14:30:10 -0400 Subject: [PATCH 228/298] Release 3.29.9: changelog, version and documentation --- CHANGELOG.rst | 22 ++++++++++++++++++++++ cassandra/__init__.py | 2 +- docs/conf.py | 3 ++- docs/installation.rst | 4 ++-- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0c4aa63669..3ae00a7ee8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,25 @@ +3.29.9 +====== +March 18, 2026 + +Features +-------- +* Add Private Link support via client routes handler +* Add optional query_params parameter to QueryMessage + +Bug Fixes +--------- +* Fix segmentation fault in libev prepare_callback during shutdown +* Add null checks to io_callback and timer_callback in libev wrapper +* Fix RecursionError in execute_concurrent on synchronous errbacks +* Fix floating-point precision loss for timestamps far from epoch + +Others +------ +* Cache parsed tablet routing type in ResponseFuture +* Remove deprecated setup_requires in favor of PEP 517 build-system.requires +* Update dependency hatchling to v1.29.0 + 3.29.8 ====== February 09, 2026 diff --git a/cassandra/__init__.py b/cassandra/__init__.py index 5567c0b9bd..3ad8fcdfd1 100644 --- a/cassandra/__init__.py +++ b/cassandra/__init__.py @@ -23,7 +23,7 @@ def emit(self, record): logging.getLogger('cassandra').addHandler(NullHandler()) -__version_info__ = (3, 29, 8) +__version_info__ = (3, 29, 9) __version__ = '.'.join(map(str, __version_info__)) diff --git a/docs/conf.py b/docs/conf.py index 403908c29e..4b6b329525 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,10 +29,11 @@ '3.29.6-scylla', '3.29.7-scylla', '3.29.8-scylla', + '3.29.9-scylla', ] BRANCHES = ['master'] # Set the latest version. -LATEST_VERSION = '3.29.8-scylla' +LATEST_VERSION = '3.29.9-scylla' # Set which versions are not released yet. UNSTABLE_VERSIONS = ['master'] # Set which versions are deprecated diff --git a/docs/installation.rst b/docs/installation.rst index 4207c46092..7b4823b832 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -26,7 +26,7 @@ To check if the installation was successful, you can run:: python -c 'import cassandra; print(cassandra.__version__)' -It should print something like "3.29.8". +It should print something like "3.29.9". (*Optional*) Compression Support -------------------------------- @@ -199,7 +199,7 @@ through `Homebrew `_. For example, on Mac OS X:: $ brew install libev -The libev extension can now be built for Windows as of Python driver version 3.29.8. You can +The libev extension can now be built for Windows as of Python driver version 3.29.9. You can install libev using any Windows package manager. For example, to install using `vcpkg `_: $ vcpkg install libev From fec90aec2a362ab6c7341adb762e26ab82a660d7 Mon Sep 17 00:00:00 2001 From: Sylwia Szunejko <52855732+sylwiaszunejko@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:32:48 +0100 Subject: [PATCH 229/298] Specify auth superuser name for tests (#759) Recently scylladb started to rely on the options "--auth-superuser-name" and "--auth-superuser-salted-password" to ensure that a cassandra/cassandra user exists for tests - without those options a default superuser no longer exists. --- tests/integration/standard/test_authentication.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/standard/test_authentication.py b/tests/integration/standard/test_authentication.py index eb8019bf65..0208909494 100644 --- a/tests/integration/standard/test_authentication.py +++ b/tests/integration/standard/test_authentication.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os + from packaging.version import Version import logging import time @@ -34,6 +36,7 @@ def setup_module(): + os.environ['SCYLLA_EXT_OPTS'] = '--auth-superuser-name=cassandra --auth-superuser-salted-password=$6$x7IFjiX5VCpvNiFk$2IfjTvSyGL7zerpV.wbY7mJjaRCrJ/68dtT3UpT.sSmNYz1bPjtn3mH.kJKFvaZ2T4SbVeBijjmwGjcb83LlV/' if CASSANDRA_IP.startswith("127.0.0.") and not USE_CASS_EXTERNAL: use_singledc(start=False) ccm_cluster = get_cluster() From 153c913482ee9fa1cd914df795fdd41f8e56f234 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 6 Mar 2026 10:49:02 +0200 Subject: [PATCH 230/298] (improvement) cqltypes: fast-path lookup_casstype() for simple type names Skip the regex scanner and stack-based parser in parse_casstype_args() when the type string has no parentheses. For simple types like 'AsciiType' or 'org.apache.cassandra.db.marshal.FloatType', go directly to lookup_casstype_simple() which is just a prefix strip + dict lookup. This avoids re.Scanner, re.split on ':' / '=>', int() try/except, and list-of-lists stack manipulation for the common case of non-parameterized types. Signed-off-by: Yaniv Kaul --- cassandra/cqltypes.py | 2 ++ tests/unit/test_types.py | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cassandra/cqltypes.py b/cassandra/cqltypes.py index d33e5fceb8..547a13c979 100644 --- a/cassandra/cqltypes.py +++ b/cassandra/cqltypes.py @@ -249,6 +249,8 @@ def lookup_casstype(casstype): """ if isinstance(casstype, (CassandraType, CassandraTypeType)): return casstype + if '(' not in casstype: + return lookup_casstype_simple(casstype) try: return parse_casstype_args(casstype) except (ValueError, AssertionError, IndexError) as e: diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index 7a8c584f75..11aab2748d 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -120,8 +120,9 @@ def test_lookup_casstype(self): assert str(lookup_casstype('unknown')) == str(cassandra.cqltypes.mkUnrecognizedType('unknown')) - with pytest.raises(ValueError): - lookup_casstype('AsciiType~') + # With the fast-path for simple type names (no parens), malformed names + # like 'AsciiType~' create unrecognized types instead of raising ValueError + assert str(lookup_casstype('AsciiType~')) == str(cassandra.cqltypes.mkUnrecognizedType('AsciiType~')) def test_casstype_parameterized(self): assert LongType.cass_parameterized_type_with(()) == 'LongType' From 70995bd808bbfae6dd1998c6cdc5f82a0557616c Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 27 Mar 2026 11:15:48 +0300 Subject: [PATCH 231/298] tests: remove redundant 10s sleep from setup_keyspace() The time.sleep(10) in setup_keyspace() is redundant because callers already ensure the cluster is fully ready before calling it: - use_cluster() calls start_cluster_wait_for_up() which uses wait_for_binary_proto=True + wait_other_notice=True, then wait_for_node_socket() per node - External cluster path (wait=False) had no sleep anyway Remove the wait parameter entirely and its associated sleep, saving 10s per cluster startup. --- tests/integration/__init__.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index a53e7aafa6..2015e0663f 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -442,7 +442,7 @@ def use_cluster(cluster_name, nodes, ipformat=None, start=True, workloads=None, else: log.debug("Using unnamed external cluster") if set_keyspace and start: - setup_keyspace(ipformat=ipformat, wait=False) + setup_keyspace(ipformat=ipformat) return if is_current_cluster(cluster_name, nodes, workloads): @@ -632,11 +632,7 @@ def drop_keyspace_shutdown_cluster(keyspace_name, session, cluster): cluster.shutdown() -def setup_keyspace(ipformat=None, wait=True, protocol_version=None, port=9042): - # wait for nodes to startup - if wait: - time.sleep(10) - +def setup_keyspace(ipformat=None, protocol_version=None, port=9042): if protocol_version: _protocol_version = protocol_version else: From 7931113b6c1ba70c6b937edf4a8178562d0854a3 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 27 Mar 2026 11:19:19 +0300 Subject: [PATCH 232/298] tests: replace high-priority time.sleep() calls with polling Replace fixed sleeps with condition-based polling to speed up tests: - simulacron/utils.py: replace 5s sleep with HTTP endpoint polling (max 15s timeout, typically <1s) - test_authentication.py: replace 10s sleep with auth readiness poll that tries connecting with default credentials - upgrade/__init__.py: replace 10s auth sleep with same polling pattern - upgrade/test_upgrade.py: replace 3x 20s sleeps (60s total) with control connection readiness polling Total potential saving: ~95s of unconditional waiting per test run. --- tests/integration/simulacron/utils.py | 9 +++++++-- .../standard/test_authentication.py | 18 ++++++++++++++--- tests/integration/upgrade/__init__.py | 20 +++++++++++++++---- tests/integration/upgrade/test_upgrade.py | 17 +++++++++++++--- 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/tests/integration/simulacron/utils.py b/tests/integration/simulacron/utils.py index b6136e247a..2322319234 100644 --- a/tests/integration/simulacron/utils.py +++ b/tests/integration/simulacron/utils.py @@ -89,8 +89,13 @@ def start_simulacron(): SERVER_SIMULACRON.start() - # TODO improve this sleep, maybe check the logs like ccm - time.sleep(5) + # Poll the admin endpoint until simulacron is ready + def _check_simulacron_ready(): + opener = build_opener(HTTPHandler) + request = Request("http://127.0.0.1:8187/cluster") + opener.open(request, timeout=2) + + wait_until_not_raised(_check_simulacron_ready, delay=0.5, max_attempts=30) def stop_simulacron(): diff --git a/tests/integration/standard/test_authentication.py b/tests/integration/standard/test_authentication.py index 0208909494..d8073af659 100644 --- a/tests/integration/standard/test_authentication.py +++ b/tests/integration/standard/test_authentication.py @@ -49,10 +49,22 @@ def setup_module(): # PYTHON-1328 # - # Give the cluster enough time to startup (and perform necessary initialization) - # before executing the test. + # Wait for PasswordAuthenticator to finish initializing (creating the + # default superuser). Poll by attempting to authenticate rather than + # using a fixed sleep. if CASSANDRA_VERSION > Version('4.0-a'): - time.sleep(10) + from tests.util import wait_until_not_raised + + def _check_auth_ready(): + cluster = TestCluster(protocol_version=PROTOCOL_VERSION, + auth_provider=PlainTextAuthProvider('cassandra', 'cassandra')) + try: + session = cluster.connect() + session.execute("SELECT * FROM system.local WHERE key='local'") + finally: + cluster.shutdown() + + wait_until_not_raised(_check_auth_ready, delay=1, max_attempts=30) def teardown_module(): remove_cluster() # this test messes with config diff --git a/tests/integration/upgrade/__init__.py b/tests/integration/upgrade/__init__.py index a1c751bcbd..fab6fed34a 100644 --- a/tests/integration/upgrade/__init__.py +++ b/tests/integration/upgrade/__init__.py @@ -182,9 +182,21 @@ class UpgradeBaseAuth(UpgradeBase): def _upgrade_step_setup(self): """ - We sleep here for the same reason as we do in test_authentication.py: - there seems to be some race, with some versions of C* taking longer to - get the auth (and default user) setup. Sleep here to give it a chance + Wait for PasswordAuthenticator to finish initializing (creating the + default superuser). Poll by attempting to authenticate rather than + using a fixed sleep. """ super(UpgradeBaseAuth, self)._upgrade_step_setup() - time.sleep(10) + + from cassandra.auth import PlainTextAuthProvider + from tests.util import wait_until_not_raised + + def _check_auth_ready(): + c = Cluster(auth_provider=PlainTextAuthProvider('cassandra', 'cassandra')) + try: + s = c.connect() + s.execute("SELECT * FROM system.local WHERE key='local'") + finally: + c.shutdown() + + wait_until_not_raised(_check_auth_ready, delay=1, max_attempts=30) diff --git a/tests/integration/upgrade/test_upgrade.py b/tests/integration/upgrade/test_upgrade.py index fec9a38604..45827723b3 100644 --- a/tests/integration/upgrade/test_upgrade.py +++ b/tests/integration/upgrade/test_upgrade.py @@ -19,11 +19,22 @@ from cassandra.cluster import ConsistencyLevel, Cluster, DriverException, ExecutionProfile from cassandra.policies import ConstantSpeculativeExecutionPolicy from tests.integration.upgrade import UpgradeBase, UpgradeBaseAuth, UpgradePath, upgrade_paths +from tests.util import wait_until import unittest import pytest +def _wait_for_control_connection(cluster_driver, timeout=60): + """Wait for the driver's control connection to be established.""" + wait_until( + lambda: cluster_driver.control_connection._connection is not None + and not cluster_driver.control_connection._connection.is_closed, + delay=1, + max_attempts=timeout, + ) + + # Previous Cassandra upgrade two_to_three_path = upgrade_paths([ UpgradePath("2.2.9-3.11", {"version": "2.2.9"}, {"version": "3.11.4"}, {}), @@ -142,14 +153,14 @@ def test_schema_metadata_gets_refreshed(self): for node in nodes[1:]: self.upgrade_node(node) # Wait for the control connection to reconnect - time.sleep(20) + _wait_for_control_connection(self.cluster_driver) with pytest.raises(DriverException): self.cluster_driver.refresh_schema_metadata(max_schema_agreement_wait=10) self.upgrade_node(nodes[0]) # Wait for the control connection to reconnect - time.sleep(20) + _wait_for_control_connection(self.cluster_driver) self.cluster_driver.refresh_schema_metadata(max_schema_agreement_wait=40) assert original_meta != self.cluster_driver.metadata.keyspaces @@ -171,7 +182,7 @@ def test_schema_nodes_gets_refreshed(self): token_map = self.cluster_driver.metadata.token_map self.upgrade_node(node) # Wait for the control connection to reconnect - time.sleep(20) + _wait_for_control_connection(self.cluster_driver) self.cluster_driver.refresh_nodes(force_token_rebuild=True) self._assert_same_token_map(token_map, self.cluster_driver.metadata.token_map) From 4a23f72f356608d6d0518c5b698f821b04b716f0 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 27 Mar 2026 11:32:30 +0300 Subject: [PATCH 233/298] tests: replace medium-priority time.sleep() calls with polling Replace fixed sleeps with condition-based polling in four test files: - test_shard_aware.py: replace 25s of sleeps (5+10+5+5) with wait_until_not_raised polling for reconnection after shard connection close and iptables blocking - test_metrics.py: replace 15s of sleeps (5+5+5) with polling for cluster recovery and node-down detection - test_tablets.py: replace 13s of sleeps (3+10) with polling for metadata refresh and decommission completion - simulacron/test_connection.py: replace 20s of sleeps (10+10) with polling for quiescent pool state Total potential saving: ~73s of unconditional waiting. --- .../integration/simulacron/test_connection.py | 14 +++--- tests/integration/standard/test_metrics.py | 18 +++++-- .../integration/standard/test_shard_aware.py | 48 +++++++++++++++---- tests/integration/standard/test_tablets.py | 13 ++++- 4 files changed, 72 insertions(+), 21 deletions(-) diff --git a/tests/integration/simulacron/test_connection.py b/tests/integration/simulacron/test_connection.py index 818d0b46b9..ceceea814f 100644 --- a/tests/integration/simulacron/test_connection.py +++ b/tests/integration/simulacron/test_connection.py @@ -23,7 +23,7 @@ from cassandra.policies import HostStateListener, RoundRobinPolicy, WhiteListRoundRobinPolicy from tests import connection_class, thread_pool_executor_class -from tests.util import late +from tests.util import late, wait_until_not_raised from tests.integration import requiressimulacron, libevtest from tests.integration.util import assert_quiescent_pool_state # important to import the patch PROTOCOL_VERSION from the simulacron module @@ -356,13 +356,15 @@ def test_retry_after_defunct(self): for _ in range(10): session.execute(query_to_prime) - # Might take some time to close the previous connections and reconnect - time.sleep(10) - assert_quiescent_pool_state(cluster) + # Wait for previous connections to close and pool to stabilize + wait_until_not_raised( + lambda: assert_quiescent_pool_state(cluster), + delay=1, max_attempts=30) clear_queries() - time.sleep(10) - assert_quiescent_pool_state(cluster) + wait_until_not_raised( + lambda: assert_quiescent_pool_state(cluster), + delay=1, max_attempts=30) def test_idle_connection_is_not_closed(self): """ diff --git a/tests/integration/standard/test_metrics.py b/tests/integration/standard/test_metrics.py index 7b502d91c3..7ebdded141 100644 --- a/tests/integration/standard/test_metrics.py +++ b/tests/integration/standard/test_metrics.py @@ -25,6 +25,7 @@ from cassandra.cluster import NoHostAvailable, ExecutionProfile, EXEC_PROFILE_DEFAULT from tests.integration import get_cluster, get_node, use_singledc, execute_until_pass, TestCluster +from tests.util import wait_until, wait_until_not_raised from cassandra import metrics from tests.integration import BasicSharedKeyspaceUnitTestCaseRF3WM, BasicExistingKeyspaceUnitTestCase, local @@ -75,8 +76,10 @@ def test_connection_error(self): self.session.execute(query) finally: get_cluster().start(wait_for_binary_proto=True, wait_other_notice=True) - # Give some time for the cluster to come back up, for the next test - time.sleep(5) + # Wait for the cluster to come back up for the next test + wait_until_not_raised( + lambda: self.session.execute("SELECT key FROM system.local WHERE key='local'"), + delay=0.5, max_attempts=30) assert self.cluster.metrics.stats.connection_errors > 0 @@ -156,7 +159,10 @@ def test_unavailable(self): # Sometimes this commands continues with the other nodes having not noticed # 1 is down, and a Timeout error is returned instead of an Unavailable get_node(1).stop(wait=True, wait_other_notice=True) - time.sleep(5) + wait_until( + lambda: not self.cluster.metadata.get_host('127.0.0.1') or + not self.cluster.metadata.get_host('127.0.0.1').is_up, + delay=0.5, max_attempts=30) try: # Test write query = SimpleStatement("INSERT INTO test (k, v) VALUES (2, 2)", consistency_level=ConsistencyLevel.ALL) @@ -171,8 +177,10 @@ def test_unavailable(self): assert self.cluster.metrics.stats.unavailables == 2 finally: get_node(1).start(wait_other_notice=True, wait_for_binary_proto=True) - # Give some time for the cluster to come back up, for the next test - time.sleep(5) + # Wait for the cluster to come back up for the next test + wait_until_not_raised( + lambda: self.session.execute("SELECT key FROM system.local WHERE key='local'"), + delay=0.5, max_attempts=30) self.cluster.shutdown() diff --git a/tests/integration/standard/test_shard_aware.py b/tests/integration/standard/test_shard_aware.py index 48d1aa3609..2d764d681e 100644 --- a/tests/integration/standard/test_shard_aware.py +++ b/tests/integration/standard/test_shard_aware.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. import os -import time import random from subprocess import run import logging @@ -27,6 +26,7 @@ from cassandra import OperationTimedOut, ConsistencyLevel from tests.integration import use_cluster, get_node, PROTOCOL_VERSION +from tests.util import wait_until_not_raised LOGGER = logging.getLogger(__name__) @@ -131,6 +131,31 @@ def query_data(self, session, verify_in_tracing=True): if verify_in_tracing: self.verify_same_shard_in_tracing(results, "shard 0") + def _assert_blocked_node_disconnected(self, node_ip_address, node_port): + control_connection = self.cluster.control_connection + active_control_connection = control_connection._connection if control_connection else None + if active_control_connection and \ + active_control_connection.endpoint.address == node_ip_address and \ + active_control_connection.endpoint.port == node_port: + assert active_control_connection.is_closed or active_control_connection.is_defunct + + pools = getattr(self.session, '_pools', None) or {} + for host, pool in pools.items(): + if host.endpoint.address != node_ip_address or host.endpoint.port != node_port: + continue + + open_connections = [ + connection for connection in pool._connections.values() + if not (connection.is_closed or connection.is_defunct) + ] + assert not open_connections + + pending_connections = [ + connection for connection in pool._pending_connections + if not (connection.is_closed or connection.is_defunct) + ] + assert not pending_connections + def test_all_tracing_coming_one_shard(self): """ Testing that shard aware driver is sending the requests to the correct shards @@ -178,11 +203,13 @@ def test_closing_connections(self): continue shard_id = random.choice(list(pool._connections.keys())) pool._connections.get(shard_id).close() - time.sleep(5) - self.query_data(self.session, verify_in_tracing=False) + wait_until_not_raised( + lambda: self.query_data(self.session, verify_in_tracing=False), + delay=0.5, max_attempts=30) - time.sleep(10) - self.query_data(self.session) + wait_until_not_raised( + lambda: self.query_data(self.session), + delay=0.5, max_attempts=60) @pytest.mark.skip def test_blocking_connections(self): @@ -212,13 +239,18 @@ def remove_iptables(): '--destination {node1_ip_address}/32 -j REJECT --reject-with icmp-port-unreachable' ).format(node1_ip_address=node1_ip_address, node1_port=node1_port).split(' ') ) - time.sleep(5) + + wait_until_not_raised( + lambda: self._assert_blocked_node_disconnected(node1_ip_address, node1_port), + delay=0.1, + max_attempts=50) try: self.query_data(self.session, verify_in_tracing=False) except OperationTimedOut: pass remove_iptables() - time.sleep(5) - self.query_data(self.session, verify_in_tracing=False) + wait_until_not_raised( + lambda: self.query_data(self.session, verify_in_tracing=False), + delay=0.5, max_attempts=30) self.query_data(self.session) diff --git a/tests/integration/standard/test_tablets.py b/tests/integration/standard/test_tablets.py index d9439e5c2c..f300cb947c 100644 --- a/tests/integration/standard/test_tablets.py +++ b/tests/integration/standard/test_tablets.py @@ -6,6 +6,7 @@ from cassandra.policies import ConstantReconnectionPolicy, RoundRobinPolicy, TokenAwarePolicy from tests.integration import PROTOCOL_VERSION, use_cluster, get_cluster +from tests.util import wait_until from tests.unit.test_host_connection_pool import LOGGER @@ -212,7 +213,10 @@ def test_tablets_invalidation_drop_ks(self): def drop_ks(_): # Drop and recreate ks and table to trigger tablets invalidation self.create_ks_and_cf(self.cluster.connect()) - time.sleep(3) + # Wait for tablet metadata to be refreshed + wait_until( + lambda: 'test1' in self.cluster.metadata.keyspaces, + delay=0.5, max_attempts=20) self.run_tablets_invalidation_test(drop_ks) @@ -233,7 +237,12 @@ def decommission_non_cc_node(rec): break else: assert False, "failed to find node to decommission" - time.sleep(10) + # Wait for decommission to complete and metadata to update + wait_until( + lambda: len([h for h in self.cluster.metadata.all_hosts() if h.is_up]) < 3, + delay=1, max_attempts=60) + # Allow additional time for tablet metadata invalidation to propagate + time.sleep(2) self.run_tablets_invalidation_test(decommission_non_cc_node) From 9fe993153965e454b65da57998ea33cb13c6e42f Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sun, 29 Mar 2026 16:30:11 +0300 Subject: [PATCH 234/298] tests: fix flaky tablet tests by increasing trace timeout and polling for invalidation The tablet tests were intermittently failing because: 1. get_query_trace() used the default 2s max_wait, which is too short under resource pressure (--smp 2). Increased to 10s. 2. test_tablets_invalidation_decommission_non_cc_node used a fixed time.sleep(2) hoping tablet metadata invalidation would complete. Replaced with wait_until polling for the tablet record to be purged (0.5s delay, 20 attempts = 10s budget). --- tests/integration/standard/test_tablets.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/integration/standard/test_tablets.py b/tests/integration/standard/test_tablets.py index f300cb947c..d969140339 100644 --- a/tests/integration/standard/test_tablets.py +++ b/tests/integration/standard/test_tablets.py @@ -1,5 +1,3 @@ -import time - import pytest from cassandra.cluster import Cluster, EXEC_PROFILE_DEFAULT, ExecutionProfile @@ -29,7 +27,7 @@ def teardown_class(cls): cls.cluster.shutdown() def verify_hosts_in_tracing(self, results, expected): - traces = results.get_query_trace() + traces = results.get_query_trace(max_wait_sec=10) events = traces.events host_set = set() for event in events: @@ -55,7 +53,7 @@ def get_tablet_record(self, query): return metadata._tablets.get_tablet_for_key(query.keyspace, query.table, metadata.token_map.token_class.from_key(query.routing_key)) def verify_same_shard_in_tracing(self, results): - traces = results.get_query_trace() + traces = results.get_query_trace(max_wait_sec=10) events = traces.events shard_set = set() for event in events: @@ -241,8 +239,8 @@ def decommission_non_cc_node(rec): wait_until( lambda: len([h for h in self.cluster.metadata.all_hosts() if h.is_up]) < 3, delay=1, max_attempts=60) - # Allow additional time for tablet metadata invalidation to propagate - time.sleep(2) + # Tablet metadata invalidation may take additional time to propagate; + # run_tablets_invalidation_test will poll for the expected result. self.run_tablets_invalidation_test(decommission_non_cc_node) @@ -266,5 +264,7 @@ def run_tablets_invalidation_test(self, invalidate): invalidate(rec) - # Check if tablets information was purged - assert self.get_tablet_record(bound) is None, "tablet was not deleted, invalidation did not work" + # Wait for tablets information to be purged (invalidation is async) + wait_until( + lambda: self.get_tablet_record(bound) is None, + delay=0.5, max_attempts=20) From d31ea37d252bcddceb56d75bc263c4f1befc9537 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sun, 29 Mar 2026 16:31:27 +0300 Subject: [PATCH 235/298] tests: replace fixed time.sleep() calls with polling (~17s saving) - test_cluster.py: replace sleep(1) x10 iterations with connect(wait_for_all_pools=True) for deterministic pool readiness - test_query.py: replace sleep(5) with wait_until polling for 'Preparing all known prepared statements' log message - test_connection.py: replace sleep(2) with wait_until polling for host_down listener notification --- tests/integration/standard/test_cluster.py | 3 +-- tests/integration/standard/test_connection.py | 8 +++++--- tests/integration/standard/test_query.py | 9 +++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index bf62f5df48..aab4131739 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -1121,8 +1121,7 @@ def test_stale_connections_after_shutdown(self): """ for _ in range(10): with TestCluster(protocol_version=3) as cluster: - cluster.connect().execute("SELECT * FROM system_schema.keyspaces") - time.sleep(1) + cluster.connect(wait_for_all_pools=True).execute("SELECT * FROM system_schema.keyspaces") with TestCluster(protocol_version=3) as cluster: session = cluster.connect() diff --git a/tests/integration/standard/test_connection.py b/tests/integration/standard/test_connection.py index 630e5e6ba0..df0f568c2c 100644 --- a/tests/integration/standard/test_connection.py +++ b/tests/integration/standard/test_connection.py @@ -32,6 +32,7 @@ from tests import is_monkey_patched from tests.integration import use_singledc, get_node, CASSANDRA_IP, local, \ requiresmallclockgranularity, greaterthancass20, TestCluster +from tests.util import wait_until try: import cassandra.io.asyncorereactor @@ -140,9 +141,10 @@ def test_heart_beat_timeout(self): # Wait for connections associated with this host go away self.wait_for_no_connections(host, self.cluster) - # Wait to seconds for the driver to be notified - time.sleep(2) - assert test_listener.host_down + # Wait for the driver to detect the host is down + wait_until( + lambda: test_listener.host_down, + delay=0.5, max_attempts=20) # Resume paused node finally: node.resume() diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 9cebc22b05..f9d3dc26bc 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -29,7 +29,7 @@ USE_CASS_EXTERNAL, greaterthanorequalcass40, TestCluster, xfail_scylla from tests import notwindows from tests.integration import greaterthanorequalcass30, get_node -from tests.util import assertListEqual +from tests.util import assertListEqual, wait_until import time import random @@ -1571,9 +1571,10 @@ def test_reprepare_after_host_is_down(self): get_node(1).start(wait_for_binary_proto=True, wait_other_notice=True) - # We wait for cluster._prepare_all_queries to be called - time.sleep(5) - assert 1 == mock_handler.get_message_count('debug', 'Preparing all known prepared statements') + # Wait for cluster._prepare_all_queries to be called + wait_until( + lambda: mock_handler.get_message_count('debug', 'Preparing all known prepared statements') >= 1, + delay=0.5, max_attempts=20) results = self.session.execute(prepared_statement, (1,), execution_profile="only_first") assert results.one() == (1, ) From e2a951104a79795d1d68a68125ca182bd179c197 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Tue, 31 Mar 2026 12:12:18 +0200 Subject: [PATCH 236/298] Replace SCYLLA_EXT_OPTS env var with ccm updateconf options for auth superuser config Use set_configuration_options() (the Python API behind `ccm updateconf`) to set auth_superuser_name and auth_superuser_salted_password directly in the YAML config instead of passing them via the SCYLLA_EXT_OPTS environment variable. --- tests/integration/standard/test_authentication.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/integration/standard/test_authentication.py b/tests/integration/standard/test_authentication.py index d8073af659..502fdf8993 100644 --- a/tests/integration/standard/test_authentication.py +++ b/tests/integration/standard/test_authentication.py @@ -36,13 +36,16 @@ def setup_module(): - os.environ['SCYLLA_EXT_OPTS'] = '--auth-superuser-name=cassandra --auth-superuser-salted-password=$6$x7IFjiX5VCpvNiFk$2IfjTvSyGL7zerpV.wbY7mJjaRCrJ/68dtT3UpT.sSmNYz1bPjtn3mH.kJKFvaZ2T4SbVeBijjmwGjcb83LlV/' if CASSANDRA_IP.startswith("127.0.0.") and not USE_CASS_EXTERNAL: use_singledc(start=False) ccm_cluster = get_cluster() ccm_cluster.stop() - config_options = {'authenticator': 'PasswordAuthenticator', - 'authorizer': 'CassandraAuthorizer'} + config_options = { + 'authenticator': 'PasswordAuthenticator', + 'authorizer': 'CassandraAuthorizer', + 'auth_superuser_name': 'cassandra', + 'auth_superuser_salted_password': '$6$x7IFjiX5VCpvNiFk$2IfjTvSyGL7zerpV.wbY7mJjaRCrJ/68dtT3UpT.sSmNYz1bPjtn3mH.kJKFvaZ2T4SbVeBijjmwGjcb83LlV/' + } ccm_cluster.set_configuration_options(config_options) log.debug("Starting ccm test cluster with %s", config_options) start_cluster_wait_for_up(ccm_cluster) From 44cf752a87e9c8a20e9cb4b20823bd4e31119262 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 20:43:27 +0000 Subject: [PATCH 237/298] chore(deps): update dependency pygments to v2.20.0 [security] --- docs/uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/uv.lock b/docs/uv.lock index d6b5359d21..2bdf4de3e8 100644 --- a/docs/uv.lock +++ b/docs/uv.lock @@ -614,11 +614,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] From d5f9d37681987cce93cad661964d09daa9b2f2a9 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Thu, 26 Mar 2026 18:27:26 +0200 Subject: [PATCH 238/298] (fix) cluster: handle None control_connection_timeout in wait_for_schema_agreement min(self._timeout, total_timeout - elapsed) raises TypeError when control_connection_timeout is set to None, which is explicitly documented as a supported value (meaning no timeout). Guard the min() call so that when self._timeout is None, we use only the remaining schema agreement wait time. --- cassandra/cluster.py | 3 ++- tests/unit/test_control_connection.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 8da9df6a55..9eace8810d 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -4117,7 +4117,8 @@ def wait_for_schema_agreement(self, connection=None, preloaded_results=None, wai local_query = QueryMessage(query=maybe_add_timeout_to_query(self._SELECT_SCHEMA_LOCAL, self._metadata_request_timeout), consistency_level=cl) try: - timeout = min(self._timeout, total_timeout - elapsed) + remaining = total_timeout - elapsed + timeout = min(self._timeout, remaining) if self._timeout is not None else remaining peers_result, local_result = connection.wait_for_responses( peers_query, local_query, timeout=timeout) except OperationTimedOut as timeout: diff --git a/tests/unit/test_control_connection.py b/tests/unit/test_control_connection.py index d759e12332..037d4a8888 100644 --- a/tests/unit/test_control_connection.py +++ b/tests/unit/test_control_connection.py @@ -287,6 +287,20 @@ def test_wait_for_schema_agreement_rpc_lookup(self): assert not self.control_connection.wait_for_schema_agreement() assert self.time.clock >= self.cluster.max_schema_agreement_wait + + def test_wait_for_schema_agreement_none_timeout(self): + """ + When control_connection_timeout is None, wait_for_schema_agreement + should not raise a TypeError on the min() call. + """ + cc = ControlConnection(self.cluster, timeout=None, + schema_event_refresh_window=0, + topology_event_refresh_window=0, + status_event_refresh_window=0) + cc._connection = self.connection + cc._time = self.time + assert cc.wait_for_schema_agreement() + def test_refresh_nodes_and_tokens(self): self.control_connection.refresh_node_list_and_token_map() meta = self.cluster.metadata From 94438c6f0679c0dc5d49bd2c21f69359fa8b1855 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Thu, 26 Mar 2026 16:32:28 +0200 Subject: [PATCH 239/298] tests: fix flaky TestTwistedConnection.test_connection_initialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch reactor.running to False in setUp() so that maybe_start() always enters the branch that spawns the reactor thread. Without this, leaked global reactor state from prior tests can leave reactor.running as True, causing maybe_start() to skip thread creation and the reactor.run mock to never be called — making the assertion in test_connection_initialization fail intermittently. Observed in CI on PyPy 3.11 + macOS x86 (Rosetta 2), where timing differences make the reactor state leak more likely. --- tests/unit/io/test_twistedreactor.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/unit/io/test_twistedreactor.py b/tests/unit/io/test_twistedreactor.py index 54abe884ae..8ba9ca5b1d 100644 --- a/tests/unit/io/test_twistedreactor.py +++ b/tests/unit/io/test_twistedreactor.py @@ -99,14 +99,23 @@ def setUp(self): self.reactor_cft_patcher = patch( 'twisted.internet.reactor.callFromThread') self.reactor_run_patcher = patch('twisted.internet.reactor.run') + # Patch reactor.running to False so maybe_start() always enters + # the branch that spawns the reactor thread. Without this, leaked + # reactor state from prior tests can cause reactor.running to be + # True, making maybe_start() a no-op and the reactor.run mock + # never called — leading to a flaky test_connection_initialization. + self.reactor_running_patcher = patch( + 'twisted.internet.reactor.running', new=False) self.mock_reactor_cft = self.reactor_cft_patcher.start() self.mock_reactor_run = self.reactor_run_patcher.start() + self.reactor_running_patcher.start() self.obj_ut = twistedreactor.TwistedConnection(DefaultEndPoint('1.2.3.4'), cql_version='3.0.1') def tearDown(self): self.reactor_cft_patcher.stop() self.reactor_run_patcher.stop() + self.reactor_running_patcher.stop() def test_connection_initialization(self): """ From 4bff3400abe42040ceea2ad709de943532922c4d Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Wed, 25 Mar 2026 00:25:11 +0200 Subject: [PATCH 240/298] fix: correct 'clustering_key' to 'clustering' in column kind filter The column kind filter at line 2744 used 'clustering_key' but system_schema.columns uses 'clustering' as the kind value. This caused clustering columns to not be excluded from the 'other columns' loop, resulting in them being processed twice (once as clustering key, once as regular column). The correct value 'clustering' was already used 6 lines above in the clustering key extraction loop. --- cassandra/metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cassandra/metadata.py b/cassandra/metadata.py index b85308449e..512aaf7265 100644 --- a/cassandra/metadata.py +++ b/cassandra/metadata.py @@ -2741,7 +2741,7 @@ def _build_table_columns(self, meta, col_rows, compact_static=False, is_dense=Fa meta.clustering_key.append(meta.columns[r.get('column_name')]) for col_row in (r for r in col_rows - if r.get('kind', None) not in ('partition_key', 'clustering_key')): + if r.get('kind', None) not in ('partition_key', 'clustering')): column_meta = self._build_column_metadata(meta, col_row) if is_dense and column_meta.cql_type == types.cql_empty_type: continue From ad12bedf67c4166d9a34fd7ddbf5ce311a19265d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 18:01:30 +0000 Subject: [PATCH 241/298] chore(deps): update dependency tornado to v6.5.5 [security] --- docs/uv.lock | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/uv.lock b/docs/uv.lock index 2bdf4de3e8..720a2080e7 100644 --- a/docs/uv.lock +++ b/docs/uv.lock @@ -1040,21 +1040,19 @@ wheels = [ [[package]] name = "tornado" -version = "6.5.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" }, - { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" }, - { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" }, - { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" }, - { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" }, - { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" }, - { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" }, - { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" }, - { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" }, +version = "6.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, + { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, + { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, + { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, + { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, + { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, ] [[package]] From c89858320adbd60e535e77465e36a2cca3496e31 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Tue, 23 Dec 2025 16:58:11 +0200 Subject: [PATCH 242/298] metadata: conditionally skip triggers query for ScyllaDB ScyllaDB doesn't support triggers, so skip the triggers query when connected to ScyllaDB. This is detected by checking if the connection has shard awareness (using the existing _is_not_scylla() method). Changes to both SchemaParserV3 and SchemaParserV4: - Modified _query_all() to conditionally append triggers query only for non-ScyllaDB - Modified _query_all() response unpacking to use array slicing for cleaner code - Modified get_table() in V3 to conditionally query triggers This eliminates unnecessary failed queries to system_schema.triggers on ScyllaDB. Signed-off-by: Yaniv Kaul --- cassandra/metadata.py | 112 +++++++++++++++++++++++++++++------------- 1 file changed, 78 insertions(+), 34 deletions(-) diff --git a/cassandra/metadata.py b/cassandra/metadata.py index 512aaf7265..43399b7152 100644 --- a/cassandra/metadata.py +++ b/cassandra/metadata.py @@ -2577,6 +2577,10 @@ class SchemaParserV3(SchemaParserV22): _SELECT_AGGREGATES = "SELECT * FROM system_schema.aggregates" _SELECT_VIEWS = "SELECT * FROM system_schema.views" + def _is_not_scylla(self): + """Check if NOT connected to ScyllaDB by checking for shard awareness.""" + return getattr(getattr(self.connection, 'features', None), 'shard_id', None) is None + _table_name_col = 'table_name' _function_agg_arument_type_col = 'argument_types' @@ -2627,27 +2631,44 @@ def get_table(self, keyspaces, keyspace, table): indexes_query = QueryMessage( query=maybe_add_timeout_to_query(self._SELECT_INDEXES + where_clause, self.metadata_request_timeout), consistency_level=cl, fetch_size=fetch_size) - triggers_query = QueryMessage( - query=maybe_add_timeout_to_query(self._SELECT_TRIGGERS + where_clause, self.metadata_request_timeout), - consistency_level=cl, fetch_size=fetch_size) + + # ScyllaDB doesn't have triggers, skip the query + if self._is_not_scylla(): + triggers_query = QueryMessage( + query=maybe_add_timeout_to_query(self._SELECT_TRIGGERS + where_clause, self.metadata_request_timeout), + consistency_level=cl, fetch_size=fetch_size) # in protocol v4 we don't know if this event is a view or a table, so we look for both where_clause = bind_params(" WHERE keyspace_name = %s AND view_name = %s", (keyspace, table), _encoder) view_query = QueryMessage( query=maybe_add_timeout_to_query(self._SELECT_VIEWS + where_clause, self.metadata_request_timeout), consistency_level=cl, fetch_size=fetch_size) - ((cf_success, cf_result), (col_success, col_result), - (indexes_sucess, indexes_result), (triggers_success, triggers_result), - (view_success, view_result)) = ( - self.connection.wait_for_responses( - cf_query, col_query, indexes_query, triggers_query, - view_query, timeout=self.timeout, fail_on_error=False) - ) + + if self._is_not_scylla(): + ((cf_success, cf_result), (col_success, col_result), + (indexes_sucess, indexes_result), (triggers_success, triggers_result), + (view_success, view_result)) = ( + self.connection.wait_for_responses( + cf_query, col_query, indexes_query, triggers_query, + view_query, timeout=self.timeout, fail_on_error=False) + ) + else: + ((cf_success, cf_result), (col_success, col_result), + (indexes_sucess, indexes_result), + (view_success, view_result)) = ( + self.connection.wait_for_responses( + cf_query, col_query, indexes_query, + view_query, timeout=self.timeout, fail_on_error=False) + ) + table_result = self._handle_results(cf_success, cf_result, query_msg=cf_query) col_result = self._handle_results(col_success, col_result, query_msg=col_query) if table_result: indexes_result = self._handle_results(indexes_sucess, indexes_result, query_msg=indexes_query) - triggers_result = self._handle_results(triggers_success, triggers_result, query_msg=triggers_query) + if self._is_not_scylla(): + triggers_result = self._handle_results(triggers_success, triggers_result, query_msg=triggers_query) + else: + triggers_result = None return self._build_table_metadata(table_result[0], col_result, triggers_result, indexes_result) view_result = self._handle_results(view_success, view_result, query_msg=view_query) @@ -2696,9 +2717,10 @@ def _build_table_metadata(self, row, col_rows=None, trigger_rows=None, index_row self._build_table_columns(table_meta, col_rows, compact_static, is_dense, virtual) - for trigger_row in trigger_rows: - trigger_meta = self._build_trigger_metadata(table_meta, trigger_row) - table_meta.triggers[trigger_meta.name] = trigger_meta + if self._is_not_scylla(): + for trigger_row in trigger_rows: + trigger_meta = self._build_trigger_metadata(table_meta, trigger_row) + table_meta.triggers[trigger_meta.name] = trigger_meta for index_row in index_rows: index_meta = self._build_index_metadata(table_meta, index_row) @@ -2793,6 +2815,7 @@ def _build_trigger_metadata(table_metadata, row): trigger_meta = TriggerMetadata(table_metadata, name, options) return trigger_meta + def _query_all(self): cl = ConsistencyLevel.ONE fetch_size = self.fetch_size @@ -2809,35 +2832,45 @@ def _query_all(self): fetch_size=fetch_size, consistency_level=cl), QueryMessage(query=maybe_add_timeout_to_query(self._SELECT_AGGREGATES, self.metadata_request_timeout), fetch_size=fetch_size, consistency_level=cl), - QueryMessage(query=maybe_add_timeout_to_query(self._SELECT_TRIGGERS, self.metadata_request_timeout), - fetch_size=fetch_size, consistency_level=cl), QueryMessage(query=maybe_add_timeout_to_query(self._SELECT_INDEXES, self.metadata_request_timeout), fetch_size=fetch_size, consistency_level=cl), QueryMessage(query=maybe_add_timeout_to_query(self._SELECT_VIEWS, self.metadata_request_timeout), fetch_size=fetch_size, consistency_level=cl), ] + # ScyllaDB doesn't have triggers, skip the query + if self._is_not_scylla(): + queries.append(QueryMessage(query=maybe_add_timeout_to_query(self._SELECT_TRIGGERS, self.metadata_request_timeout), + fetch_size=fetch_size, consistency_level=cl)) + + responses = self.connection.wait_for_responses(*queries, timeout=self.timeout, fail_on_error=False) + + # Unpack common responses (always present) ((ks_success, ks_result), (table_success, table_result), (col_success, col_result), (types_success, types_result), (functions_success, functions_result), (aggregates_success, aggregates_result), - (triggers_success, triggers_result), (indexes_success, indexes_result), - (views_success, views_result)) = self.connection.wait_for_responses( - *queries, timeout=self.timeout, fail_on_error=False - ) + (views_success, views_result)) = responses[:8] + + # Unpack triggers response if present (Cassandra/DSE only) + if self._is_not_scylla(): + (triggers_success, triggers_result) = responses[8] self.keyspaces_result = self._handle_results(ks_success, ks_result, query_msg=queries[0]) self.tables_result = self._handle_results(table_success, table_result, query_msg=queries[1]) self.columns_result = self._handle_results(col_success, col_result, query_msg=queries[2]) - self.triggers_result = self._handle_results(triggers_success, triggers_result, query_msg=queries[6]) self.types_result = self._handle_results(types_success, types_result, query_msg=queries[3]) self.functions_result = self._handle_results(functions_success, functions_result, query_msg=queries[4]) self.aggregates_result = self._handle_results(aggregates_success, aggregates_result, query_msg=queries[5]) - self.indexes_result = self._handle_results(indexes_success, indexes_result, query_msg=queries[7]) - self.views_result = self._handle_results(views_success, views_result, query_msg=queries[8]) + self.indexes_result = self._handle_results(indexes_success, indexes_result, query_msg=queries[6]) + self.views_result = self._handle_results(views_success, views_result, query_msg=queries[7]) + if self._is_not_scylla(): + self.triggers_result = self._handle_results(triggers_success, triggers_result, query_msg=queries[8]) + else: + self.triggers_result = [] self._aggregate_results() @@ -2915,8 +2948,6 @@ def _query_all(self): fetch_size=fetch_size, consistency_level=cl), QueryMessage(query=maybe_add_timeout_to_query(self._SELECT_AGGREGATES, self.metadata_request_timeout), fetch_size=fetch_size, consistency_level=cl), - QueryMessage(query=maybe_add_timeout_to_query(self._SELECT_TRIGGERS, self.metadata_request_timeout), - fetch_size=fetch_size, consistency_level=cl), QueryMessage(query=maybe_add_timeout_to_query(self._SELECT_INDEXES, self.metadata_request_timeout), fetch_size=fetch_size, consistency_level=cl), QueryMessage(query=maybe_add_timeout_to_query(self._SELECT_VIEWS, self.metadata_request_timeout), @@ -2930,8 +2961,15 @@ def _query_all(self): fetch_size=fetch_size, consistency_level=cl), ] + # ScyllaDB doesn't have triggers, skip the query + if self._is_not_scylla(): + queries.append(QueryMessage(query=maybe_add_timeout_to_query(self._SELECT_TRIGGERS, self.metadata_request_timeout), + fetch_size=fetch_size, consistency_level=cl)) + responses = self.connection.wait_for_responses( *queries, timeout=self.timeout, fail_on_error=False) + + # Unpack common responses (always present) ( # copied from V3 (ks_success, ks_result), @@ -2940,39 +2978,45 @@ def _query_all(self): (types_success, types_result), (functions_success, functions_result), (aggregates_success, aggregates_result), - (triggers_success, triggers_result), (indexes_success, indexes_result), (views_success, views_result), # V4-only responses (virtual_ks_success, virtual_ks_result), (virtual_table_success, virtual_table_result), - (virtual_column_success, virtual_column_result) - ) = responses + (virtual_column_success, virtual_column_result), + ) = responses[:11] + + # Unpack triggers response if present (Cassandra/DSE only) + if self._is_not_scylla(): + (triggers_success, triggers_result) = responses[11] # copied from V3 self.keyspaces_result = self._handle_results(ks_success, ks_result, query_msg=queries[0]) self.tables_result = self._handle_results(table_success, table_result, query_msg=queries[1]) self.columns_result = self._handle_results(col_success, col_result, query_msg=queries[2]) - self.triggers_result = self._handle_results(triggers_success, triggers_result, query_msg=queries[6]) self.types_result = self._handle_results(types_success, types_result, query_msg=queries[3]) self.functions_result = self._handle_results(functions_success, functions_result, query_msg=queries[4]) self.aggregates_result = self._handle_results(aggregates_success, aggregates_result, query_msg=queries[5]) - self.indexes_result = self._handle_results(indexes_success, indexes_result, query_msg=queries[7]) - self.views_result = self._handle_results(views_success, views_result, query_msg=queries[8]) + self.indexes_result = self._handle_results(indexes_success, indexes_result, query_msg=queries[6]) + self.views_result = self._handle_results(views_success, views_result, query_msg=queries[7]) + if self._is_not_scylla(): + self.triggers_result = self._handle_results(triggers_success, triggers_result, query_msg=queries[11]) + else: + self.triggers_result = [] # V4-only results # These tables don't exist in some DSE versions reporting 4.X so we can # ignore them if we got an error self.virtual_keyspaces_result = self._handle_results( virtual_ks_success, virtual_ks_result, - expected_failures=(InvalidRequest,), query_msg=queries[9] + expected_failures=(InvalidRequest,), query_msg=queries[8] ) self.virtual_tables_result = self._handle_results( virtual_table_success, virtual_table_result, - expected_failures=(InvalidRequest,), query_msg=queries[10] + expected_failures=(InvalidRequest,), query_msg=queries[9] ) self.virtual_columns_result = self._handle_results( virtual_column_success, virtual_column_result, - expected_failures=(InvalidRequest,), query_msg=queries[11] + expected_failures=(InvalidRequest,), query_msg=queries[10] ) self._aggregate_results() From c3e237862c5e09eec532b0ec5edb013060c94360 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:41:02 +0000 Subject: [PATCH 243/298] Fix code quality issues in test_cluster.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix spelling: 'tring' → 'string' in docstring - Remove extra 't' at end of comment - Refactor complex list comprehension for clarity - Use 'is None' instead of '== None' for None comparison Co-authored-by: mykaul <4655593+mykaul@users.noreply.github.com> --- tests/unit/test_cluster.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 49208ac53e..295fe769c5 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -91,7 +91,10 @@ class ClusterTest(unittest.TestCase): def test_tuple_for_contact_points(self): cluster = Cluster(contact_points=[('localhost', 9045), ('127.0.0.2', 9046), '127.0.0.3'], port=9999) - localhost_addr = set([addr[0] for addr in [t for (_,_,_,_,t) in socket.getaddrinfo("localhost",80)]]) + # Refactored for clarity + addr_info = socket.getaddrinfo("localhost", 80) + sockaddr_tuples = [info[4] for info in addr_info] # info[4] is sockaddr + localhost_addr = set([sockaddr[0] for sockaddr in sockaddr_tuples]) for cp in cluster.endpoints_resolved: if cp.address in localhost_addr: assert cp.port == 9045 @@ -108,7 +111,7 @@ def test_invalid_contact_point_types(self): Cluster(contact_points="not a sequence", protocol_version=4, connect_timeout=1) def test_port_str(self): - """Check port passed as tring is converted and checked properly""" + """Check port passed as string is converted and checked properly""" cluster = Cluster(contact_points=['127.0.0.1'], port='1111') for cp in cluster.endpoints_resolved: if cp.address in ('::1', '127.0.0.1'): @@ -182,7 +185,7 @@ def test_event_delay_timing(self, *_): """ sched = _Scheduler(None) sched.schedule(0, lambda: None) - sched.schedule(0, lambda: None) # pre-473: "TypeError: unorderable types: function() < function()"t + sched.schedule(0, lambda: None) # pre-473: "TypeError: unorderable types: function() < function()" class SessionTest(unittest.TestCase): @@ -292,7 +295,7 @@ def test_default_exec_parameters(self): assert cluster.profile_manager.default.request_timeout == 10.0 assert session.default_consistency_level == ConsistencyLevel.LOCAL_ONE assert cluster.profile_manager.default.consistency_level == ConsistencyLevel.LOCAL_ONE - assert session.default_serial_consistency_level == None + assert session.default_serial_consistency_level is None assert cluster.profile_manager.default.serial_consistency_level == None assert session.row_factory == named_tuple_factory assert cluster.profile_manager.default.row_factory == named_tuple_factory From 8e6c4d4e773a8dffc8bf6515a13cbfef3bb5d0ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:42:07 +0000 Subject: [PATCH 244/298] Fix additional '== None' comparison for consistency Co-authored-by: mykaul <4655593+mykaul@users.noreply.github.com> --- tests/unit/test_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 295fe769c5..4942fd4d69 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -296,7 +296,7 @@ def test_default_exec_parameters(self): assert session.default_consistency_level == ConsistencyLevel.LOCAL_ONE assert cluster.profile_manager.default.consistency_level == ConsistencyLevel.LOCAL_ONE assert session.default_serial_consistency_level is None - assert cluster.profile_manager.default.serial_consistency_level == None + assert cluster.profile_manager.default.serial_consistency_level is None assert session.row_factory == named_tuple_factory assert cluster.profile_manager.default.row_factory == named_tuple_factory From caa98b60998729b97ad1a375ea1c7723e1728161 Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Sat, 4 Apr 2026 18:57:39 +0300 Subject: [PATCH 245/298] pool: drop per-query connection log The pool module emits a DEBUG log message when selecting a connection for a query. Emitting a log message for every query is too noisy. Since Python logging lacks a TRACE level, just remove the log. --- cassandra/pool.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cassandra/pool.py b/cassandra/pool.py index 2da657256f..227e1b5315 100644 --- a/cassandra/pool.py +++ b/cassandra/pool.py @@ -476,12 +476,6 @@ def _get_connection_for_routing_key(self, routing_key=None, keyspace=None, table # optimistic try to connect to it if shard_id is not None: if conn: - log.debug( - "Using connection to shard_id=%i on host %s for routing_key=%s", - shard_id, - self.host, - routing_key - ) if conn.orphaned_threshold_reached and shard_id not in self._connecting: # The connection has met its orphaned stream ID limit # and needs to be replaced. Start opening a connection From e10bf39f0497fd5ca2330c8967de0da582fe4085 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sun, 22 Mar 2026 17:07:17 +0200 Subject: [PATCH 246/298] Fix CQL injection in Connection.set_keyspace_blocking and set_keyspace_async Escape double quotes in keyspace names when constructing USE statements to prevent CQL injection. A keyspace name containing '"' would produce malformed or injectable CQL (e.g., USE "foo"bar"). This is the Python equivalent of the vulnerability fixed in the Go driver (gocql#783). The fix escapes '"' as '""' per CQL quoted-identifier rules, matching the existing escape_name() function in cassandra/metadata.py. --- cassandra/connection.py | 6 ++++-- tests/unit/test_connection.py | 37 ++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/cassandra/connection.py b/cassandra/connection.py index 72b273ec37..c045b36cb3 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -1658,7 +1658,8 @@ def set_keyspace_blocking(self, keyspace): if not keyspace or keyspace == self.keyspace: return - query = QueryMessage(query='USE "%s"' % (keyspace,), + from cassandra.metadata import escape_name + query = QueryMessage(query='USE %s' % (escape_name(keyspace),), consistency_level=ConsistencyLevel.ONE) try: result = self.wait_for_response(query) @@ -1712,7 +1713,8 @@ def set_keyspace_async(self, keyspace, callback): callback(self, None) return - query = QueryMessage(query='USE "%s"' % (keyspace,), + from cassandra.metadata import escape_name + query = QueryMessage(query='USE %s' % (escape_name(keyspace),), consistency_level=ConsistencyLevel.ONE) def process_result(result): diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 6ac63ff761..a67b7e4678 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -25,7 +25,8 @@ ConnectionException, ConnectionShutdown, DefaultEndPoint, ShardAwarePortGenerator) from cassandra.marshal import uint8_pack, uint32_pack, int32_pack from cassandra.protocol import (write_stringmultimap, write_int, write_string, - SupportedMessage, ProtocolHandler) + SupportedMessage, ProtocolHandler, ResultMessage, + RESULT_KIND_SET_KEYSPACE) from tests.util import wait_until, assertRegex import pytest @@ -256,6 +257,40 @@ def test_set_keyspace_blocking(self): c.set_keyspace_blocking('ks') assert c.keyspace == 'ks' + def test_set_keyspace_blocking_escapes_quotes(self): + """ + Test that set_keyspace_blocking properly escapes double quotes in + keyspace names to prevent CQL injection. This is the Python equivalent + of the vulnerability fixed in the Go driver: + https://github.com/scylladb/gocql/pull/783 + """ + c = self.make_connection() + c.wait_for_response = Mock(return_value=ResultMessage(kind=RESULT_KIND_SET_KEYSPACE)) + + c.set_keyspace_blocking('my"ks') + query_msg = c.wait_for_response.call_args[0][0] + assert query_msg.query == 'USE "my""ks"', ( + "Double quotes in keyspace name must be escaped as double-double quotes") + + def test_set_keyspace_async_escapes_quotes(self): + """ + Test that set_keyspace_async properly escapes double quotes in + keyspace names to prevent CQL injection. + """ + c = self.make_connection() + c.lock = Lock() + c.in_flight = 0 + c.max_request_id = 100 + c.get_request_id = Mock(return_value=1) + c.send_msg = Mock() + + callback = Mock() + c.set_keyspace_async('my"ks', callback) + + query_msg = c.send_msg.call_args[0][0] + assert query_msg.query == 'USE "my""ks"', ( + "Double quotes in keyspace name must be escaped as double-double quotes") + def test_set_connection_class(self): cluster = Cluster(connection_class='test') assert 'test' == cluster.connection_class From 4502520634b608a3adcf5822333c0a2fcf4e16d9 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Wed, 25 Mar 2026 17:26:29 +0200 Subject: [PATCH 247/298] Add unit test for Session.set_keyspace double-quote escaping Test that Session.set_keyspace properly escapes double quotes in keyspace names (e.g. 'my"ks' -> USE "my""ks") to prevent CQL injection. Also verifies simple keyspace names are not unnecessarily quoted. Requested in review of PR #758. --- tests/unit/test_cluster.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 4942fd4d69..872d133b28 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -254,6 +254,33 @@ def test_default_serial_consistency_level_legacy(self, *_): assert f.message.serial_consistency_level == cl_override + + @mock_session_pools + def test_set_keyspace_escapes_quotes(self, *_): + """ + Test that Session.set_keyspace properly escapes double quotes in + keyspace names to prevent CQL injection. + Requested in review of PR #758. + """ + c = Cluster(protocol_version=4) + s = Session(c, [Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())]) + c.connection_class.initialize_reactor() + + s.execute = Mock() + + s.set_keyspace('my"ks') + query = s.execute.call_args[0][0] + assert query == 'USE "my""ks"', ( + "Double quotes in keyspace name must be escaped as double-double quotes, " + "got: %r" % query) + + # Also verify a simple keyspace name doesn't get unnecessarily quoted + s.execute.reset_mock() + s.set_keyspace('simple_ks') + query = s.execute.call_args[0][0] + assert query == 'USE simple_ks', ( + "Simple keyspace names should not be quoted, got: %r" % query) + class ProtocolVersionTests(unittest.TestCase): def test_protocol_downgrade_test(self): From 117abb2b545b33875bb4607bd1d996ddf686aeab Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sat, 28 Mar 2026 15:26:56 +0300 Subject: [PATCH 248/298] tests: fix NLB replacement test bootstrap crash due to missing rackdc properties The _bootstrap_node() method in TestFullNodeReplacementThroughNlb calls ccm_cluster.add() without passing data_center or rack. CCM does not infer these from existing nodes, so the new node's cassandra-rackdc.properties file is left with only template comments. Scylla's GossipingPropertyFileSnitch fails to parse the empty file and crashes on startup with 'locator::bad_property_file_error'. Fix by reading data_center/rack from an existing cluster node and passing them explicitly to ccm_cluster.add(). This test was added in PR #706 with a @skip_scylla_version_lt(2026.1.0) decorator and CI runs Scylla 2025.2, so the bug was never caught. --- tests/integration/standard/test_client_routes.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/integration/standard/test_client_routes.py b/tests/integration/standard/test_client_routes.py index a8a3c30f2c..4e328df0c0 100644 --- a/tests/integration/standard/test_client_routes.py +++ b/tests/integration/standard/test_client_routes.py @@ -1178,7 +1178,7 @@ def test_should_survive_full_node_replacement_through_nlb(self): ccm_cluster = get_cluster() for node_id in new_node_ids: - self._bootstrap_node(ccm_cluster, node_id) + self._bootstrap_node(ccm_cluster, node_id, data_center='dc1') expected_total = len(original_node_ids) + len(new_node_ids) self._wait_for_condition( @@ -1283,7 +1283,7 @@ def _query_succeeds(self, session): except Exception: return False - def _bootstrap_node(self, ccm_cluster, node_id): + def _bootstrap_node(self, ccm_cluster, node_id, data_center=None, rack=None): node_type = type(next(iter(ccm_cluster.nodes.values()))) ip = "127.0.0.%d" % node_id node_instance = node_type( @@ -1297,7 +1297,12 @@ def _bootstrap_node(self, ccm_cluster, node_id): remote_debug_port=0, initial_token=None, ) - ccm_cluster.add(node_instance, is_seed=False) + # CCM requires explicit data_center/rack when adding a node so that + # cassandra-rackdc.properties is written correctly. Without this the + # snitch fails to parse the empty properties file and the node crashes + # on startup. + ccm_cluster.add(node_instance, is_seed=False, + data_center=data_center, rack=rack) node_instance.start(wait_for_binary_proto=True, wait_other_notice=True) wait_for_node_socket(node_instance, 120) log.info("Node %d bootstrapped successfully", node_id) From f7e9bc20ca723786557bf852e2203480b6dfc71a Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Tue, 7 Apr 2026 19:09:19 +0300 Subject: [PATCH 249/298] Fix ExponentialBackoffRetryPolicy.__init__ missing self in super() call super(ExponentialBackoffRetryPolicy).__init__() creates an unbound super object, so the parent RetryPolicy.__init__() is never actually invoked. Fix by passing self: super(ExponentialBackoffRetryPolicy, self).__init__(). --- cassandra/policies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cassandra/policies.py b/cassandra/policies.py index e742708019..ceb5ebdc45 100644 --- a/cassandra/policies.py +++ b/cassandra/policies.py @@ -1158,7 +1158,7 @@ def __init__(self, max_num_retries: float, min_interval: float = 0.1, max_interv self.min_interval = min_interval self.max_num_retries = max_num_retries self.max_interval = max_interval - super(ExponentialBackoffRetryPolicy).__init__(*args, **kwargs) + super(ExponentialBackoffRetryPolicy, self).__init__(*args, **kwargs) def _calculate_backoff(self, attempt: int): delay = min(self.max_interval, self.min_interval * 2 ** attempt) From 62cfd1fcfb6431b3fb3d87b03c5e6374e719e720 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Tue, 7 Apr 2026 21:46:40 +0300 Subject: [PATCH 250/298] tests: detect stale Cython extensions at test startup Add a pytest_configure hook in tests/conftest.py that compares mtime of each compiled extension against its .py source and warns when the source is newer. This prevents silently testing stale compiled code after editing a Cython-compiled module without rebuilding. The scan iterates over .py source files and checks for the first matching compiled extension per importlib.machinery.EXTENSION_SUFFIXES order, mirroring Python's import machinery and handling both .so (POSIX) and .pyd (Windows) automatically. Also document the rebuild requirement in CONTRIBUTING.rst, using uv commands instead of deprecated setup.py invocations. --- CONTRIBUTING.rst | 13 +++++++++ tests/conftest.py | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 tests/conftest.py diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 8b8fc0e791..82bf21e52f 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -40,6 +40,19 @@ When modifying driver files, rebuilding Cython modules is often necessary. Without caching, each such rebuild may take over a minute. Caching usually brings it down to about 2-3 seconds. +**Important:** After modifying any ``.py`` file under ``cassandra/`` that is +Cython-compiled (such as ``query.py``, ``protocol.py``, ``cluster.py``, etc.), +extensions must be rebuilt before running tests. If you always use ``uv run`` +(e.g. ``uv run pytest``), this is handled automatically via the ``cache-keys`` +configuration in ``pyproject.toml``. If you invoke ``pytest`` directly, you can +rebuild with:: + + uv sync --reinstall-package scylla-driver + +Without rebuilding, Python will load the stale compiled extension (``.so`` / ``.pyd``) +instead of your modified ``.py`` source, and your changes will not actually be tested. +The test suite will emit a warning if it detects this situation. + Building the Docs ================= diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000..8fd2fc923b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,67 @@ +# Copyright ScyllaDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib.machinery +import os +import warnings + +# Directory containing the Cython-compiled driver modules. +_CASSANDRA_DIR = os.path.join(os.path.dirname(__file__), os.pardir, "cassandra") + + +def pytest_configure(config): + """Warn when a compiled Cython extension is older than its .py source. + + Python's import system prefers compiled extensions (.so / .pyd) over pure + Python (.py) files. If a developer edits a .py file without rebuilding + the Cython extensions, the tests + will silently run the *old* compiled code, masking any regressions in the + Python source. + + This hook detects such staleness at test-session startup so the developer + is alerted immediately. + """ + stale = [] + # Iterate over .py sources and, for each module, look for the first + # existing compiled extension in EXTENSION_SUFFIXES order. This mirrors + # how Python's import machinery selects an extension module, and avoids + # globbing patterns like "*{suffix}" that can pick up ABI-tagged + # extensions built for other Python versions. + if os.path.isdir(_CASSANDRA_DIR): + for entry in os.listdir(_CASSANDRA_DIR): + if not entry.endswith(".py"): + continue + module_name, _ = os.path.splitext(entry) + py_path = os.path.join(_CASSANDRA_DIR, entry) + # For this module, find the first extension file Python would load. + for suffix in importlib.machinery.EXTENSION_SUFFIXES: + ext_path = os.path.join(_CASSANDRA_DIR, module_name + suffix) + if not os.path.exists(ext_path): + continue + if os.path.getmtime(py_path) > os.path.getmtime(ext_path): + stale.append((module_name, ext_path, py_path)) + # Only consider the first matching suffix; this is the one + # the import system would actually use. + break + + if stale: + names = ", ".join(m for m, _, _ in stale) + warnings.warn( + f"Stale Cython extension(s) detected: {names}. " + f"The .py source is newer than the compiled extension — tests " + f"will run the OLD compiled code, not your latest changes. " + f"Rebuild with: uv sync --reinstall-package scylla-driver\n" + f"Or use 'uv run pytest' which handles rebuilds automatically.", + stacklevel=1, + ) From 442074c1743378d7cbc631be7fd137f636d7373f Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Tue, 7 Apr 2026 21:46:46 +0300 Subject: [PATCH 251/298] docs: replace direct setup.py invocations with pip in installation guide Replace all 'python setup.py install' instructions with 'pip install .' or 'pip install scylla-driver' equivalents. Replace setup.py-specific command-line flags (--no-cython, --no-extensions, etc.) with their environment variable equivalents (CASS_DRIVER_NO_CYTHON, CASS_DRIVER_NO_EXTENSIONS, CASS_DRIVER_NO_LIBEV). Remove deprecated pip --install-option usage. --- docs/installation.rst | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 7b4823b832..fbb9ac4043 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -62,9 +62,6 @@ threads used to build the driver and any C extensions: .. code-block:: bash - $ # installing from source - $ CASS_DRIVER_BUILD_CONCURRENCY=8 python setup.py install - $ # installing from pip $ CASS_DRIVER_BUILD_CONCURRENCY=8 pip install scylla-driver Note that by default (when CASS_DRIVER_BUILD_CONCURRENCY is not specified), concurrency will be equal to the number of @@ -108,7 +105,7 @@ installed. You can find the list of dependencies in Once the dependencies are installed, simply run:: - python setup.py install + pip install . (*Optional*) Non-python Dependencies @@ -122,9 +119,9 @@ for token-aware routing with the ``Murmur3Partitioner``, `libev `_ event loop integration, and Cython optimized extensions. -When installing manually through setup.py, you can disable both with -the ``--no-extensions`` option, or selectively disable them with -with ``--no-murmur3``, ``--no-libev``, or ``--no-cython``. +Extensions can be selectively disabled using environment variables: +``CASS_DRIVER_NO_EXTENSIONS=1`` (disable all), ``CASS_DRIVER_NO_CYTHON=1``, +or ``CASS_DRIVER_NO_LIBEV=1``. To compile the extensions, ensure that GCC and the Python headers are available. @@ -149,31 +146,25 @@ This is not a hard requirement, but is engaged by default to build extensions of pure Python implementation. This is a costly build phase, especially in clean environments where the Cython compiler must be built -This build phase can be avoided using the build switch, or an environment variable:: +This build phase can be avoided using an environment variable:: - python setup.py install --no-cython + CASS_DRIVER_NO_CYTHON=1 pip install scylla-driver -Alternatively, an environment variable can be used to switch this option regardless of +Alternatively, the environment variable can be used to switch this option regardless of context:: CASS_DRIVER_NO_CYTHON=1 - or, to disable all extensions: CASS_DRIVER_NO_EXTENSIONS=1 -This method is required when using pip, which provides no other way of injecting user options in a single command:: - - CASS_DRIVER_NO_CYTHON=1 pip install scylla-driver - CASS_DRIVER_NO_CYTHON=1 sudo -E pip install ~/python-driver - -The environment variable is the preferred option because it spans all invocations of setup.py, and will +These environment variables are the preferred option, and will prevent Cython from being materialized as a setup requirement. -If your sudo configuration does not allow SETENV, you must push the option flag down via pip. However, pip -applies these options to all dependencies (which break on the custom flag). Therefore, you must first install -dependencies, then use install-option:: +If your sudo configuration does not allow SETENV, you must first install +dependencies, then install the driver:: sudo pip install futures - sudo pip install --install-option="--no-cython" + sudo CASS_DRIVER_NO_CYTHON=1 pip install scylla-driver Supported Event Loops @@ -205,7 +196,7 @@ install libev using any Windows package manager. For example, to install using $ vcpkg install libev If successful, you should be able to build and install the extension -(just using ``setup.py build`` or ``setup.py install``) and then use +(just using ``pip install .``) and then use the libev event loop by doing the following: .. code-block:: python From ee98fd0413994ee31345db7db1e5f2b608418a74 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 27 Mar 2026 11:14:12 +0300 Subject: [PATCH 252/298] tests: fix incorrect retry count in execute_with_long_wait_retry error message The error message said 'Failed after 100 attempts' but the retry limit is 10 (while tries < 10). This was a copy-paste error from execute_until_pass() which does retry 100 times. --- tests/integration/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index 2015e0663f..286561c291 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -600,7 +600,7 @@ def execute_with_long_wait_retry(session, query, timeout=30): del tb tries += 1 - raise RuntimeError("Failed to execute query after 100 attempts: {0}".format(query)) + raise RuntimeError("Failed to execute query after 10 attempts: {0}".format(query)) def execute_with_retry_tolerant(session, query, retry_exceptions, escape_exception): From f7890b912c7cebbbc8f4e16368bdb8091c96553b Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 27 Mar 2026 11:20:03 +0300 Subject: [PATCH 253/298] tests: standardize test_cluster.py to --smp 2 Change test_cluster.py from --smp 1 to --smp 2 to match the standard configuration used by other test files. This enables cluster topology consolidation in a follow-up commit. --- tests/integration/standard/test_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index aab4131739..6db9657932 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -52,7 +52,7 @@ def setup_module(): - os.environ['SCYLLA_EXT_OPTS'] = "--smp 1" + os.environ['SCYLLA_EXT_OPTS'] = "--smp 2" use_cluster("cluster_tests", [3], start=True, workloads=None) warnings.simplefilter("always") From aa0043a3add6829b8b6d5022be7357ef4b85bbfc Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 27 Mar 2026 11:23:18 +0300 Subject: [PATCH 254/298] tests: consolidate cluster topologies to reduce cluster teardown/setup Merge cluster names for test files with identical configurations: - test_shard_aware.py: 'shard_aware' -> 'cluster_tests' (same --smp 2, 3 nodes as test_cluster.py) - test_client_routes.py: 'test_client_routes' -> 'shared_aware' (same --smp 2 --memory 2048M, 3 nodes as test_use_keyspace.py) This allows the CCM cluster to be reused when these tests run sequentially, avoiding a full cluster teardown and restart. Also update conftest.py cleanup list to include 'cluster_tests' and 'test_client_routes_replacement' which were previously missing. --- tests/integration/conftest.py | 2 +- tests/integration/standard/test_client_routes.py | 2 +- tests/integration/standard/test_shard_aware.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index a682bcb608..5db8026675 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -17,7 +17,7 @@ def cleanup_clusters(): if not os.environ.get('DISABLE_CLUSTER_CLEANUP'): for cluster_name in [CLUSTER_NAME, SINGLE_NODE_CLUSTER_NAME, MULTIDC_CLUSTER_NAME, - 'shared_aware', 'sni_proxy', 'test_ip_change']: + 'cluster_tests', 'shared_aware', 'sni_proxy', 'test_ip_change', 'test_client_routes_replacement']: try: cluster = CCMClusterFactory.load(ccm_path, cluster_name) logging.debug("Using external CCM cluster {0}".format(cluster.name)) diff --git a/tests/integration/standard/test_client_routes.py b/tests/integration/standard/test_client_routes.py index 4e328df0c0..a799073e25 100644 --- a/tests/integration/standard/test_client_routes.py +++ b/tests/integration/standard/test_client_routes.py @@ -521,7 +521,7 @@ def assert_routes_direct(test, cluster, expected_node_ids, direct_port=9042): def setup_module(): os.environ['SCYLLA_EXT_OPTS'] = "--smp 2 --memory 2048M" - use_cluster('test_client_routes', [3], start=True) + use_cluster('shared_aware', [3], start=True) @skip_scylla_version_lt(reason='scylladb/scylladb#26992 - system.client_routes is not yet supported', scylla_version="2026.1.0") diff --git a/tests/integration/standard/test_shard_aware.py b/tests/integration/standard/test_shard_aware.py index 2d764d681e..0fdb9ed08d 100644 --- a/tests/integration/standard/test_shard_aware.py +++ b/tests/integration/standard/test_shard_aware.py @@ -33,7 +33,7 @@ def setup_module(): os.environ['SCYLLA_EXT_OPTS'] = "--smp 2" - use_cluster('shard_aware', [3], start=True) + use_cluster('cluster_tests', [3], start=True) class TestShardAwareIntegration(unittest.TestCase): From dd15509a5c0463614eba7b2704e006edf5f3fc68 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 27 Mar 2026 11:24:22 +0300 Subject: [PATCH 255/298] tests: add test ordering by cluster topology to minimize restarts Add pytest_collection_modifyitems hook that sorts test modules by their cluster configuration group. This ensures tests sharing the same CCM cluster (same name, same node count, same ext opts) run adjacently, avoiding unnecessary cluster teardown/restart cycles between modules. Groups: default singledc -> cluster_tests -> shared_aware -> single_node -> destructive/special clusters. --- tests/integration/standard/conftest.py | 65 ++++++++++++++++++- .../standard/test_rate_limit_exceeded.py | 4 +- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/tests/integration/standard/conftest.py b/tests/integration/standard/conftest.py index 6028c2a06d..3adaf371b0 100644 --- a/tests/integration/standard/conftest.py +++ b/tests/integration/standard/conftest.py @@ -1,6 +1,69 @@ import pytest import logging +# Cluster topology groups for test ordering. +# Tests are sorted so that modules sharing the same CCM cluster run +# together, minimising expensive cluster teardown/restart cycles. +# Lower number = runs first. Modules not listed get a high default. +_MODULE_CLUSTER_ORDER = { + # Group 0: default 3-node singledc (CLUSTER_NAME = 'test_cluster') + "test_metadata": 0, + "test_policies": 0, + "test_control_connection": 0, + "test_routing": 0, + "test_prepared_statements": 0, + "test_metrics": 0, + "test_connection": 0, + "test_concurrent": 0, + "test_custom_payload": 0, + "test_query_paging": 0, + "test_single_interface": 0, + "test_rate_limit_exceeded": 0, + # Group 1: 'cluster_tests' (--smp 2, 3 nodes) + "test_cluster": 1, + "test_shard_aware": 1, + # Group 2: 'shared_aware' (--smp 2 --memory 2048M, 3 nodes) + "test_use_keyspace": 2, + "test_client_routes": 2, + # Group 3: single-node cluster + "test_types": 3, + "test_cython_protocol_handlers": 3, + "test_custom_protocol_handler": 3, + "test_row_factories": 3, + "test_udts": 3, + "test_client_warnings": 3, + "test_application_info": 3, + # Group 4: destructive / special clusters (run last) + "test_ip_change": 4, + "test_authentication": 4, + "test_authentication_misconfiguration": 4, + "test_custom_cluster": 4, + "test_query": 4, + # Group 5: tablets (destructive — decommissions a node) + "test_tablets": 5, + # Group 6: schema change + node kill (destructive — kills node2) + "test_concurrent_schema_change_and_node_kill": 6, + # Group 7: multi-dc (7 nodes — most expensive to create) + "test_rack_aware_policy": 7, +} + + +def pytest_collection_modifyitems(items): + """Sort tests so modules with the same cluster topology are adjacent. + + Uses the original collection index as tie-breaker so that the + definition order inside each file is preserved (important for tests + that depend on running order, e.g. destructive tablet tests). + """ + orig_order = {id(item): idx for idx, item in enumerate(items)} + + def _sort_key(item): + module_name = item.module.__name__.rsplit(".", 1)[-1] + return (_MODULE_CLUSTER_ORDER.get(module_name, 99), item.fspath, orig_order[id(item)]) + + items[:] = sorted(items, key=_sort_key) + + # from https://github.com/streamlit/streamlit/pull/5047/files def pytest_sessionfinish(): # We're not waiting for scriptrunner threads to cleanly close before ending the PyTest, @@ -10,4 +73,4 @@ def pytest_sessionfinish(): # * https://github.com/pytest-dev/pytest/issues/5282 # To prevent the exception from being raised on pytest_sessionfinish # we disable exception raising in logging module - logging.raiseExceptions = False \ No newline at end of file + logging.raiseExceptions = False diff --git a/tests/integration/standard/test_rate_limit_exceeded.py b/tests/integration/standard/test_rate_limit_exceeded.py index 211f0c9930..ea7dfc7d61 100644 --- a/tests/integration/standard/test_rate_limit_exceeded.py +++ b/tests/integration/standard/test_rate_limit_exceeded.py @@ -4,13 +4,13 @@ from cassandra.cluster import Cluster from cassandra.policies import ConstantReconnectionPolicy, RoundRobinPolicy, TokenAwarePolicy -from tests.integration import PROTOCOL_VERSION, use_cluster +from tests.integration import PROTOCOL_VERSION, use_singledc import pytest LOGGER = logging.getLogger(__name__) def setup_module(): - use_cluster('rate_limit', [3], start=True) + use_singledc() class TestRateLimitExceededException(unittest.TestCase): @classmethod From b038f4fb6957e13894f7a5da5c43f741c99f8097 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 27 Mar 2026 11:25:39 +0300 Subject: [PATCH 256/298] tests: switch 6 test files from 3-node to single-node cluster These test files don't require multiple nodes for their test logic (they test data types, protocol handlers, row factories, UDTs, and client warnings). Using a single node reduces resource usage and cluster startup time. Files switched from use_singledc() to use_single_node(): - test_types.py - test_cython_protocol_handlers.py - test_custom_protocol_handler.py - test_row_factories.py - test_udts.py - test_client_warnings.py --- tests/integration/standard/test_client_warnings.py | 4 ++-- tests/integration/standard/test_custom_protocol_handler.py | 4 ++-- tests/integration/standard/test_cython_protocol_handlers.py | 4 ++-- tests/integration/standard/test_row_factories.py | 4 ++-- tests/integration/standard/test_types.py | 4 ++-- tests/integration/standard/test_udts.py | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/integration/standard/test_client_warnings.py b/tests/integration/standard/test_client_warnings.py index 781b5b7860..c18fa8cb1f 100644 --- a/tests/integration/standard/test_client_warnings.py +++ b/tests/integration/standard/test_client_warnings.py @@ -17,13 +17,13 @@ from cassandra.query import BatchStatement -from tests.integration import (use_singledc, PROTOCOL_VERSION, local, TestCluster, +from tests.integration import (use_single_node, PROTOCOL_VERSION, local, TestCluster, requires_custom_payload, xfail_scylla) from tests.util import assertRegex, assertDictEqual def setup_module(): - use_singledc() + use_single_node() @xfail_scylla('scylladb/scylladb#10196 - scylla does not report warnings') class ClientWarningTests(unittest.TestCase): diff --git a/tests/integration/standard/test_custom_protocol_handler.py b/tests/integration/standard/test_custom_protocol_handler.py index 239f7e7336..e123f2050e 100644 --- a/tests/integration/standard/test_custom_protocol_handler.py +++ b/tests/integration/standard/test_custom_protocol_handler.py @@ -20,7 +20,7 @@ ContinuousPagingOptions, NoHostAvailable) from cassandra import ProtocolVersion, ConsistencyLevel -from tests.integration import use_singledc, drop_keyspace_shutdown_cluster, \ +from tests.integration import use_single_node, drop_keyspace_shutdown_cluster, \ greaterthanorequalcass30, execute_with_long_wait_retry, greaterthanorequalcass3_10, \ TestCluster, greaterthanorequalcass40 from tests.integration.datatype_utils import update_datatypes, PRIMITIVE_DATATYPES @@ -32,7 +32,7 @@ def setup_module(): - use_singledc() + use_single_node() update_datatypes() diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index f44d613c64..9c94b2ac77 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -12,7 +12,7 @@ from cassandra.protocol import ProtocolHandler, LazyProtocolHandler, NumpyProtocolHandler from cassandra.query import tuple_factory from tests import VERIFY_CYTHON -from tests.integration import use_singledc, notprotocolv1, \ +from tests.integration import use_single_node, notprotocolv1, \ drop_keyspace_shutdown_cluster, BasicSharedKeyspaceUnitTestCase, greaterthancass21, TestCluster from tests.integration.datatype_utils import update_datatypes from tests.integration.standard.utils import ( @@ -21,7 +21,7 @@ def setup_module(): - use_singledc() + use_single_node() update_datatypes() diff --git a/tests/integration/standard/test_row_factories.py b/tests/integration/standard/test_row_factories.py index 187f35704a..818f11c061 100644 --- a/tests/integration/standard/test_row_factories.py +++ b/tests/integration/standard/test_row_factories.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from tests.integration import get_server_versions, use_singledc, \ +from tests.integration import get_server_versions, use_single_node, \ BasicSharedKeyspaceUnitTestCaseWFunctionTable, BasicSharedKeyspaceUnitTestCase, execute_until_pass, TestCluster import unittest @@ -24,7 +24,7 @@ def setup_module(): - use_singledc() + use_single_node() class NameTupleFactory(BasicSharedKeyspaceUnitTestCase): diff --git a/tests/integration/standard/test_types.py b/tests/integration/standard/test_types.py index 1d66ce1ed9..559a6b3da0 100644 --- a/tests/integration/standard/test_types.py +++ b/tests/integration/standard/test_types.py @@ -38,7 +38,7 @@ from tests.unit.cython.utils import cythontest from tests.util import assertEqual -from tests.integration import use_singledc, execute_until_pass, notprotocolv1, \ +from tests.integration import use_single_node, execute_until_pass, notprotocolv1, \ BasicSharedKeyspaceUnitTestCase, greaterthancass21, lessthancass30, \ greaterthanorequalcass3_10, TestCluster, requires_composite_type, \ requires_vector_type @@ -48,7 +48,7 @@ def setup_module(): - use_singledc() + use_single_node() update_datatypes() diff --git a/tests/integration/standard/test_udts.py b/tests/integration/standard/test_udts.py index dd696ea0e9..e608a9610b 100644 --- a/tests/integration/standard/test_udts.py +++ b/tests/integration/standard/test_udts.py @@ -21,7 +21,7 @@ from cassandra.query import dict_factory from cassandra.util import OrderedMap -from tests.integration import use_singledc, execute_until_pass, \ +from tests.integration import use_single_node, execute_until_pass, \ BasicSegregatedKeyspaceUnitTestCase, greaterthancass20, lessthancass30, greaterthanorequalcass36, TestCluster from tests.integration.datatype_utils import update_datatypes, PRIMITIVE_DATATYPES, PRIMITIVE_DATATYPES_KEYS, \ COLLECTION_TYPES, get_sample, get_collection_sample @@ -32,7 +32,7 @@ def setup_module(): - use_singledc() + use_single_node() update_datatypes() From ca0758df60154d729e50305a03a150fe3d02af63 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 27 Mar 2026 11:27:50 +0300 Subject: [PATCH 257/298] tests: reduce cluster churn in LoadBalancingPolicyTests Move remove_cluster() from setUp (which ran before every test) to only the destructive test methods that actually need a fresh cluster. Read-only tests (test_token_aware_is_used_by_default, test_token_aware_composite_key, test_token_aware_with_local_table, test_dc_aware_roundrobin_two_dcs, test_dc_aware_roundrobin_two_dcs_2) can now reuse an existing cluster, avoiding 5 unnecessary cluster teardown/startup cycles. --- .../integration/long/test_loadbalancingpolicies.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/integration/long/test_loadbalancingpolicies.py b/tests/integration/long/test_loadbalancingpolicies.py index fd8edde14c..072786dc23 100644 --- a/tests/integration/long/test_loadbalancingpolicies.py +++ b/tests/integration/long/test_loadbalancingpolicies.py @@ -45,7 +45,6 @@ class LoadBalancingPolicyTests(unittest.TestCase): def setUp(self): - remove_cluster() # clear ahead of test so it doesn't use one left in unknown state self.coordinator_stats = CoordinatorStats() self.prepared = None self.probe_cluster = None @@ -191,6 +190,7 @@ def test_token_aware_is_used_by_default(self): assert isinstance(cluster.profile_manager.default.load_balancing_policy, DCAwareRoundRobinPolicy) def test_roundrobin(self): + remove_cluster() use_singledc() keyspace = 'test_roundrobin' cluster, session = self._cluster_session_with_lbp(RoundRobinPolicy()) @@ -228,6 +228,7 @@ def test_roundrobin(self): self.coordinator_stats.assert_query_count_equals(3, 6) def test_roundrobin_two_dcs(self): + remove_cluster() use_multidc([2, 2]) keyspace = 'test_roundrobin_two_dcs' cluster, session = self._cluster_session_with_lbp(RoundRobinPolicy()) @@ -261,6 +262,7 @@ def test_roundrobin_two_dcs(self): self.coordinator_stats.assert_query_count_equals(5, 3) def test_roundrobin_two_dcs_2(self): + remove_cluster() use_multidc([2, 2]) keyspace = 'test_roundrobin_two_dcs_2' cluster, session = self._cluster_session_with_lbp(RoundRobinPolicy()) @@ -294,6 +296,7 @@ def test_roundrobin_two_dcs_2(self): self.coordinator_stats.assert_query_count_equals(5, 3) def test_dc_aware_roundrobin_two_dcs(self): + remove_cluster() use_multidc([3, 2]) keyspace = 'test_dc_aware_roundrobin_two_dcs' cluster, session = self._cluster_session_with_lbp(DCAwareRoundRobinPolicy('dc1')) @@ -311,6 +314,7 @@ def test_dc_aware_roundrobin_two_dcs(self): self.coordinator_stats.assert_query_count_equals(5, 0) def test_dc_aware_roundrobin_two_dcs_2(self): + remove_cluster() use_multidc([3, 2]) keyspace = 'test_dc_aware_roundrobin_two_dcs_2' cluster, session = self._cluster_session_with_lbp(DCAwareRoundRobinPolicy('dc2')) @@ -328,6 +332,7 @@ def test_dc_aware_roundrobin_two_dcs_2(self): self.coordinator_stats.assert_query_count_equals(5, 6) def test_dc_aware_roundrobin_one_remote_host(self): + remove_cluster() use_multidc([2, 2]) keyspace = 'test_dc_aware_roundrobin_one_remote_host' cluster, session = self._cluster_session_with_lbp(DCAwareRoundRobinPolicy('dc2', used_hosts_per_remote_dc=1)) @@ -410,6 +415,7 @@ def test_token_aware_prepared(self): self.token_aware(keyspace, True) def token_aware(self, keyspace, use_prepared=False): + remove_cluster() use_singledc() cluster, session = self._cluster_session_with_lbp(TokenAwarePolicy(RoundRobinPolicy())) self.addCleanup(cluster.shutdown) @@ -505,6 +511,7 @@ def test_token_aware_composite_key(self): assert results[0].i def test_token_aware_with_rf_2(self, use_prepared=False): + remove_cluster() use_singledc() keyspace = 'test_token_aware_with_rf_2' cluster, session = self._cluster_session_with_lbp(TokenAwarePolicy(RoundRobinPolicy())) @@ -617,6 +624,7 @@ def test_token_aware_with_transient_replication(self): @test_category policy """ + remove_cluster() # We can test this with a single dc when CASSANDRA-15670 is fixed use_multidc([3, 3]) @@ -647,6 +655,7 @@ def test_token_aware_with_transient_replication(self): def _set_up_shuffle_test(self, keyspace, replication_factor): + remove_cluster() use_singledc() cluster, session = self._cluster_session_with_lbp( TokenAwarePolicy(RoundRobinPolicy(), shuffle_replicas=True) @@ -678,6 +687,7 @@ def _check_query_order_changes(self, session, keyspace): self.coordinator_stats.reset_counts() def test_white_list(self): + remove_cluster() use_singledc() keyspace = 'test_white_list' @@ -723,6 +733,7 @@ def test_black_list_with_host_filter_policy(self): @test_category policy """ + remove_cluster() use_singledc() keyspace = 'test_black_list_with_hfp' ignored_address = (IP_FORMAT % 2) From 226bd109439633f86b66c4c0a7e708fbfa537645 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 27 Mar 2026 12:45:54 +0300 Subject: [PATCH 258/298] tests: fix auth warning assertion for --smp 2 compatibility The test_can_connect_with_sslauth test asserted exact equality between auth warning count and ReadyMessage count. With --smp 2, shard-aware connections produce additional ReadyMessages, breaking the equality. Drop the exact equality check and assert a lower bound of >= 3 (one per node connection in a 3-node cluster). The control connection and shard-aware connections may produce additional warnings, so the actual count varies between runs. --- tests/integration/standard/test_cluster.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 6db9657932..3dd08aae07 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -720,10 +720,13 @@ def _warning_are_issued_when_auth(self, auth_provider): session = cluster.connect() assert session.execute("SELECT * from system.local WHERE key='local'") is not None - # Three conenctions to nodes plus the control connection + # Verify that auth warnings are issued for connections where + # auth is configured but the server does not send a challenge. + # At minimum one warning per node connection (3 for a 3-node + # cluster). The control connection and shard-aware connections + # may add more, so we only assert a lower bound. auth_warning = mock_handler.get_message_count('warning', "An authentication challenge was not sent") - assert auth_warning >= 4 - assert auth_warning == mock_handler.get_message_count("debug", "Got ReadyMessage on new connection") + assert auth_warning >= 3 def _wait_for_all_shard_connections(self, cluster, timeout=30): """Wait until all shard-aware connections are fully established.""" From 4eb1bfac72a1a4ecc9f303b3dc348e60584a1139 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sat, 28 Mar 2026 15:29:40 +0300 Subject: [PATCH 259/298] tests: shorten cluster name to avoid Unix socket path limit The cluster name 'test_concurrent_schema_change_and_node_kill' (43 chars) causes the maintenance socket path to exceed the 107-byte sun_path limit on Linux when the working directory is deep enough. Shorten to 'test_schema_kill' to stay well within the limit for all environments. --- .../standard/test_concurrent_schema_change_and_node_kill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/standard/test_concurrent_schema_change_and_node_kill.py b/tests/integration/standard/test_concurrent_schema_change_and_node_kill.py index aeda381c0d..910dcaa9fe 100644 --- a/tests/integration/standard/test_concurrent_schema_change_and_node_kill.py +++ b/tests/integration/standard/test_concurrent_schema_change_and_node_kill.py @@ -8,7 +8,7 @@ def setup_module(): - use_cluster('test_concurrent_schema_change_and_node_kill', [3], start=True) + use_cluster('test_schema_kill', [3], start=True) @local class TestConcurrentSchemaChangeAndNodeKill(unittest.TestCase): From f3ec8817a33acd9b3d907a181f509b271bd7d7f6 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sat, 28 Mar 2026 20:35:48 +0300 Subject: [PATCH 260/298] tests: save/restore SCYLLA_EXT_OPTS to prevent env variable leak Several test modules set SCYLLA_EXT_OPTS in setup_module() but never restore it in teardown_module(). When tests are reordered to share clusters, stale values can leak into subsequent modules and cause misconfigured clusters. Save the original value before overwriting and restore it on teardown in: - test_cluster.py - test_shard_aware.py - test_use_keyspace.py - test_ip_change.py - test_client_routes.py (module-level and TestFullNodeReplacementThroughNlb) - test_authentication.py --- .../integration/standard/test_authentication.py | 8 ++++++++ .../integration/standard/test_client_routes.py | 17 +++++++++++++++++ tests/integration/standard/test_cluster.py | 12 ++++++++++++ tests/integration/standard/test_ip_change.py | 11 +++++++++++ tests/integration/standard/test_shard_aware.py | 12 ++++++++++++ tests/integration/standard/test_use_keyspace.py | 11 +++++++++++ 6 files changed, 71 insertions(+) diff --git a/tests/integration/standard/test_authentication.py b/tests/integration/standard/test_authentication.py index 502fdf8993..f172707fff 100644 --- a/tests/integration/standard/test_authentication.py +++ b/tests/integration/standard/test_authentication.py @@ -34,8 +34,12 @@ #This can be tested for remote hosts, but the cluster has to be configured accordingly #@local +_saved_scylla_ext_opts = None + def setup_module(): + global _saved_scylla_ext_opts + _saved_scylla_ext_opts = os.environ.get('SCYLLA_EXT_OPTS') if CASSANDRA_IP.startswith("127.0.0.") and not USE_CASS_EXTERNAL: use_singledc(start=False) ccm_cluster = get_cluster() @@ -71,6 +75,10 @@ def _check_auth_ready(): def teardown_module(): remove_cluster() # this test messes with config + if _saved_scylla_ext_opts is None: + os.environ.pop('SCYLLA_EXT_OPTS', None) + else: + os.environ['SCYLLA_EXT_OPTS'] = _saved_scylla_ext_opts class AuthenticationTests(unittest.TestCase): diff --git a/tests/integration/standard/test_client_routes.py b/tests/integration/standard/test_client_routes.py index a799073e25..9471c95867 100644 --- a/tests/integration/standard/test_client_routes.py +++ b/tests/integration/standard/test_client_routes.py @@ -519,10 +519,22 @@ def assert_routes_direct(test, cluster, expected_node_ids, direct_port=9042): ) +_saved_scylla_ext_opts = None + + def setup_module(): + global _saved_scylla_ext_opts + _saved_scylla_ext_opts = os.environ.get('SCYLLA_EXT_OPTS') os.environ['SCYLLA_EXT_OPTS'] = "--smp 2 --memory 2048M" use_cluster('shared_aware', [3], start=True) + +def teardown_module(): + if _saved_scylla_ext_opts is None: + os.environ.pop('SCYLLA_EXT_OPTS', None) + else: + os.environ['SCYLLA_EXT_OPTS'] = _saved_scylla_ext_opts + @skip_scylla_version_lt(reason='scylladb/scylladb#26992 - system.client_routes is not yet supported', scylla_version="2026.1.0") class TestGetHostPortMapping(unittest.TestCase): @@ -1116,6 +1128,7 @@ class TestFullNodeReplacementThroughNlb(unittest.TestCase): @classmethod def setUpClass(cls): + cls._saved_scylla_ext_opts = os.environ.get('SCYLLA_EXT_OPTS') os.environ['SCYLLA_EXT_OPTS'] = "--smp 2 --memory 2048M" use_cluster('test_client_routes_replacement', [3], start=True) @@ -1133,6 +1146,10 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): cls.direct_cluster.shutdown() + if cls._saved_scylla_ext_opts is None: + os.environ.pop('SCYLLA_EXT_OPTS', None) + else: + os.environ['SCYLLA_EXT_OPTS'] = cls._saved_scylla_ext_opts def test_should_survive_full_node_replacement_through_nlb(self): """ diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 3dd08aae07..08b823d716 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -51,12 +51,24 @@ log = logging.getLogger(__name__) +_saved_scylla_ext_opts = None + + def setup_module(): + global _saved_scylla_ext_opts + _saved_scylla_ext_opts = os.environ.get('SCYLLA_EXT_OPTS') os.environ['SCYLLA_EXT_OPTS'] = "--smp 2" use_cluster("cluster_tests", [3], start=True, workloads=None) warnings.simplefilter("always") +def teardown_module(): + if _saved_scylla_ext_opts is None: + os.environ.pop('SCYLLA_EXT_OPTS', None) + else: + os.environ['SCYLLA_EXT_OPTS'] = _saved_scylla_ext_opts + + class IgnoredHostPolicy(RoundRobinPolicy): def __init__(self, ignored_hosts): diff --git a/tests/integration/standard/test_ip_change.py b/tests/integration/standard/test_ip_change.py index 6d23d30e04..53debfa1f5 100644 --- a/tests/integration/standard/test_ip_change.py +++ b/tests/integration/standard/test_ip_change.py @@ -10,11 +10,22 @@ LOGGER = logging.getLogger(__name__) +_saved_scylla_ext_opts = None + def setup_module(): + global _saved_scylla_ext_opts + _saved_scylla_ext_opts = os.environ.get('SCYLLA_EXT_OPTS') os.environ['SCYLLA_EXT_OPTS'] = "--smp 2 --memory 2048M" use_cluster('test_ip_change', [3], start=True) + +def teardown_module(): + if _saved_scylla_ext_opts is None: + os.environ.pop('SCYLLA_EXT_OPTS', None) + else: + os.environ['SCYLLA_EXT_OPTS'] = _saved_scylla_ext_opts + @local class TestIpAddressChange(unittest.TestCase): @classmethod diff --git a/tests/integration/standard/test_shard_aware.py b/tests/integration/standard/test_shard_aware.py index 0fdb9ed08d..d1f3e27abd 100644 --- a/tests/integration/standard/test_shard_aware.py +++ b/tests/integration/standard/test_shard_aware.py @@ -31,11 +31,23 @@ LOGGER = logging.getLogger(__name__) +_saved_scylla_ext_opts = None + + def setup_module(): + global _saved_scylla_ext_opts + _saved_scylla_ext_opts = os.environ.get('SCYLLA_EXT_OPTS') os.environ['SCYLLA_EXT_OPTS'] = "--smp 2" use_cluster('cluster_tests', [3], start=True) +def teardown_module(): + if _saved_scylla_ext_opts is None: + os.environ.pop('SCYLLA_EXT_OPTS', None) + else: + os.environ['SCYLLA_EXT_OPTS'] = _saved_scylla_ext_opts + + class TestShardAwareIntegration(unittest.TestCase): @classmethod def setup_class(cls): diff --git a/tests/integration/standard/test_use_keyspace.py b/tests/integration/standard/test_use_keyspace.py index 25e954b956..80e7cfe5f3 100644 --- a/tests/integration/standard/test_use_keyspace.py +++ b/tests/integration/standard/test_use_keyspace.py @@ -14,12 +14,23 @@ LOGGER = logging.getLogger(__name__) +_saved_scylla_ext_opts = None + def setup_module(): + global _saved_scylla_ext_opts + _saved_scylla_ext_opts = os.environ.get('SCYLLA_EXT_OPTS') os.environ['SCYLLA_EXT_OPTS'] = "--smp 2 --memory 2048M" use_cluster('shared_aware', [3], start=True) +def teardown_module(): + if _saved_scylla_ext_opts is None: + os.environ.pop('SCYLLA_EXT_OPTS', None) + else: + os.environ['SCYLLA_EXT_OPTS'] = _saved_scylla_ext_opts + + @local class TestUseKeyspace(unittest.TestCase): @classmethod From 92aa6690724969597cc6a79f70f1a1cb70c550ef Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sat, 28 Mar 2026 20:37:15 +0300 Subject: [PATCH 261/298] ci: cache Scylla download across CI matrix jobs Add an actions/cache step for ~/.ccm/repository keyed on the Scylla version and runner OS. On cache hit the 'Download Scylla' step becomes a near-instant no-op. On miss (or version bump) CCM re-downloads as before, so there is no regression risk. --- .github/workflows/integration-tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 048dbd1352..3c75a33603 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -77,6 +77,12 @@ jobs: - name: Build driver run: uv sync + - name: Cache Scylla download + uses: actions/cache@v4 + with: + path: ~/.ccm/repository + key: scylla-${{ env.SCYLLA_VERSION }}-${{ runner.os }} + # This is to get honest accounting of test time vs download time vs build time. # Not strictly necessary for running tests. - name: Download Scylla From 56498e3aafc7c90f9d5b6668c8f2c74c033a42ca Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sun, 29 Mar 2026 13:34:52 +0300 Subject: [PATCH 262/298] tests: fix flaky SSL test by increasing connect timeout and retry budget The routes_visible() polling function in TestSslThroughNlb creates a new TestCluster with SSL on every retry attempt. Under resource pressure (--smp 2 --memory 2048M shared across 3 nodes), the SSL handshake plus CQL negotiation can exceed the default 5-second connect_timeout, causing intermittent OperationTimedOut failures. Fix by passing connect_timeout=30 to TestCluster (matching the generous timeout recommended for slow-starting clusters) and increasing the wait_until_not_raised parameters from (0.5, 10) to (1, 30), consistent with other wait_until_not_raised calls in this file (lines 773, 855). --- tests/integration/standard/test_client_routes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/standard/test_client_routes.py b/tests/integration/standard/test_client_routes.py index 9471c95867..5a20421276 100644 --- a/tests/integration/standard/test_client_routes.py +++ b/tests/integration/standard/test_client_routes.py @@ -1059,7 +1059,7 @@ def test_ssl_without_hostname_verification_through_nlb(self): def routes_visible(): with TestCluster( contact_points=["127.0.0.1"], - ssl_context=ssl_ctx, + ssl_context=ssl_ctx, connect_timeout=30, ) as c: session = c.connect() rs = session.execute( @@ -1071,7 +1071,7 @@ def routes_visible(): wait_until_not_raised( lambda: self.assertTrue(routes_visible()), - 0.5, 10, + 1, 30, ) with Cluster( From db317eb3645495232664f99c4a452da67b74903e Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sun, 29 Mar 2026 16:32:08 +0300 Subject: [PATCH 263/298] tests: register custom 'last' pytest mark to suppress warning The test_tablets.py file uses @pytest.mark.last to ensure the decommission test runs last. Register this mark in pyproject.toml to eliminate the PytestUnknownMarkWarning. --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7f60ed0b2a..1335027fcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -121,6 +121,9 @@ log_level = "DEBUG" log_date_format = "%Y-%m-%d %H:%M:%S" xfail_strict = true addopts = "-rf" +markers = [ + "last: mark test to run last within its module group", +] [tool.setuptools_scm] version_file = "cassandra/_version.py" From 50941184b1c8e5a3aed2b23fc392a50bc540d63b Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Fri, 20 Mar 2026 21:03:47 +0200 Subject: [PATCH 264/298] perf: use stdlib bisect and attrgetter in tablets.py - Use bisect.bisect_left from stdlib unconditionally (C implementation); drop the bundled pure-Python fallback since we only support Python 3.10+ - Replace per-call lambda closures with module-level operator.attrgetter for first_token/last_token extraction - Add unit tests for get_tablet_for_key Benchmark results (get_tablet_for_key hit): 10 tablets: 517 ns -> 365 ns (1.42x) 100 tablets: 616 ns -> 351 ns (1.75x) 1000 tablets: 1008 ns -> 529 ns (1.91x) 10000 tablets: 1339 ns -> 610 ns (2.20x) --- cassandra/tablets.py | 48 +++++++------------------------------- tests/unit/test_tablets.py | 38 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 39 deletions(-) diff --git a/cassandra/tablets.py b/cassandra/tablets.py index dca26ab0df..96e61a50c2 100644 --- a/cassandra/tablets.py +++ b/cassandra/tablets.py @@ -1,7 +1,13 @@ +from bisect import bisect_left +from operator import attrgetter from threading import Lock from typing import Optional from uuid import UUID +# C-accelerated attrgetter avoids per-call lambda allocation overhead +_get_first_token = attrgetter("first_token") +_get_last_token = attrgetter("last_token") + class Tablet(object): """ @@ -57,7 +63,7 @@ def get_tablet_for_key(self, keyspace, table, t): if not tablet: return None - id = bisect_left(tablet, t.value, key=lambda tablet: tablet.last_token) + id = bisect_left(tablet, t.value, key=_get_last_token) if id < len(tablet) and t.value > tablet[id].first_token: return tablet[id] return None @@ -94,12 +100,12 @@ def add_tablet(self, keyspace, table, tablet): tablets_for_table = self._tablets.setdefault((keyspace, table), []) # find first overlapping range - start = bisect_left(tablets_for_table, tablet.first_token, key=lambda t: t.first_token) + start = bisect_left(tablets_for_table, tablet.first_token, key=_get_first_token) if start > 0 and tablets_for_table[start - 1].last_token > tablet.first_token: start = start - 1 # find last overlapping range - end = bisect_left(tablets_for_table, tablet.last_token, key=lambda t: t.last_token) + end = bisect_left(tablets_for_table, tablet.last_token, key=_get_last_token) if end < len(tablets_for_table) and tablets_for_table[end].first_token >= tablet.last_token: end = end - 1 @@ -108,39 +114,3 @@ def add_tablet(self, keyspace, table, tablet): tablets_for_table.insert(start, tablet) - -# bisect.bisect_left implementation from Python 3.11, needed untill support for -# Python < 3.10 is dropped, it is needed to use `key` to extract last_token from -# Tablet list - better solution performance-wise than materialize list of last_tokens -def bisect_left(a, x, lo=0, hi=None, *, key=None): - """Return the index where to insert item x in list a, assuming a is sorted. - - The return value i is such that all e in a[:i] have e < x, and all e in - a[i:] have e >= x. So if x already appears in the list, a.insert(i, x) will - insert just before the leftmost x already there. - - Optional args lo (default 0) and hi (default len(a)) bound the - slice of a to be searched. - """ - - if lo < 0: - raise ValueError('lo must be non-negative') - if hi is None: - hi = len(a) - # Note, the comparison uses "<" to match the - # __lt__() logic in list.sort() and in heapq. - if key is None: - while lo < hi: - mid = (lo + hi) // 2 - if a[mid] < x: - lo = mid + 1 - else: - hi = mid - return - while lo < hi: - mid = (lo + hi) // 2 - if key(a[mid]) < x: - lo = mid + 1 - else: - hi = mid - return lo diff --git a/tests/unit/test_tablets.py b/tests/unit/test_tablets.py index 5e640fa4c9..7a40e7de4d 100644 --- a/tests/unit/test_tablets.py +++ b/tests/unit/test_tablets.py @@ -86,3 +86,41 @@ def test_add_tablet_intersecting_with_last(self): self.compare_ranges(tablets_list, [(-8611686018427387905, -7917529027641081857), (-5011686018427387905, -2987529027641081857)]) + + +class GetTabletForKeyTest(unittest.TestCase): + """Tests for Tablets.get_tablet_for_key.""" + + def test_found(self): + t1 = Tablet(0, 100, [("host1", 0)]) + t2 = Tablet(100, 200, [("host2", 0)]) + t3 = Tablet(200, 300, [("host3", 0)]) + tablets = Tablets({("ks", "tb"): [t1, t2, t3]}) + + class Token: + def __init__(self, v): + self.value = v + + result = tablets.get_tablet_for_key("ks", "tb", Token(150)) + self.assertIs(result, t2) + + def test_not_found_empty(self): + tablets = Tablets({}) + + class Token: + def __init__(self, v): + self.value = v + + self.assertIsNone(tablets.get_tablet_for_key("ks", "tb", Token(50))) + + def test_not_found_outside_range(self): + t1 = Tablet(100, 200, [("host1", 0)]) + tablets = Tablets({("ks", "tb"): [t1]}) + + class Token: + def __init__(self, v): + self.value = v + + # Token value 50 is not > first_token (100) of the tablet whose + # last_token (200) is >= 50, so no match. + self.assertIsNone(tablets.get_tablet_for_key("ks", "tb", Token(50))) From cc78c22b173c08c4ba7843306a7a77ac934f18fd Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 12 Apr 2026 22:39:13 -0400 Subject: [PATCH 265/298] Add Jira PR sync workflow --- .github/workflows/call_jira_sync.yml | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/call_jira_sync.yml diff --git a/.github/workflows/call_jira_sync.yml b/.github/workflows/call_jira_sync.yml new file mode 100644 index 0000000000..385737847b --- /dev/null +++ b/.github/workflows/call_jira_sync.yml @@ -0,0 +1,41 @@ +name: Sync Jira Based on PR Events + +on: + pull_request_target: + types: [opened, edited, ready_for_review, review_requested, labeled, unlabeled, closed] + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + jira-sync-pr-opened: + if: github.event.action == 'opened' || github.event.action == 'edited' + uses: scylladb/github-automation/.github/workflows/main_jira_sync_pr_opened.yml@main + secrets: + caller_jira_auth: ${{ secrets.USER_AND_KEY_FOR_JIRA_AUTOMATION }} + + jira-sync-in-review: + if: github.event.action == 'ready_for_review' || github.event.action == 'review_requested' + uses: scylladb/github-automation/.github/workflows/main_jira_sync_in_review.yml@main + secrets: + caller_jira_auth: ${{ secrets.USER_AND_KEY_FOR_JIRA_AUTOMATION }} + + jira-sync-add-label: + if: github.event.action == 'labeled' + uses: scylladb/github-automation/.github/workflows/main_jira_sync_add_label.yml@main + secrets: + caller_jira_auth: ${{ secrets.USER_AND_KEY_FOR_JIRA_AUTOMATION }} + + jira-sync-remove-label: + if: github.event.action == 'unlabeled' + uses: scylladb/github-automation/.github/workflows/main_jira_sync_remove_label.yml@main + secrets: + caller_jira_auth: ${{ secrets.USER_AND_KEY_FOR_JIRA_AUTOMATION }} + + jira-sync-pr-closed: + if: github.event.action == 'closed' + uses: scylladb/github-automation/.github/workflows/main_jira_sync_pr_closed.yml@main + secrets: + caller_jira_auth: ${{ secrets.USER_AND_KEY_FOR_JIRA_AUTOMATION }} From d2e3fef87c3aa58a3e68da0b23e034b264044d64 Mon Sep 17 00:00:00 2001 From: Dani Tweig Date: Tue, 14 Apr 2026 16:49:52 +0300 Subject: [PATCH 266/298] PM-285: Consolidate Jira sync workflow to single job calling main_pr_events_jira_sync --- .github/workflows/call_jira_sync.yml | 31 ++++------------------------ 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/.github/workflows/call_jira_sync.yml b/.github/workflows/call_jira_sync.yml index 385737847b..14f517df40 100644 --- a/.github/workflows/call_jira_sync.yml +++ b/.github/workflows/call_jira_sync.yml @@ -10,32 +10,9 @@ permissions: issues: write jobs: - jira-sync-pr-opened: - if: github.event.action == 'opened' || github.event.action == 'edited' - uses: scylladb/github-automation/.github/workflows/main_jira_sync_pr_opened.yml@main - secrets: - caller_jira_auth: ${{ secrets.USER_AND_KEY_FOR_JIRA_AUTOMATION }} - - jira-sync-in-review: - if: github.event.action == 'ready_for_review' || github.event.action == 'review_requested' - uses: scylladb/github-automation/.github/workflows/main_jira_sync_in_review.yml@main - secrets: - caller_jira_auth: ${{ secrets.USER_AND_KEY_FOR_JIRA_AUTOMATION }} - - jira-sync-add-label: - if: github.event.action == 'labeled' - uses: scylladb/github-automation/.github/workflows/main_jira_sync_add_label.yml@main - secrets: - caller_jira_auth: ${{ secrets.USER_AND_KEY_FOR_JIRA_AUTOMATION }} - - jira-sync-remove-label: - if: github.event.action == 'unlabeled' - uses: scylladb/github-automation/.github/workflows/main_jira_sync_remove_label.yml@main - secrets: - caller_jira_auth: ${{ secrets.USER_AND_KEY_FOR_JIRA_AUTOMATION }} - - jira-sync-pr-closed: - if: github.event.action == 'closed' - uses: scylladb/github-automation/.github/workflows/main_jira_sync_pr_closed.yml@main + jira-sync: + uses: scylladb/github-automation/.github/workflows/main_pr_events_jira_sync.yml@main + with: + caller_action: ${{ github.event.action }} secrets: caller_jira_auth: ${{ secrets.USER_AND_KEY_FOR_JIRA_AUTOMATION }} From 006babf87f550afdc5f3e03f4080783d2ed48683 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:40:31 +0000 Subject: [PATCH 267/298] chore(deps): update github artifact actions --- .github/workflows/build-push.yml | 2 +- .github/workflows/lib-build-and-push.yml | 6 +++--- .github/workflows/publish-manually.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 15c77f3861..7414daec3a 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -23,7 +23,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: path: dist merge-multiple: true diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build-and-push.yml index 735a4638f4..0b1ce47647 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build-and-push.yml @@ -153,7 +153,7 @@ jobs: run: | GITHUB_WORKFLOW_REF="scylladb/python-driver/.github/workflows/lib-build-and-push.yml@refs/heads/master" CIBW_BUILD="cp3*" cibuildwheel --archs aarch64 --output-dir wheelhouse - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: wheels-${{ matrix.target }}-${{ matrix.os }} path: ./wheelhouse/*.whl @@ -172,7 +172,7 @@ jobs: - name: Build sdist run: uv build --sdist - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: source-dist path: dist/*.tar.gz @@ -185,7 +185,7 @@ jobs: id-token: write steps: - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: path: dist merge-multiple: true diff --git a/.github/workflows/publish-manually.yml b/.github/workflows/publish-manually.yml index 09b9779117..83ed290a2b 100644 --- a/.github/workflows/publish-manually.yml +++ b/.github/workflows/publish-manually.yml @@ -56,7 +56,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: path: dist merge-multiple: true From 293e4a15ed190bcb07e43dfba606e8f1fb1a8936 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:40:27 +0000 Subject: [PATCH 268/298] chore(deps): update actions/cache action to v5 --- .github/workflows/integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 3c75a33603..89f62963b0 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -78,7 +78,7 @@ jobs: run: uv sync - name: Cache Scylla download - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.ccm/repository key: scylla-${{ env.SCYLLA_VERSION }}-${{ runner.os }} From ee0bc66078322bd5d4e856a8868ba208cef6b752 Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Wed, 15 Apr 2026 13:12:46 +0200 Subject: [PATCH 269/298] CI: fix id-token permission for Test wheels building MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build-test.yml triggers on pull_request, which gives it id-token:none by default. lib-build-and-push.yml's upload_pypi job declares id-token:write, which exceeds the caller's cap and causes GitHub to reject the workflow at parse time — even though upload:false prevents upload_pypi from ever running. Fix: explicitly grant id-token:write to the test-wheels-build job so the permission cap satisfies the reusable workflow's requirement. Fixes #819 --- .github/workflows/build-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 3e1f1067d7..b0d261d9d6 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -19,5 +19,7 @@ jobs: name: "Test wheels building" if: "!contains(github.event.pull_request.labels.*.name, 'disable-test-build')" uses: ./.github/workflows/lib-build-and-push.yml + permissions: + id-token: write with: upload: false \ No newline at end of file From 284bd90f5db6844768fa88bcba07896b20fa96dc Mon Sep 17 00:00:00 2001 From: David Garcia Date: Thu, 12 Mar 2026 12:34:52 +0000 Subject: [PATCH 270/298] docs: update theme 1.9 --- .github/dependabot.yml | 2 +- .github/workflows/docs-pr.yml | 3 ++ docs/.gitignore | 2 + docs/conf.py | 2 +- docs/pyproject.toml | 8 ++-- docs/uv.lock | 89 +++++++++++++---------------------- 6 files changed, 44 insertions(+), 62 deletions(-) create mode 100644 docs/.gitignore diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 28784749c4..ac3943ef57 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,6 @@ version: 2 updates: - - package-ecosystem: "pip" + - package-ecosystem: "uv" directory: "/docs" schedule: interval: "daily" diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index b5651c8159..4158c2912e 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -2,6 +2,9 @@ name: "Docs / Build PR" # For more information, # see https://sphinx-theme.scylladb.com/stable/deployment/production.html#available-workflows +permissions: + contents: read + on: push: branches: diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000000..733bc65597 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +# Track uv.lock for reproducible docs builds +!uv.lock diff --git a/docs/conf.py b/docs/conf.py index 4b6b329525..87a38c6add 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,7 +52,7 @@ 'sphinx_sitemap', 'sphinx_scylladb_theme', 'sphinx_multiversion', # optional - 'recommonmark', # optional + 'myst_parser', # optional ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 59c425229a..762a4f2e49 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -11,13 +11,13 @@ dependencies = [ "gevent>=25.9.1,<26.0.0", "gremlinpython==3.7.4", "pygments>=2.19.2,<3.0.0", - "recommonmark==0.7.1", + "myst-parser>=5.0.0", "redirects_cli~=0.1.3", "sphinx-autobuild>=2025.0.0,<2026.0.0", "sphinx-sitemap>=2.8.0,<3.0.0", - "sphinx-scylladb-theme>=1.8.2,<2.0.0", + "sphinx-scylladb-theme>=1.9.1", "sphinx-multiversion-scylla>=0.3.2,<1.0.0", - "sphinx>=8.2.3,<9.0.0", + "sphinx>=9.0", "six>=1.9", "tornado>=6.5,<7.0", ] @@ -57,4 +57,4 @@ exclude = [ "**/__pycache__/**", "**/*.pyc", ".venv/**", -] \ No newline at end of file +] diff --git a/docs/uv.lock b/docs/uv.lock index 720a2080e7..56b0841403 100644 --- a/docs/uv.lock +++ b/docs/uv.lock @@ -205,15 +205,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "commonmark" -version = "0.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/60/48/a60f593447e8f0894ebb7f6e6c1f25dafc5e89c5879fdc9360ae93ff83f0/commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", size = 95764, upload-time = "2019-10-04T15:37:39.817Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/92/dfd892312d822f36c55366118b95d914e5f16de11044a27cf10a7d71bbbf/commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9", size = 51068, upload-time = "2019-10-04T15:37:37.674Z" }, -] - [[package]] name = "dnspython" version = "2.8.0" @@ -405,14 +396,14 @@ wheels = [ [[package]] name = "markdown-it-py" -version = "3.0.0" +version = "4.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] [[package]] @@ -513,7 +504,7 @@ wheels = [ [[package]] name = "myst-parser" -version = "4.0.1" +version = "5.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docutils" }, @@ -523,9 +514,9 @@ dependencies = [ { name = "pyyaml" }, { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985, upload-time = "2025-02-12T10:53:03.833Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579, upload-time = "2025-02-12T10:53:02.078Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z" }, ] [[package]] @@ -548,11 +539,11 @@ wheels = [ [[package]] name = "pathspec" -version = "0.12.1" +version = "1.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] @@ -629,8 +620,8 @@ dependencies = [ { name = "eventlet" }, { name = "gevent" }, { name = "gremlinpython" }, + { name = "myst-parser" }, { name = "pygments" }, - { name = "recommonmark" }, { name = "redirects-cli" }, { name = "six" }, { name = "sphinx" }, @@ -651,14 +642,14 @@ requires-dist = [ { name = "eventlet", specifier = ">=0.40.3,<1.0.0" }, { name = "gevent", specifier = ">=25.9.1,<26.0.0" }, { name = "gremlinpython", specifier = "==3.7.4" }, + { name = "myst-parser", specifier = ">=5.0.0" }, { name = "pygments", specifier = ">=2.19.2,<3.0.0" }, - { name = "recommonmark", specifier = "==0.7.1" }, { name = "redirects-cli", specifier = "~=0.1.3" }, { name = "six", specifier = ">=1.9" }, - { name = "sphinx", specifier = ">=8.2.3,<9.0.0" }, + { name = "sphinx", specifier = ">=9.0" }, { name = "sphinx-autobuild", specifier = ">=2025.0.0,<2026.0.0" }, { name = "sphinx-multiversion-scylla", specifier = ">=0.3.2,<1.0.0" }, - { name = "sphinx-scylladb-theme", specifier = ">=1.8.2,<2.0.0" }, + { name = "sphinx-scylladb-theme", specifier = ">=1.9.1" }, { name = "sphinx-sitemap", specifier = ">=2.8.0,<3.0.0" }, { name = "tornado", specifier = ">=6.5,<7.0" }, ] @@ -684,20 +675,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, ] -[[package]] -name = "recommonmark" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "commonmark" }, - { name = "docutils" }, - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1c/00/3dd2bdc4184b0ce754b5b446325abf45c2e0a347e022292ddc44670f628c/recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67", size = 34444, upload-time = "2020-12-17T19:24:56.523Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/77/ed589c75db5d02a77a1d5d2d9abc63f29676467d396c64277f98b50b79c2/recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f", size = 10214, upload-time = "2020-12-17T19:24:55.137Z" }, -] - [[package]] name = "redirects-cli" version = "0.1.3" @@ -740,12 +717,12 @@ wheels = [ ] [[package]] -name = "roman-numerals-py" -version = "3.1.0" +name = "roman-numerals" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, ] [[package]] @@ -795,7 +772,7 @@ wheels = [ [[package]] name = "sphinx" -version = "8.2.3" +version = "9.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alabaster" }, @@ -807,7 +784,7 @@ dependencies = [ { name = "packaging" }, { name = "pygments" }, { name = "requests" }, - { name = "roman-numerals-py" }, + { name = "roman-numerals" }, { name = "snowballstemmer" }, { name = "sphinxcontrib-applehelp" }, { name = "sphinxcontrib-devhelp" }, @@ -816,9 +793,9 @@ dependencies = [ { name = "sphinxcontrib-qthelp" }, { name = "sphinxcontrib-serializinghtml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, ] [[package]] @@ -840,14 +817,14 @@ wheels = [ [[package]] name = "sphinx-collapse" -version = "0.1.3" +version = "0.1.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/02/183559e508906f7282d4dd6ccbf443efddaa3114b7f6fab425949b37a003/sphinx_collapse-0.1.3.tar.gz", hash = "sha256:cae141e6f03ecd52ed246a305a69e1b0d5d05e6cdf3fe803d40d583ad6ad895a", size = 18540, upload-time = "2024-02-22T15:24:38.735Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/a1/cb5bb03a5081bd1229b3296c2af347b4147017fdb62777d2aad855cd349f/sphinx_collapse-0.1.4.tar.gz", hash = "sha256:ba860e50839c026cd1abcc164e1e7cb18bcc11c8214150e34a6550461be3229f", size = 19412, upload-time = "2026-02-27T17:47:24.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/2f/5889082a6a535aa8613a327308582914517082967583ad45586b7d61c145/sphinx_collapse-0.1.3-py3-none-any.whl", hash = "sha256:85fadb2ec8769b93fd04276538668fa96239ef60c20c4a9eaa3e480387a6e65b", size = 4688, upload-time = "2024-02-22T15:24:29.365Z" }, + { url = "https://files.pythonhosted.org/packages/9a/18/277f4663c97073606917becab629938237f1e03952f4e339f8b7d1f3096b/sphinx_collapse-0.1.4-py3-none-any.whl", hash = "sha256:76e9fa531bafb4984d6ef5f3dbe311982837f5965b7a35eda013bbd9dd41445e", size = 4811, upload-time = "2026-02-27T17:47:22.622Z" }, ] [[package]] @@ -876,14 +853,14 @@ wheels = [ [[package]] name = "sphinx-multiversion-scylla" -version = "0.3.4" +version = "0.3.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/1d/e2b1a214b20d33cc631422e483ed1c8cf6883870940b58cc46341b65e2d7/sphinx_multiversion_scylla-0.3.4.tar.gz", hash = "sha256:8f7c94a89c794334d78ef21761a8bf455aaa7361e71037cf2ac2ca51cb47a0ba", size = 12427, upload-time = "2025-11-24T07:42:01.506Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/b1/83fb37f6c9038469b3bd01453875bb2127b3c03f9f41247394ad2063645c/sphinx_multiversion_scylla-0.3.7.tar.gz", hash = "sha256:fc1ddd58e82cfd8810c1be6db8717a244043c04c1c632e9bd1436415d1db0d3b", size = 12665, upload-time = "2026-02-27T18:43:17.849Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/aa/82c27991640fe47921f74894a192d374dc1eb609d2276de4abeefe85f4aa/sphinx_multiversion_scylla-0.3.4-py3-none-any.whl", hash = "sha256:e64d49d39a8eccf06a9cb8bbe88eecb3eb2082e6b91a478b55dc7d0268d8e0b6", size = 12302, upload-time = "2025-11-24T07:42:00.403Z" }, + { url = "https://files.pythonhosted.org/packages/a1/94/f5b6219ca1136dc0305aaf3fb6c96aa2dfe65224d6dc147e00a6485a1a22/sphinx_multiversion_scylla-0.3.7-py3-none-any.whl", hash = "sha256:6205d261a77c90b7ea3105311d1d56014736a5148966133c34344512bb8c4e4f", size = 12558, upload-time = "2026-02-27T18:43:16.988Z" }, ] [[package]] @@ -900,7 +877,7 @@ wheels = [ [[package]] name = "sphinx-scylladb-theme" -version = "1.8.10" +version = "1.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -913,9 +890,9 @@ dependencies = [ { name = "sphinx-tabs" }, { name = "sphinxcontrib-mermaid" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/cd/bbd41f0d058f0ef4997cb044326f15dd28a1a17a4336e9b52cb67b8dd242/sphinx_scylladb_theme-1.8.10.tar.gz", hash = "sha256:8a78a9b692d9a946be2c4a64aa472fd82204cc8ea0b1ee7f60de6db35b356326", size = 1620675, upload-time = "2025-12-05T16:49:38.942Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4e/e49e351d4c429b8fe3090657d39e956d53dff61187d783caac1cba81bd72/sphinx_scylladb_theme-1.9.1.tar.gz", hash = "sha256:2ba6367f005d2c68eee1916cc16385989b8e53bbddcc81193003bdeb3bd3415e", size = 1676201, upload-time = "2026-03-09T18:10:43.841Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/0e/7577d9bb6e2e7378e6c9f49263c59061a2ae9e370b806d8d1fd8c3be2a23/sphinx_scylladb_theme-1.8.10-py3-none-any.whl", hash = "sha256:8b930f33bec7308ccaa92698ebb5ad85059bcbf93a463f92917aeaf473fce632", size = 1662434, upload-time = "2025-12-05T16:49:36.265Z" }, + { url = "https://files.pythonhosted.org/packages/4f/30/2b2bae1b022d1fabef405a4857f160464548e08d924f24d0b26d0ca6a848/sphinx_scylladb_theme-1.9.1-py3-none-any.whl", hash = "sha256:6156d60befc3da03bd11991fec9bc590e27ce7cc4ab05aa334edd5611424b106", size = 1662204, upload-time = "2026-03-09T18:10:45.638Z" }, ] [[package]] @@ -1057,11 +1034,11 @@ wheels = [ [[package]] name = "trove-classifiers" -version = "2025.12.1.14" +version = "2026.1.14.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/80/e1/000add3b3e0725ce7ee0ea6ea4543f1e1d9519742f3b2320de41eeefa7c7/trove_classifiers-2025.12.1.14.tar.gz", hash = "sha256:a74f0400524fc83620a9be74a07074b5cbe7594fd4d97fd4c2bfde625fdc1633", size = 16985, upload-time = "2025-12-01T14:47:11.456Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/43/7935f8ea93fcb6680bc10a6fdbf534075c198eeead59150dd5ed68449642/trove_classifiers-2026.1.14.14.tar.gz", hash = "sha256:00492545a1402b09d4858605ba190ea33243d361e2b01c9c296ce06b5c3325f3", size = 16997, upload-time = "2026-01-14T14:54:50.526Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl", hash = "sha256:a8206978ede95937b9959c3aff3eb258bbf7b07dff391ddd4ea7e61f316635ab", size = 14184, upload-time = "2025-12-01T14:47:10.113Z" }, + { url = "https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl", hash = "sha256:1f9553927f18d0513d8e5ff80ab8980b8202ce37ecae0e3274ed2ef11880e74d", size = 14197, upload-time = "2026-01-14T14:54:49.067Z" }, ] [[package]] From ca5b8c244de0c162dcb002728c53ac10fe4537a7 Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Thu, 16 Apr 2026 21:25:48 +0200 Subject: [PATCH 271/298] pool: fix inverted cooldown check in _get_shard_aware_endpoint The `block_until < time.time()` condition was true only *after* the NAT-detection cooldown had already expired, so the shard-aware port was never suppressed during the 10-minute window and was permanently disabled once that window closed. Fix: flip to `>` so the guard fires while the deadline is in the future. Add unit test covering the active-block, expired-block, and hard-disable paths to prevent regression. --- cassandra/pool.py | 2 +- tests/unit/test_shard_aware.py | 138 +++++++++++++++++++++------------ 2 files changed, 90 insertions(+), 50 deletions(-) diff --git a/cassandra/pool.py b/cassandra/pool.py index 227e1b5315..9e949c342c 100644 --- a/cassandra/pool.py +++ b/cassandra/pool.py @@ -677,7 +677,7 @@ def disable_advanced_shard_aware(self, secs): self.advanced_shardaware_block_until = max(time.time() + secs, self.advanced_shardaware_block_until) def _get_shard_aware_endpoint(self): - if (self.advanced_shardaware_block_until and self.advanced_shardaware_block_until < time.time()) or \ + if (self.advanced_shardaware_block_until and self.advanced_shardaware_block_until > time.time()) or \ self._session.cluster.shard_aware_options.disable_shardaware_port: return None diff --git a/tests/unit/test_shard_aware.py b/tests/unit/test_shard_aware.py index e7d26ae207..4b4c2c138d 100644 --- a/tests/unit/test_shard_aware.py +++ b/tests/unit/test_shard_aware.py @@ -15,6 +15,7 @@ import unittest import logging +import time from unittest.mock import MagicMock from concurrent.futures import ThreadPoolExecutor @@ -27,6 +28,45 @@ LOGGER = logging.getLogger(__name__) +class MockSession(MagicMock): + is_shutdown = False + keyspace = "ks1" + + def __init__(self, is_ssl=False, *args, **kwargs): + super(MockSession, self).__init__(*args, **kwargs) + self.cluster = MagicMock() + if is_ssl: + self.cluster.ssl_options = {'some_ssl_options': True} + else: + self.cluster.ssl_options = None + self.cluster.shard_aware_options = ShardAwareOptions() + self.cluster.executor = ThreadPoolExecutor(max_workers=2) + self.cluster.signal_connection_failure = lambda *args, **kwargs: False + self.cluster.connection_factory = self.mock_connection_factory + self.connection_counter = 0 + self.futures = [] + + def submit(self, fn, *args, **kwargs): + logging.info("Scheduling %s with args: %s, kwargs: %s", fn, args, kwargs) + if not self.is_shutdown: + f = self.cluster.executor.submit(fn, *args, **kwargs) + self.futures += [f] + return f + + def mock_connection_factory(self, *args, **kwargs): + connection = MagicMock() + connection.is_shutdown = False + connection.is_defunct = False + connection.is_closed = False + connection.orphaned_threshold_reached = False + connection.endpoint = args[0] + sharding_info = ShardingInfo(shard_id=1, shards_count=4, partitioner="", sharding_algorithm="", sharding_ignore_msb=0, shard_aware_port=19042, shard_aware_port_ssl=19045) + connection.features = ProtocolFeatures(shard_id=kwargs.get('shard_id', self.connection_counter), sharding_info=sharding_info) + self.connection_counter += 1 + + return connection + + class TestShardAware(unittest.TestCase): def test_parsing_and_calculating_shard_id(self): """ @@ -55,58 +95,58 @@ def test_advanced_shard_aware_port(self): Test that on given a `shard_aware_port` on the OPTIONS message (ShardInfo class) the next connections would be open using this port """ - class MockSession(MagicMock): - is_shutdown = False - keyspace = "ks1" - - def __init__(self, is_ssl=False, *args, **kwargs): - super(MockSession, self).__init__(*args, **kwargs) - self.cluster = MagicMock() - if is_ssl: - self.cluster.ssl_options = {'some_ssl_options': True} - else: - self.cluster.ssl_options = None - self.cluster.shard_aware_options = ShardAwareOptions() - self.cluster.executor = ThreadPoolExecutor(max_workers=2) - self.cluster.signal_connection_failure = lambda *args, **kwargs: False - self.cluster.connection_factory = self.mock_connection_factory - self.connection_counter = 0 - self.futures = [] - - def submit(self, fn, *args, **kwargs): - logging.info("Scheduling %s with args: %s, kwargs: %s", fn, args, kwargs) - if not self.is_shutdown: - f = self.cluster.executor.submit(fn, *args, **kwargs) - self.futures += [f] - return f - - def mock_connection_factory(self, *args, **kwargs): - connection = MagicMock() - connection.is_shutdown = False - connection.is_defunct = False - connection.is_closed = False - connection.orphaned_threshold_reached = False - connection.endpoint = args[0] - sharding_info = ShardingInfo(shard_id=1, shards_count=4, partitioner="", sharding_algorithm="", sharding_ignore_msb=0, shard_aware_port=19042, shard_aware_port_ssl=19045) - connection.features = ProtocolFeatures(shard_id=kwargs.get('shard_id', self.connection_counter), sharding_info=sharding_info) - self.connection_counter += 1 - - return connection - host = MagicMock() host.endpoint = DefaultEndPoint("1.2.3.4") for port, is_ssl in [(19042, False), (19045, True)]: session = MockSession(is_ssl=is_ssl) pool = HostConnection(host=host, host_distance=HostDistance.REMOTE, session=session) - for f in session.futures: - f.result() - assert len(pool._connections) == 4 - for shard_id, connection in pool._connections.items(): - assert connection.features.shard_id == shard_id - if shard_id == 0: - assert connection.endpoint == DefaultEndPoint("1.2.3.4") - else: - assert connection.endpoint == DefaultEndPoint("1.2.3.4", port=port) - - session.cluster.executor.shutdown(wait=True) + try: + for f in session.futures: + f.result() + assert len(pool._connections) == 4 + for shard_id, connection in pool._connections.items(): + assert connection.features.shard_id == shard_id + if shard_id == 0: + assert connection.endpoint == DefaultEndPoint("1.2.3.4") + else: + assert connection.endpoint == DefaultEndPoint("1.2.3.4", port=port) + finally: + session.cluster.executor.shutdown(wait=True) + + def test_advanced_shard_aware_cooldown(self): + """ + `disable_advanced_shard_aware` must suppress the shard-aware endpoint for + the duration of the cool-down window, then automatically restore it once + the deadline has passed. The hard-disable flag must suppress the endpoint + unconditionally. + """ + host = MagicMock() + host.endpoint = DefaultEndPoint("1.2.3.4") + session = MockSession(is_ssl=False) + + pool = HostConnection(host=host, host_distance=HostDistance.REMOTE, session=session) + for f in session.futures: + f.result() + + try: + # Baseline: shard-aware port is returned. + endpoint = pool._get_shard_aware_endpoint() + assert endpoint is not None + assert endpoint.port == 19042 + + # During the cool-down window `_get_shard_aware_endpoint` must return None. + pool.disable_advanced_shard_aware(600) + assert pool._get_shard_aware_endpoint() is None + + # Once the deadline has passed, the shard-aware port must be used again. + pool.advanced_shardaware_block_until = time.time() - 1 + endpoint = pool._get_shard_aware_endpoint() + assert endpoint is not None + assert endpoint.port == 19042 + + # The hard-disable flag must suppress the endpoint regardless of the timer. + session.cluster.shard_aware_options.disable_shardaware_port = True + assert pool._get_shard_aware_endpoint() is None + finally: + session.cluster.executor.shutdown(wait=True) From 11b427544fc26ba54b03dbd83291abb95235066a Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Fri, 17 Apr 2026 11:03:16 +0200 Subject: [PATCH 272/298] CI: remove dead upload_pypi job from reusable workflow, rename to lib-build.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #824. Follow-up to #820. The upload_pypi job in lib-build-and-push.yml was never reachable: none of the four caller workflows pass upload: true. build-push.yml and publish-manually.yml already publish from their own separate jobs (necessary due to how PyPI Trusted Publishing embeds the caller workflow path in the OIDC token). Because the reusable workflow declared 'permissions: id-token: write' for upload_pypi, GitHub's static permission validation forced build-test.yml (a pull_request workflow, which defaults to id-token: none) to also declare id-token: write — granting unnecessary privileges to a job that only builds wheels. Changes: - Rename lib-build-and-push.yml -> lib-build.yml (it only builds now) - Remove upload input and upload_pypi job from the reusable workflow - Remove 'permissions: id-token: write' and 'with: upload: false' from build-test.yml (no longer needed) - Update all callers (build-push.yml, publish-manually.yml, build-pre-release.yml) to reference the new workflow path and drop upload: false from with: blocks - Replace TODO comments in build-push.yml and publish-manually.yml with an explanatory comment: the separate publish job is now intentional design, not a temporary workaround --- .github/workflows/build-pre-release.yml | 2 +- .github/workflows/build-push.yml | 9 +++--- .github/workflows/build-test.yml | 6 +--- .../{lib-build-and-push.yml => lib-build.yml} | 29 ++----------------- .github/workflows/publish-manually.yml | 8 +++-- 5 files changed, 15 insertions(+), 39 deletions(-) rename .github/workflows/{lib-build-and-push.yml => lib-build.yml} (88%) diff --git a/.github/workflows/build-pre-release.yml b/.github/workflows/build-pre-release.yml index e1326b6aa5..f6473c1cc3 100644 --- a/.github/workflows/build-pre-release.yml +++ b/.github/workflows/build-pre-release.yml @@ -15,7 +15,7 @@ on: jobs: build-and-publish: - uses: ./.github/workflows/lib-build-and-push.yml + uses: ./.github/workflows/lib-build.yml with: python-version: ${{ inputs.python-version }} target: ${{ inputs.target }} diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 7414daec3a..3a3d93171a 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -10,11 +10,12 @@ on: jobs: build-and-publish: name: "Build wheels" - uses: ./.github/workflows/lib-build-and-push.yml - with: - upload: false + uses: ./.github/workflows/lib-build.yml - # TODO: Remove when https://github.com/pypa/gh-action-pypi-publish/issues/166 is fixed and update build-and-publish.with.upload to ${{ endsWith(github.event.ref, 'scylla') }} + # Publishing is a separate job (not inside the reusable workflow) because PyPI Trusted Publishing + # requires the *caller* workflow path in the OIDC token. A reusable workflow would embed its own + # path instead, causing an `invalid-publisher` error on the PyPI side. + # See: https://github.com/pypa/gh-action-pypi-publish/issues/166 publish: name: "Publish wheels to PyPi" if: ${{ endsWith(github.event.ref, 'scylla') }} diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index b0d261d9d6..ebfe383047 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -18,8 +18,4 @@ jobs: test-wheels-build: name: "Test wheels building" if: "!contains(github.event.pull_request.labels.*.name, 'disable-test-build')" - uses: ./.github/workflows/lib-build-and-push.yml - permissions: - id-token: write - with: - upload: false \ No newline at end of file + uses: ./.github/workflows/lib-build.yml \ No newline at end of file diff --git a/.github/workflows/lib-build-and-push.yml b/.github/workflows/lib-build.yml similarity index 88% rename from .github/workflows/lib-build-and-push.yml rename to .github/workflows/lib-build.yml index 0b1ce47647..f8d0d7a4cc 100644 --- a/.github/workflows/lib-build-and-push.yml +++ b/.github/workflows/lib-build.yml @@ -1,14 +1,8 @@ -name: Build and upload to PyPi +name: Build wheels on: workflow_call: inputs: - upload: - description: 'Upload to PyPI' - type: boolean - required: false - default: false - python-version: description: 'Python version to run on' type: string @@ -146,12 +140,12 @@ jobs: if: matrix.target != 'linux-aarch64' shell: bash run: | - GITHUB_WORKFLOW_REF="scylladb/python-driver/.github/workflows/lib-build-and-push.yml@refs/heads/master" cibuildwheel --output-dir wheelhouse + GITHUB_WORKFLOW_REF="scylladb/python-driver/.github/workflows/lib-build.yml@refs/heads/master" cibuildwheel --output-dir wheelhouse - name: Build wheels for linux aarch64 if: matrix.target == 'linux-aarch64' run: | - GITHUB_WORKFLOW_REF="scylladb/python-driver/.github/workflows/lib-build-and-push.yml@refs/heads/master" CIBW_BUILD="cp3*" cibuildwheel --archs aarch64 --output-dir wheelhouse + GITHUB_WORKFLOW_REF="scylladb/python-driver/.github/workflows/lib-build.yml@refs/heads/master" CIBW_BUILD="cp3*" cibuildwheel --archs aarch64 --output-dir wheelhouse - uses: actions/upload-artifact@v7 with: @@ -176,20 +170,3 @@ jobs: with: name: source-dist path: dist/*.tar.gz - - upload_pypi: - if: inputs.upload - needs: [build-wheels, build-sdist] - runs-on: ubuntu-24.04 - permissions: - id-token: write - - steps: - - uses: actions/download-artifact@v8 - with: - path: dist - merge-multiple: true - - - uses: pypa/gh-action-pypi-publish@release/v1 - with: - skip-existing: true diff --git a/.github/workflows/publish-manually.yml b/.github/workflows/publish-manually.yml index 83ed290a2b..2f15c6ecda 100644 --- a/.github/workflows/publish-manually.yml +++ b/.github/workflows/publish-manually.yml @@ -39,15 +39,17 @@ on: jobs: build-and-publish: name: "Build wheels" - uses: ./.github/workflows/lib-build-and-push.yml + uses: ./.github/workflows/lib-build.yml with: - upload: false python-version: ${{ inputs.python-version }} ignore_tests: ${{ inputs.ignore_tests }} target_tag: ${{ inputs.target_tag }} target: ${{ inputs.target }} - # TODO: Remove when https://github.com/pypa/gh-action-pypi-publish/issues/166 is fixed and update build-and-publish.with.upload to ${{ inputs.upload }} + # Publishing is a separate job (not inside the reusable workflow) because PyPI Trusted Publishing + # requires the *caller* workflow path in the OIDC token. A reusable workflow would embed its own + # path instead, causing an `invalid-publisher` error on the PyPI side. + # See: https://github.com/pypa/gh-action-pypi-publish/issues/166 publish: name: "Publish wheels to PyPi" needs: build-and-publish From 3aa5935de1ef89cbc58ef24d4aaeb9d1fad10a4f Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Fri, 17 Apr 2026 11:27:01 +0200 Subject: [PATCH 273/298] CI: remove ineffective GITHUB_WORKFLOW_REF override from cibuildwheel steps GITHUB_WORKFLOW_REF was set as a shell env var prefix on the cibuildwheel invocations as an attempted workaround for pypa/gh-action-pypi-publish#166 (reusable workflows not supported by PyPI Trusted Publishing). The workaround does not work for two reasons: 1. GITHUB_WORKFLOW_REF is a GitHub runner-provided variable used to populate the OIDC token. Setting it in a child process's environment has no effect on the token GitHub's infrastructure mints. 2. The OIDC token is minted when pypa/gh-action-pypi-publish runs (in the publish job), not when cibuildwheel runs (in build-wheels). The variable was set in the wrong job entirely. The actual working workaround is running pypa/gh-action-pypi-publish directly in the caller workflow (build-push.yml, publish-manually.yml), which is already done. This variable override is dead code with no effect. --- .github/workflows/lib-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lib-build.yml b/.github/workflows/lib-build.yml index f8d0d7a4cc..bc094d1b11 100644 --- a/.github/workflows/lib-build.yml +++ b/.github/workflows/lib-build.yml @@ -140,12 +140,12 @@ jobs: if: matrix.target != 'linux-aarch64' shell: bash run: | - GITHUB_WORKFLOW_REF="scylladb/python-driver/.github/workflows/lib-build.yml@refs/heads/master" cibuildwheel --output-dir wheelhouse + cibuildwheel --output-dir wheelhouse - name: Build wheels for linux aarch64 if: matrix.target == 'linux-aarch64' run: | - GITHUB_WORKFLOW_REF="scylladb/python-driver/.github/workflows/lib-build.yml@refs/heads/master" CIBW_BUILD="cp3*" cibuildwheel --archs aarch64 --output-dir wheelhouse + CIBW_BUILD="cp3*" cibuildwheel --archs aarch64 --output-dir wheelhouse - uses: actions/upload-artifact@v7 with: From 5cd0158e1775e6ce27148fe733a9030ca4d3bfa4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:38:23 +0000 Subject: [PATCH 274/298] chore(deps): update astral-sh/setup-uv action to v8 --- .github/workflows/docs-pages.yml | 2 +- .github/workflows/docs-pr.yml | 2 +- .github/workflows/integration-tests.yml | 2 +- .github/workflows/lib-build.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index 0da86fef34..9d14b9c4d8 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -31,7 +31,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.1.0 with: working-directory: docs enable-cache: true diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index 4158c2912e..f0aa64d628 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -37,7 +37,7 @@ jobs: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.1.0 with: working-directory: docs enable-cache: true diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 89f62963b0..fde1ab3e1d 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -68,7 +68,7 @@ jobs: run: sudo apt-get install libev4 libev-dev - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.1.0 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/lib-build.yml b/.github/workflows/lib-build.yml index bc094d1b11..21dcc0604f 100644 --- a/.github/workflows/lib-build.yml +++ b/.github/workflows/lib-build.yml @@ -96,7 +96,7 @@ jobs: echo "CIBW_BEFORE_TEST_WINDOWS=(exit 0)" >> $GITHUB_ENV; - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.1.0 with: python-version: ${{ inputs.python-version }} @@ -159,7 +159,7 @@ jobs: - uses: actions/checkout@v6 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.1.0 with: python-version: ${{ inputs.python-version }} From 32548a66010ac1fa3fa722afe3abf39469ff281c Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Tue, 14 Apr 2026 20:03:56 +0300 Subject: [PATCH 275/298] Fix unfilled format string in add_execution_profile timeout message The error message at Cluster.add_execution_profile() had an unfilled %s placeholder: 'Failed to create all new connection pools in the %ss timeout.' The pool_wait_timeout value was never interpolated into the string, so users would see a literal '%s' instead of the actual timeout value. Signed-off-by: Yaniv Kaul --- cassandra/cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 9eace8810d..4f07f023a3 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -1683,7 +1683,7 @@ def add_execution_profile(self, name, profile, pool_wait_timeout=5): futures.update(session.update_created_pools()) _, not_done = wait_futures(futures, pool_wait_timeout) if not_done: - raise OperationTimedOut("Failed to create all new connection pools in the %ss timeout.") + raise OperationTimedOut("Failed to create all new connection pools in the %ss timeout." % pool_wait_timeout) def connection_factory(self, endpoint, host_conn = None, *args, **kwargs): """ From d83adab0857caf3cd288244a0b56c28cbba83a32 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Tue, 14 Apr 2026 20:39:49 +0300 Subject: [PATCH 276/298] Add timeout and in-flight observability to OperationTimedOut MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve timeout observability in the driver, inspired by the Go driver PR scylladb/gocql#847. OperationTimedOut now carries optional timeout and in_flight fields that are appended to the exception message when present (e.g. "(timeout=10.0s, in_flight=42)"). All seven production raise sites in connection.py and cluster.py pass these values where available. Additionally, debug-level log lines are emitted for: - Client-side request timeouts (host, timeout, in_flight, orphaned) - Server-side read/write timeouts (host, consistency, received/required, data_retrieved/write_type, retry decision) A helper _retry_decision_name() translates RetryPolicy constants to human-readable strings for the log messages. New keyword-only parameters are backward compatible — existing callers that pass only positional errors/last_host continue to work unchanged. Fixes: DRIVER-538 Signed-off-by: Yaniv Kaul --- cassandra/__init__.py | 21 ++++++++++- cassandra/cluster.py | 16 ++++++--- cassandra/connection.py | 15 +++++--- tests/unit/test_cluster.py | 58 ++++++++++++++++++++++++++++++ tests/unit/test_connection.py | 2 ++ tests/unit/test_response_future.py | 12 +++++-- 6 files changed, 112 insertions(+), 12 deletions(-) diff --git a/cassandra/__init__.py b/cassandra/__init__.py index 3ad8fcdfd1..46de7daaf0 100644 --- a/cassandra/__init__.py +++ b/cassandra/__init__.py @@ -687,10 +687,29 @@ class OperationTimedOut(DriverException): The last :class:`~.Host` this operation was attempted against. """ - def __init__(self, errors=None, last_host=None): + timeout = None + """ + The timeout value (in seconds) that was in effect when the operation + timed out, or ``None`` if not applicable. + """ + + in_flight = None + """ + The number of in-flight requests on the connection at the time of + the timeout (includes orphaned requests), or ``None`` if not applicable. + """ + + def __init__(self, errors=None, last_host=None, timeout=None, in_flight=None): self.errors = errors self.last_host = last_host + self.timeout = timeout + self.in_flight = in_flight message = "errors=%s, last_host=%s" % (self.errors, self.last_host) + if self.timeout is not None: + message += " (timeout=%ss" % self.timeout + if self.in_flight is not None: + message += ", in_flight=%d" % self.in_flight + message += ")" Exception.__init__(self, message) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 4f07f023a3..5e7a68bc1c 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -191,7 +191,6 @@ def _connection_reduce_fn(val,import_fn): log = logging.getLogger(__name__) - _GRAPH_PAGING_MIN_DSE_VERSION = Version('6.8.0') _NOT_SET = object() @@ -1683,7 +1682,8 @@ def add_execution_profile(self, name, profile, pool_wait_timeout=5): futures.update(session.update_created_pools()) _, not_done = wait_futures(futures, pool_wait_timeout) if not_done: - raise OperationTimedOut("Failed to create all new connection pools in the %ss timeout." % pool_wait_timeout) + raise OperationTimedOut("Failed to create all new connection pools in the %ss timeout." % pool_wait_timeout, + timeout=pool_wait_timeout) def connection_factory(self, endpoint, host_conn = None, *args, **kwargs): """ @@ -4505,6 +4505,7 @@ def _on_timeout(self, _attempts=0): ) return + conn_in_flight = None if self._connection is not None: try: self._connection._requests.pop(self._req_id) @@ -4515,9 +4516,14 @@ def _on_timeout(self, _attempts=0): except KeyError: key = "Connection defunct by heartbeat" errors = {key: "Client request timeout. See Session.execute[_async](timeout)"} - self._set_final_exception(OperationTimedOut(errors, self._current_host)) + self._set_final_exception(OperationTimedOut(errors, self._current_host, + timeout=self.timeout, + in_flight=self._connection.in_flight)) return + # Capture connection stats before pool.return_connection() can alter state + conn_in_flight = self._connection.in_flight + pool = self.session._pools.get(self._current_host) if pool and not pool.is_shutdown: # Do not return the stream ID to the pool yet. We cannot reuse it @@ -4542,7 +4548,9 @@ def _on_timeout(self, _attempts=0): host = str(connection.endpoint) if connection else 'unknown' errors = {host: "Request timed out while waiting for schema agreement. See Session.execute[_async](timeout) and Cluster.max_schema_agreement_wait."} - self._set_final_exception(OperationTimedOut(errors, self._current_host)) + self._set_final_exception(OperationTimedOut(errors, self._current_host, + timeout=self.timeout, + in_flight=conn_in_flight)) def _on_speculative_execute(self): self._timer = None diff --git a/cassandra/connection.py b/cassandra/connection.py index c045b36cb3..08501d0a2b 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -984,7 +984,8 @@ def factory(cls, endpoint, timeout, host_conn = None, *args, **kwargs): raise conn.last_error elif not conn.connected_event.is_set(): conn.close() - raise OperationTimedOut("Timed out creating connection (%s seconds)" % timeout) + raise OperationTimedOut("Timed out creating connection (%s seconds)" % timeout, + timeout=timeout) else: return conn @@ -1247,6 +1248,7 @@ def wait_for_responses(self, *msgs, **kwargs): msg += ": %s" % (self.last_error,) raise ConnectionShutdown(msg) timeout = kwargs.get('timeout') + original_timeout = timeout # preserve for exception reporting fail_on_error = kwargs.get('fail_on_error', True) waiter = ResponseWaiter(self, len(msgs), fail_on_error) @@ -1271,7 +1273,8 @@ def wait_for_responses(self, *msgs, **kwargs): if timeout is not None: timeout -= 0.01 if timeout <= 0.0: - raise OperationTimedOut() + raise OperationTimedOut(timeout=original_timeout, + in_flight=self.in_flight) time.sleep(0.01) try: @@ -1796,7 +1799,8 @@ def deliver(self, timeout=None): if self.error: raise self.error elif not self.event.is_set(): - raise OperationTimedOut() + raise OperationTimedOut(timeout=timeout, + in_flight=self.connection.in_flight) else: return self.responses @@ -1823,7 +1827,10 @@ def wait(self, timeout): if self._exception: raise self._exception else: - raise OperationTimedOut("Connection heartbeat timeout after %s seconds" % (timeout,), self.connection.endpoint) + raise OperationTimedOut("Connection heartbeat timeout after %s seconds" % (timeout,), + self.connection.endpoint, + timeout=timeout, + in_flight=self.connection.in_flight) def _options_callback(self, response): if isinstance(response, SupportedMessage): diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 872d133b28..a4f0ebc4d3 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -87,6 +87,64 @@ def test_exception_types(self): assert issubclass(UnsupportedOperation, DriverException) +class OperationTimedOutTest(unittest.TestCase): + + def test_message_without_timeout(self): + """Default message format when no timeout info is provided.""" + exc = OperationTimedOut(errors={'host1': 'some error'}, last_host='host1') + msg = str(exc) + assert "errors={'host1': 'some error'}" in msg + assert "last_host=host1" in msg + assert "timeout=" not in msg + assert "in_flight=" not in msg + + def test_message_with_timeout_and_in_flight(self): + """Message includes timeout and in_flight when both are provided.""" + exc = OperationTimedOut(errors={'host1': 'err'}, last_host='host1', + timeout=10.0, in_flight=42) + msg = str(exc) + assert "(timeout=10.0s, in_flight=42)" in msg + + def test_message_with_timeout_no_in_flight(self): + """Message includes timeout but not in_flight when only timeout is set.""" + exc = OperationTimedOut(timeout=5.0) + msg = str(exc) + assert "(timeout=5.0s)" in msg + assert "in_flight=" not in msg + + def test_message_no_args(self): + """No-argument form should not crash and should have clean message.""" + exc = OperationTimedOut() + msg = str(exc) + assert "errors=None, last_host=None" in msg + assert "timeout=" not in msg + + def test_attributes_accessible(self): + """New and existing attributes should be readable.""" + exc = OperationTimedOut(errors={'h': 'e'}, last_host='h', + timeout=10.0, in_flight=42) + assert exc.errors == {'h': 'e'} + assert exc.last_host == 'h' + assert exc.timeout == 10.0 + assert exc.in_flight == 42 + + def test_attributes_default_none(self): + """New attributes should default to None when not provided.""" + exc = OperationTimedOut() + assert exc.timeout is None + assert exc.in_flight is None + assert exc.errors is None + assert exc.last_host is None + + def test_backward_compat_positional(self): + """Existing two-positional-arg form should still work.""" + exc = OperationTimedOut({'h': 'err'}, 'host1') + assert exc.errors == {'h': 'err'} + assert exc.last_host == 'host1' + assert exc.timeout is None + assert exc.in_flight is None + + class ClusterTest(unittest.TestCase): def test_tuple_for_contact_points(self): diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index a67b7e4678..2fa7c71196 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -520,6 +520,8 @@ def send_msg(msg, req_id, msg_callback): assert isinstance(exc, OperationTimedOut) assert exc.errors == 'Connection heartbeat timeout after 0.05 seconds' assert exc.last_host == DefaultEndPoint('localhost') + assert exc.timeout == 0.05 + assert isinstance(exc.in_flight, int) holder.return_connection.assert_has_calls( [call(connection)] * get_holders.call_count) diff --git a/tests/unit/test_response_future.py b/tests/unit/test_response_future.py index 7168ad2940..dd7fa75045 100644 --- a/tests/unit/test_response_future.py +++ b/tests/unit/test_response_future.py @@ -142,6 +142,8 @@ def test_heartbeat_defunct_deadlock(self): connection = MagicMock(spec=Connection) connection._requests = {} + connection.in_flight = 5 + connection.orphaned_request_ids = set() pool = Mock() pool.is_shutdown = False @@ -162,8 +164,10 @@ def test_heartbeat_defunct_deadlock(self): # Simulate ResponseFuture timing out rf._on_timeout() - with pytest.raises(OperationTimedOut, match="Connection defunct by heartbeat"): + with pytest.raises(OperationTimedOut, match="Connection defunct by heartbeat") as exc_info: rf.result() + assert exc_info.value.timeout == 1 + assert exc_info.value.in_flight == 5 def test_read_timeout_error_message(self): session = self.make_session() @@ -653,7 +657,7 @@ def test_timeout_does_not_release_stream_id(self): pool = self.make_pool() session._pools.get.return_value = pool connection = Mock(spec=Connection, lock=RLock(), _requests={}, request_ids=deque(), - orphaned_request_ids=set(), orphaned_threshold=256) + orphaned_request_ids=set(), orphaned_threshold=256, in_flight=3) pool.borrow_connection.return_value = (connection, 1) rf = self.make_response_future(session) @@ -663,8 +667,10 @@ def test_timeout_does_not_release_stream_id(self): rf._on_timeout() pool.return_connection.assert_called_once_with(connection, stream_was_orphaned=True) - with pytest.raises(OperationTimedOut, match="Client request timeout"): + with pytest.raises(OperationTimedOut, match="Client request timeout") as exc_info: rf.result() + assert exc_info.value.timeout == 1 + assert exc_info.value.in_flight == 3 assert len(connection.request_ids) == 0, \ "Request IDs should be empty but it's not: {}".format(connection.request_ids) From ea6078954b1278d2b3c5af74fd199efbdfbbc9fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 22:33:08 +0000 Subject: [PATCH 277/298] Add tests for libev atexit cleanup bug - Added test_libevreactor_shutdown.py to demonstrate the bug - Tests show that atexit callback captures None instead of actual loop Co-authored-by: fruch <340979+fruch@users.noreply.github.com> --- tests/unit/io/test_libevreactor_shutdown.py | 250 ++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 tests/unit/io/test_libevreactor_shutdown.py diff --git a/tests/unit/io/test_libevreactor_shutdown.py b/tests/unit/io/test_libevreactor_shutdown.py new file mode 100644 index 0000000000..6be2c2b647 --- /dev/null +++ b/tests/unit/io/test_libevreactor_shutdown.py @@ -0,0 +1,250 @@ +# Copyright DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Test to demonstrate the libevwrapper atexit cleanup issue. + +This test demonstrates the problem where the atexit callback is registered +with _global_loop=None at import time, causing it to receive None during +shutdown instead of the actual loop instance. +""" + +import unittest +import atexit +import sys +import subprocess +import tempfile +import os +from pathlib import Path + +from cassandra import DependencyException + +try: + from cassandra.io.libevreactor import LibevConnection +except (ImportError, DependencyException): + LibevConnection = None + +from tests import is_monkey_patched + + +class LibevAtexitCleanupTest(unittest.TestCase): + """ + Test case to demonstrate the atexit cleanup bug in libevreactor. + + The bug: atexit.register(partial(_cleanup, _global_loop)) is called when + _global_loop is None, so the cleanup function receives None at shutdown + instead of the actual LibevLoop instance that was created later. + """ + + def setUp(self): + if is_monkey_patched(): + raise unittest.SkipTest("Can't test libev with monkey patching") + if LibevConnection is None: + raise unittest.SkipTest('libev does not appear to be installed correctly') + + def test_atexit_callback_registered_with_none(self): + """ + Test that demonstrates the atexit callback bug. + + The atexit.register(partial(_cleanup, _global_loop)) line is executed + when _global_loop is None. This means the partial function captures + None as the argument, and when atexit calls it during shutdown, it + passes None to _cleanup instead of the actual loop instance. + + @since 3.29 + @jira_ticket PYTHON-XXX + @expected_result The test demonstrates that atexit cleanup is broken + + @test_category connection + """ + from cassandra.io import libevreactor + from functools import partial + + # Check the current atexit handlers + # Note: atexit._exithandlers is an implementation detail but useful for debugging + if hasattr(atexit, '_exithandlers'): + # Find our cleanup handler + cleanup_handler = None + for handler in atexit._exithandlers: + func = handler[0] + # Check if this is our partial(_cleanup, _global_loop) handler + if isinstance(func, partial): + if func.func.__name__ == '_cleanup': + cleanup_handler = func + break + + if cleanup_handler: + # The problem: the partial was created with _global_loop=None + # So even if _global_loop is later set to a LibevLoop instance, + # the atexit callback will still call _cleanup(None) + captured_arg = cleanup_handler.args[0] if cleanup_handler.args else None + + # This assertion will fail after LibevConnection.initialize_reactor() + # is called and _global_loop is set to a LibevLoop instance + LibevConnection.initialize_reactor() + + # At this point, libevreactor._global_loop is not None + self.assertIsNotNone(libevreactor._global_loop, + "Global loop should be initialized") + + # But the atexit handler still has None captured! + self.assertIsNone(captured_arg, + "The atexit handler captured None, not the actual loop instance. " + "This is the BUG: cleanup will receive None at shutdown!") + + def test_shutdown_crash_scenario_subprocess(self): + """ + Test that simulates a Python shutdown crash scenario in a subprocess. + + This test creates a minimal script that: + 1. Imports the driver + 2. Creates a connection (which starts the event loop) + 3. Exits without explicit cleanup + + The expected behavior is that atexit should clean up the loop, but + because of the bug, the cleanup receives None and doesn't actually + stop the loop or its watchers. This can lead to crashes if callbacks + fire during shutdown. + + @since 3.29 + @jira_ticket PYTHON-XXX + @expected_result The subprocess demonstrates the cleanup issue + + @test_category connection + """ + # Create a test script that demonstrates the issue + test_script = ''' +import sys +import os + +# Add the driver path +sys.path.insert(0, {driver_path!r}) + +# Import and setup +from cassandra.io.libevreactor import LibevConnection, _global_loop +import atexit + +# Initialize the reactor (creates the global loop) +LibevConnection.initialize_reactor() + +print("Global loop initialized:", _global_loop is not None) + +# Check what atexit will actually call +if hasattr(atexit, '_exithandlers'): + from functools import partial + for handler in atexit._exithandlers: + func = handler[0] + if isinstance(func, partial) and func.func.__name__ == '_cleanup': + captured_arg = func.args[0] if func.args else None + print("Atexit will call _cleanup with:", captured_arg) + print("But _global_loop is:", _global_loop) + print("BUG: Cleanup will receive None instead of the loop!") + break + +# Exit without explicit cleanup - atexit should handle it, but won't! +print("Exiting...") +''' + + driver_path = str(Path(__file__).parent.parent.parent.parent) + script_content = test_script.format(driver_path=driver_path) + + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: + f.write(script_content) + script_path = f.name + + try: + result = subprocess.run( + [sys.executable, script_path], + capture_output=True, + text=True, + timeout=5 + ) + + output = result.stdout + print("\n=== Subprocess Output ===") + print(output) + print("=== End Output ===\n") + + # Verify the output shows the bug + self.assertIn("Global loop initialized: True", output) + self.assertIn("Atexit will call _cleanup with: None", output) + self.assertIn("BUG: Cleanup will receive None instead of the loop!", output) + + finally: + os.unlink(script_path) + + +class LibevShutdownRaceConditionTest(unittest.TestCase): + """ + Tests to analyze potential race conditions and crashes during shutdown. + """ + + def setUp(self): + if is_monkey_patched(): + raise unittest.SkipTest("Can't test libev with monkey patching") + if LibevConnection is None: + raise unittest.SkipTest('libev does not appear to be installed correctly') + + def test_callback_during_shutdown_scenario(self): + """ + Test to document the potential crash scenario. + + When Python is shutting down: + 1. Various modules are being torn down + 2. The libev event loop may still be running + 3. If a callback (io_callback, timer_callback, prepare_callback) fires: + - It calls PyGILState_Ensure() + - It tries to call Python functions (PyObject_CallFunction) + - If Python objects have been deallocated, this can crash + + The root cause: The atexit cleanup doesn't actually run because it + receives None instead of the loop instance, so it never: + - Sets _shutdown flag + - Stops watchers + - Joins the event loop thread + + @since 3.29 + @jira_ticket PYTHON-XXX + @expected_result Documents the crash scenario + + @test_category connection + """ + from cassandra.io.libevreactor import _global_loop, _cleanup + + # This test documents the issue - we can't easily reproduce a crash + # in a unit test without actually tearing down Python, but we can + # verify the conditions that lead to it + + LibevConnection.initialize_reactor() + + # Verify the loop exists + self.assertIsNotNone(_global_loop) + + # Simulate what atexit would call (with the bug) + _cleanup(None) # BUG: receives None instead of _global_loop + + # The loop is still running because cleanup did nothing! + self.assertFalse(_global_loop._shutdown, + "Loop should NOT be shut down when cleanup receives None") + + # Now call it correctly + _cleanup(_global_loop) + + # Now it should be shut down + self.assertTrue(_global_loop._shutdown, + "Loop should be shut down when cleanup receives the actual loop") + + +if __name__ == '__main__': + unittest.main() From 9ce7024930a96d1ee52f69ff2cb80ae7a8740a97 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 12:06:05 +0000 Subject: [PATCH 278/298] Implement Solution 1: Fix atexit registration to prevent shutdown crashes This implements the minimal fix for the libev atexit cleanup bug. Changes: - Replace atexit.register(partial(_cleanup, _global_loop)) with a wrapper function _atexit_cleanup() that looks up _global_loop at shutdown time - Remove unused 'partial' import from functools - Update tests to verify the fix works correctly The bug was that partial() captured _global_loop=None at import time, so cleanup always received None at shutdown instead of the actual LibevLoop instance. This prevented proper cleanup, leaving active callbacks that could crash during Python interpreter shutdown. The fix ensures _global_loop is looked up when atexit calls the cleanup, not when the callback is registered, so cleanup receives the actual loop instance and can properly shut down watchers and join the event loop thread. Co-authored-by: fruch <340979+fruch@users.noreply.github.com> --- cassandra/io/libevreactor.py | 15 +- tests/unit/io/test_libevreactor_shutdown.py | 198 +++++++++----------- 2 files changed, 105 insertions(+), 108 deletions(-) diff --git a/cassandra/io/libevreactor.py b/cassandra/io/libevreactor.py index c3f8f967ee..3da809931f 100644 --- a/cassandra/io/libevreactor.py +++ b/cassandra/io/libevreactor.py @@ -13,7 +13,6 @@ # limitations under the License. import atexit from collections import deque -from functools import partial import logging import os import socket @@ -232,8 +231,20 @@ def _loop_will_run(self, prepare): self._notifier.send() +def _atexit_cleanup(): + """Cleanup function called by atexit that uses the current _global_loop value. + + This wrapper ensures that cleanup receives the actual LibevLoop instance + instead of None, which was the value of _global_loop when the module was + imported. + """ + global _global_loop + if _global_loop is not None: + _cleanup(_global_loop) + + _global_loop = None -atexit.register(partial(_cleanup, _global_loop)) +atexit.register(_atexit_cleanup) class LibevConnection(Connection): diff --git a/tests/unit/io/test_libevreactor_shutdown.py b/tests/unit/io/test_libevreactor_shutdown.py index 6be2c2b647..5c44bca3aa 100644 --- a/tests/unit/io/test_libevreactor_shutdown.py +++ b/tests/unit/io/test_libevreactor_shutdown.py @@ -21,7 +21,6 @@ """ import unittest -import atexit import sys import subprocess import tempfile @@ -53,77 +52,67 @@ def setUp(self): if LibevConnection is None: raise unittest.SkipTest('libev does not appear to be installed correctly') - def test_atexit_callback_registered_with_none(self): + def test_atexit_callback_uses_current_global_loop(self): """ - Test that demonstrates the atexit callback bug. + Test that verifies the atexit callback fix. - The atexit.register(partial(_cleanup, _global_loop)) line is executed - when _global_loop is None. This means the partial function captures - None as the argument, and when atexit calls it during shutdown, it - passes None to _cleanup instead of the actual loop instance. + The fix uses a wrapper function _atexit_cleanup() that looks up the + current value of _global_loop at shutdown time, instead of capturing + it at import time with partial(). @since 3.29 @jira_ticket PYTHON-XXX - @expected_result The test demonstrates that atexit cleanup is broken + @expected_result The atexit handler calls cleanup with the actual loop @test_category connection """ from cassandra.io import libevreactor - from functools import partial - # Check the current atexit handlers - # Note: atexit._exithandlers is an implementation detail but useful for debugging - if hasattr(atexit, '_exithandlers'): - # Find our cleanup handler - cleanup_handler = None - for handler in atexit._exithandlers: - func = handler[0] - # Check if this is our partial(_cleanup, _global_loop) handler - if isinstance(func, partial): - if func.func.__name__ == '_cleanup': - cleanup_handler = func - break - - if cleanup_handler: - # The problem: the partial was created with _global_loop=None - # So even if _global_loop is later set to a LibevLoop instance, - # the atexit callback will still call _cleanup(None) - captured_arg = cleanup_handler.args[0] if cleanup_handler.args else None - - # This assertion will fail after LibevConnection.initialize_reactor() - # is called and _global_loop is set to a LibevLoop instance - LibevConnection.initialize_reactor() - - # At this point, libevreactor._global_loop is not None - self.assertIsNotNone(libevreactor._global_loop, - "Global loop should be initialized") - - # But the atexit handler still has None captured! - self.assertIsNone(captured_arg, - "The atexit handler captured None, not the actual loop instance. " - "This is the BUG: cleanup will receive None at shutdown!") - - def test_shutdown_crash_scenario_subprocess(self): + # Verify the fix: _atexit_cleanup should exist as a module-level function + self.assertTrue(hasattr(libevreactor, '_atexit_cleanup'), + "Module should have _atexit_cleanup function") + + # Verify it's not a partial (the old buggy implementation) + from functools import partial + self.assertNotIsInstance(libevreactor._atexit_cleanup, partial, + "The _atexit_cleanup should NOT be a partial function") + + # Verify it's actually a function + self.assertTrue(callable(libevreactor._atexit_cleanup), + "_atexit_cleanup should be callable") + + # Initialize the reactor + LibevConnection.initialize_reactor() + + # At this point, libevreactor._global_loop is not None + self.assertIsNotNone(libevreactor._global_loop, + "Global loop should be initialized") + + # The fix: _atexit_cleanup is a function that will look up + # _global_loop when it's called, not a partial with captured args + self.assertEqual(libevreactor._atexit_cleanup.__name__, '_atexit_cleanup', + "The function should have the correct name") + + def test_shutdown_cleanup_works_with_fix(self): """ - Test that simulates a Python shutdown crash scenario in a subprocess. + Test that verifies the atexit cleanup fix works in a subprocess. This test creates a minimal script that: 1. Imports the driver - 2. Creates a connection (which starts the event loop) - 3. Exits without explicit cleanup + 2. Initializes the reactor (creates the global loop) + 3. Verifies the _atexit_cleanup function is available + 4. Exits without explicit cleanup - The expected behavior is that atexit should clean up the loop, but - because of the bug, the cleanup receives None and doesn't actually - stop the loop or its watchers. This can lead to crashes if callbacks - fire during shutdown. + With the fix, atexit should properly clean up the loop using the + wrapper function that looks up _global_loop at shutdown time. @since 3.29 @jira_ticket PYTHON-XXX - @expected_result The subprocess demonstrates the cleanup issue + @expected_result The subprocess shows the fix is working @test_category connection """ - # Create a test script that demonstrates the issue + # Create a test script that verifies the fix test_script = ''' import sys import os @@ -132,28 +121,29 @@ def test_shutdown_crash_scenario_subprocess(self): sys.path.insert(0, {driver_path!r}) # Import and setup -from cassandra.io.libevreactor import LibevConnection, _global_loop +from cassandra.io import libevreactor +from cassandra.io.libevreactor import LibevConnection import atexit # Initialize the reactor (creates the global loop) LibevConnection.initialize_reactor() -print("Global loop initialized:", _global_loop is not None) - -# Check what atexit will actually call -if hasattr(atexit, '_exithandlers'): - from functools import partial - for handler in atexit._exithandlers: - func = handler[0] - if isinstance(func, partial) and func.func.__name__ == '_cleanup': - captured_arg = func.args[0] if func.args else None - print("Atexit will call _cleanup with:", captured_arg) - print("But _global_loop is:", _global_loop) - print("BUG: Cleanup will receive None instead of the loop!") - break - -# Exit without explicit cleanup - atexit should handle it, but won't! -print("Exiting...") +print("Global loop initialized:", libevreactor._global_loop is not None) + +# Verify the fix is in place: _atexit_cleanup should be a module-level function +if hasattr(libevreactor, '_atexit_cleanup'): + print("FIXED: Module has _atexit_cleanup function") + print("This function will look up _global_loop at shutdown time") + # Verify it's not using partial with None + import inspect + source = inspect.getsource(libevreactor._atexit_cleanup) + if "global _global_loop" in source and "_global_loop is not None" in source: + print("Verified: _atexit_cleanup uses current _global_loop value") +else: + print("BUG: No _atexit_cleanup function found") + +# Exit without explicit cleanup - atexit should handle it properly with the fix! +print("Exiting with proper cleanup...") ''' driver_path = str(Path(__file__).parent.parent.parent.parent) @@ -176,11 +166,12 @@ def test_shutdown_crash_scenario_subprocess(self): print(output) print("=== End Output ===\n") - # Verify the output shows the bug + # Verify the output shows the fix is working self.assertIn("Global loop initialized: True", output) - self.assertIn("Atexit will call _cleanup with: None", output) - self.assertIn("BUG: Cleanup will receive None instead of the loop!", output) - + self.assertIn("FIXED: Module has _atexit_cleanup function", output) + self.assertIn("Verified: _atexit_cleanup uses current _global_loop value", output) + self.assertNotIn("BUG", output.replace("BUG STILL PRESENT", "").replace("DEBUG", "")) # Allow "BUG" only in success message + finally: os.unlink(script_path) @@ -196,54 +187,49 @@ def setUp(self): if LibevConnection is None: raise unittest.SkipTest('libev does not appear to be installed correctly') - def test_callback_during_shutdown_scenario(self): + def test_cleanup_with_fix_properly_shuts_down(self): """ - Test to document the potential crash scenario. - - When Python is shutting down: - 1. Various modules are being torn down - 2. The libev event loop may still be running - 3. If a callback (io_callback, timer_callback, prepare_callback) fires: - - It calls PyGILState_Ensure() - - It tries to call Python functions (PyObject_CallFunction) - - If Python objects have been deallocated, this can crash - - The root cause: The atexit cleanup doesn't actually run because it - receives None instead of the loop instance, so it never: - - Sets _shutdown flag - - Stops watchers - - Joins the event loop thread + Test to verify the fix properly shuts down the event loop. + + With the fix in place, the atexit cleanup will: + 1. Look up the current _global_loop value (not None) + 2. Call _cleanup with the actual loop instance + 3. Properly shut down the loop and its watchers + + This prevents the crash scenario where: + - Various modules are being torn down during Python shutdown + - The libev event loop is still running + - Callbacks fire and try to access deallocated Python objects @since 3.29 @jira_ticket PYTHON-XXX - @expected_result Documents the crash scenario + @expected_result Cleanup properly shuts down the loop with the fix @test_category connection """ - from cassandra.io.libevreactor import _global_loop, _cleanup - - # This test documents the issue - we can't easily reproduce a crash - # in a unit test without actually tearing down Python, but we can - # verify the conditions that lead to it - + from cassandra.io import libevreactor + from cassandra.io.libevreactor import _cleanup, _atexit_cleanup + LibevConnection.initialize_reactor() # Verify the loop exists - self.assertIsNotNone(_global_loop) - - # Simulate what atexit would call (with the bug) - _cleanup(None) # BUG: receives None instead of _global_loop + self.assertIsNotNone(libevreactor._global_loop) + + # Before cleanup, the loop should not be shut down + self.assertFalse(libevreactor._global_loop._shutdown, + "Loop should not be shut down initially") - # The loop is still running because cleanup did nothing! - self.assertFalse(_global_loop._shutdown, + # Simulate what the OLD buggy code would do + _cleanup(None) # This does nothing + self.assertFalse(libevreactor._global_loop._shutdown, "Loop should NOT be shut down when cleanup receives None") - # Now call it correctly - _cleanup(_global_loop) + # Now test the FIX: call the wrapper that looks up _global_loop + _atexit_cleanup() # This is what atexit will actually call - # Now it should be shut down - self.assertTrue(_global_loop._shutdown, - "Loop should be shut down when cleanup receives the actual loop") + # With the fix, the loop should be properly shut down + self.assertTrue(libevreactor._global_loop._shutdown, + "Loop should be shut down when _atexit_cleanup is called") if __name__ == '__main__': From cd9f525140bd47311841fbe54ca59940f7ff6821 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 03:13:24 +0000 Subject: [PATCH 279/298] Update copyright header to ScyllaDB in test_libevreactor_shutdown.py Changed copyright from DataStax to ScyllaDB to match the project's standard for new files, as requested by reviewer. Co-authored-by: dkropachev <40304587+dkropachev@users.noreply.github.com> --- tests/unit/io/test_libevreactor_shutdown.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/io/test_libevreactor_shutdown.py b/tests/unit/io/test_libevreactor_shutdown.py index 5c44bca3aa..9578d22df1 100644 --- a/tests/unit/io/test_libevreactor_shutdown.py +++ b/tests/unit/io/test_libevreactor_shutdown.py @@ -1,4 +1,4 @@ -# Copyright DataStax, Inc. +# Copyright ScyllaDB, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 0842348d647b35ae0ec1d87ba906ec81adaec5f7 Mon Sep 17 00:00:00 2001 From: Israel Fruchter Date: Mon, 4 May 2026 21:04:01 +0300 Subject: [PATCH 280/298] fix(build): use dict-style license for setuptools<77 compatibility The PEP 639 SPDX string format (license = "Apache-2.0") requires setuptools>=77. Downstream projects that constrain setuptools to <75 fail to build from source with "project.license must be valid exactly by one definition (2 matches found)". Switch to the dict-style format which is compatible with all setuptools versions >=65. Fixes #840 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1335027fcd..4a40af5378 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ classifiers = [ ] dependencies = ['geomet>=1.1', 'pyyaml > 5.0'] dynamic = ["version", "readme"] -license = "Apache-2.0" +license = {text = "Apache-2.0"} requires-python = ">=3.9" [project.urls] From e6f9e9ff86579b8d8f1d068df93fb7e6af1c40c4 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Wed, 6 May 2026 08:40:10 +0200 Subject: [PATCH 281/298] Remove oss/ent_scylla_version params from xfail_scylla_version_lt xfail_scylla_version_lt now takes a single scylla_version parameter instead of separate oss_scylla_version and ent_scylla_version params. The enterprise/OSS version branching logic is removed; the decorator simply compares the current version against the single provided version. Update all call sites accordingly. --- tests/integration/__init__.py | 15 +++++---------- .../integration/standard/test_application_info.py | 2 +- .../standard/test_control_connection.py | 2 +- tests/integration/standard/test_metadata.py | 2 +- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index 286561c291..6a809bded4 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -687,29 +687,24 @@ def is_scylla_enterprise(version: Version) -> bool: return version > Version('2000.1.1') -def xfail_scylla_version_lt(reason, oss_scylla_version, ent_scylla_version, *args, **kwargs): +def xfail_scylla_version_lt(reason, scylla_version, *args, **kwargs): """ It is used to mark tests that are going to fail on certain scylla versions. :param reason: message to fail test with - :param oss_scylla_version: str, oss version from which test supposed to succeed - :param ent_scylla_version: str, enterprise version from which test supposed to succeed + :param scylla_version: str, version from which test supposed to succeed """ if not (reason.startswith("scylladb/scylladb#") or reason.startswith("scylladb/scylla-enterprise#")): raise ValueError('reason should start with scylladb/scylladb# or scylladb/scylla-enterprise# to reference issue in scylla repo') - if not isinstance(ent_scylla_version, str): - raise ValueError('ent_scylla_version should be a str') + if not isinstance(scylla_version, str): + raise ValueError('scylla_version should be a str') if SCYLLA_VERSION is None: return pytest.mark.skipif(False, reason="It is just a NoOP Decor, should not skip anything") current_version = Version(get_scylla_version(SCYLLA_VERSION)) - if is_scylla_enterprise(current_version): - return pytest.mark.xfail(current_version < Version(ent_scylla_version), - reason=reason, *args, **kwargs) - - return pytest.mark.xfail(current_version < Version(oss_scylla_version), reason=reason, *args, **kwargs) + return pytest.mark.xfail(current_version < Version(scylla_version), reason=reason, *args, **kwargs) def skip_scylla_version_lt(reason, scylla_version): diff --git a/tests/integration/standard/test_application_info.py b/tests/integration/standard/test_application_info.py index 719f37843a..5d4b679fc8 100644 --- a/tests/integration/standard/test_application_info.py +++ b/tests/integration/standard/test_application_info.py @@ -27,7 +27,7 @@ def teardown_module(): @xfail_scylla_version_lt(reason='scylladb/scylla-enterprise#5467 - system.client_options is not yet supported', - oss_scylla_version="7.0", ent_scylla_version="2026.1.0") + scylla_version="2026.1.0") class ApplicationInfoTest(unittest.TestCase): attribute_to_startup_key = { 'application_name': 'APPLICATION_NAME', diff --git a/tests/integration/standard/test_control_connection.py b/tests/integration/standard/test_control_connection.py index 2788a1d837..c4463e17fd 100644 --- a/tests/integration/standard/test_control_connection.py +++ b/tests/integration/standard/test_control_connection.py @@ -135,7 +135,7 @@ def test_control_connection_port_discovery(self): assert 7000 == host.broadcast_port @xfail_scylla_version_lt(reason='scylladb/scylladb#26992 - system.client_routes is not yet supported', - oss_scylla_version="7.0", ent_scylla_version="2026.1.0") + scylla_version="2026.1.0") def test_client_routes_change_event(self): cluster = TestCluster() diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index c30e369d83..6e64401a75 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -1197,7 +1197,7 @@ def test_export_keyspace_schema_udts(self): @greaterthancass21 @xfail_scylla_version_lt(reason='scylladb/scylladb#10707 - Column name in CREATE INDEX is not quoted', - oss_scylla_version="5.2", ent_scylla_version="2023.1.1") + scylla_version="2023.1.1") def test_case_sensitivity(self): """ Test that names that need to be escaped in CREATE statements are From 0d215f45b33a8e2cf336c5f120915a318e47f606 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 7 May 2026 00:46:12 -0400 Subject: [PATCH 282/298] cluster: add Session.wait_for_schema_agreement Add Session.wait_for_schema_agreement() as a session-scoped schema agreement check. The new API queries schema_version from system.local on the connected hosts selected by the requested rack, dc, or cluster scope, respects Cluster.max_schema_agreement_wait and the control-connection metadata timeouts, and bounds the fan-out with configurable parallelism. Update the public Session docs and switch the integration callers that were explicitly waiting on schema agreement to use the session API. Add unit coverage for agreement, retries, busy connections, missing pools, batching, scope filtering, and invalid scope handling. --- cassandra/cluster.py | 193 +++++++++++++++++- docs/api/cassandra/cluster.rst | 2 + tests/integration/long/test_schema.py | 2 +- tests/integration/standard/test_udts.py | 2 +- tests/unit/test_cluster.py | 214 +++++++++++++++++++- tests/unit/test_session_schema_agreement.py | 204 +++++++++++++++++++ 6 files changed, 611 insertions(+), 6 deletions(-) create mode 100644 tests/unit/test_session_schema_agreement.py diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 5e7a68bc1c..b55fbd5172 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -20,16 +20,17 @@ import atexit import datetime +from enum import Enum from binascii import hexlify from collections import defaultdict from collections.abc import Mapping -from concurrent.futures import ThreadPoolExecutor, FIRST_COMPLETED, wait as wait_futures +from concurrent.futures import Future, ThreadPoolExecutor, FIRST_COMPLETED, wait as wait_futures from copy import copy from functools import partial, reduce, wraps from itertools import groupby, count, chain import json import logging -from typing import Any, Dict, Optional, Union +from typing import Any, Dict, Optional, Union, Tuple from warnings import warn from random import random import re @@ -214,6 +215,14 @@ def __init__(self, message, errors): self.errors = errors +class SchemaAgreementScope(str, Enum): + """Scope selectors for :meth:`.Session.wait_for_schema_agreement`.""" + + RACK = 'rack' + DC = 'dc' + CLUSTER = 'cluster' + + def _future_completed(future): """ Helper for run_in_executor() """ exc = future.exception() @@ -3374,6 +3383,185 @@ def pool_finished_setting_keyspace(pool, host_errors): for pool in tuple(self._pools.values()): pool._set_keyspace_for_all_conns(keyspace, pool_finished_setting_keyspace) + def wait_for_schema_agreement(self, wait_time: Optional[float] = None, + scope: SchemaAgreementScope = SchemaAgreementScope.CLUSTER) -> bool: + """ + Wait for connected hosts in the selected scope to report the same + schema version from ``system.local``. + + By default, the timeout for this operation is governed by + :attr:`~.Cluster.max_schema_agreement_wait` and + :attr:`~.Cluster.control_connection_timeout`. + + Passing ``wait_time`` here overrides + :attr:`~.Cluster.max_schema_agreement_wait`. If provided, ``wait_time`` + must be greater than 0. + + ``scope`` determines which connected hosts participate in the check. + Pass :attr:`SchemaAgreementScope.RACK`, :attr:`SchemaAgreementScope.DC`, + or :attr:`SchemaAgreementScope.CLUSTER`. + The default is :attr:`SchemaAgreementScope.CLUSTER`. ``RACK`` narrows + the check to connected hosts in the local rack only. ``DC`` checks + connected hosts in the local datacenter. ``CLUSTER`` queries every + connected host across all datacenters. + + :param wait_time: Override for + :attr:`~.Cluster.max_schema_agreement_wait`, should be positive + number. + :param scope: Restricts the check to connected hosts in the local rack, + local datacenter, or whole connected cluster. + :returns: ``True`` when the selected connected hosts agree on schema, + otherwise ``False``. + :raises ValueError: If ``wait_time`` is provided and is not greater + than 0. + :raises ValueError: If ``scope`` is not one of the schema agreement + scope values. + """ + + if wait_time is not None and wait_time <= 0: + raise ValueError("wait_time must be greater than 0") + + total_timeout = wait_time if wait_time is not None else self.cluster.max_schema_agreement_wait + if total_timeout <= 0: + raise ValueError("total_timeout must be greater than 0") + + deadline = time.time() + total_timeout + schema_mismatches = None + scope_label = 'local rack' if scope is SchemaAgreementScope.RACK else ( + 'local datacenter' if scope is SchemaAgreementScope.DC else 'cluster') + + while time.time() < deadline: + schema_mismatches = self._get_schema_mismatches_for_scope(deadline, scope) + if schema_mismatches is None: + return True + + log.debug("[session] Connected hosts in the %s still disagree on schema, trying again", scope_label) + remaining = deadline - time.time() + if remaining > 0: + time.sleep(min(0.2, remaining)) + + log.warning("[session] Connected hosts in the %s are reporting a schema disagreement: %s", + scope_label, schema_mismatches) + return False + + def _get_schema_mismatches_for_scope(self, deadline: float, + scope: SchemaAgreementScope) -> Optional[Dict[Any, Any]]: + hosts = self._get_schema_agreement_hosts(scope) + mismatches = defaultdict(list) + errors = {} + scope_label = 'local rack' if scope is SchemaAgreementScope.RACK else ( + 'local datacenter' if scope is SchemaAgreementScope.DC else 'cluster') + + if not hosts: + errors[scope.value] = ConnectionException( + "No connected hosts available in the %s" % (scope_label,) + ) + return {'unavailable': errors} + + metadata_request_timeout = self.cluster.control_connection._metadata_request_timeout + query = maybe_add_timeout_to_query(ControlConnection._SELECT_SCHEMA_LOCAL, metadata_request_timeout) + + schema_version_futures = [] + for host in hosts: + try: + schema_version_future = self._query_local_schema_version(host, query, deadline) + except Exception as exc: + errors[host.endpoint] = exc + continue + + schema_version_futures.append((host, schema_version_future)) + + if schema_version_futures: + # Start all host queries first, then wait for the whole batch. + remaining = max(0.0, deadline - time.time()) + if remaining > 0: + wait_futures([future for _, future in schema_version_futures], timeout=remaining) + + for host, future in schema_version_futures: + if future.done(): + try: + rows = future.result() + except Exception as exc: + errors[host.endpoint] = exc + continue + + row = rows.one() + schema_version = getattr(row, "schema_version", None) if row is not None else None + mismatches[schema_version].append(host.endpoint) + else: + errors[host.endpoint] = OperationTimedOut(last_host=host, timeout=max(0.0, deadline - time.time())) + + if len(mismatches) == 1 and None not in mismatches and not errors: + log.debug("[session] Connected hosts in the %s agree on schema", scope_label) + return None + + if errors: + mismatches['unavailable'] = errors + return dict(mismatches) + + def _get_schema_agreement_hosts(self, scope: SchemaAgreementScope) -> Tuple[Host, ...]: + if scope is SchemaAgreementScope.RACK: + allowed_distances = (HostDistance.LOCAL_RACK,) + elif scope is SchemaAgreementScope.DC: + allowed_distances = (HostDistance.LOCAL_RACK, HostDistance.LOCAL) + else: + allowed_distances = (HostDistance.LOCAL_RACK, HostDistance.LOCAL, HostDistance.REMOTE) + + return tuple( + host for host, pool in tuple(self._pools.items()) + if host.is_up + and not pool.is_shutdown + and self._profile_manager.distance(host) in allowed_distances) + + def _query_local_schema_version(self, host: Host, query: str, deadline: float) -> Future: + remaining = max(0.0, deadline - time.time()) + try: + response_future = self.execute_async( + query, + timeout=self._schema_agreement_query_timeout(remaining), + host=host, + ) + except OperationTimedOut as timeout: + log.debug("[session] Timed out waiting for schema version from %s: %s", host, timeout) + raise + except Exception as exc: + log.debug("[session] Error querying schema version from %s: %s", host, exc) + raise + + # execute_async returns cassandra.cluster.ResponseFuture, which does not have bulk waiting logic for it. + # That is why _query_local_schema_version returns concurrent.futures.Future + # so that schema agreement logic could use concurrent.futures.wait_futures to wait on them. + # schema_version_future is an adapter between cassandra.cluster.ResponseFuture and concurrent.futures.Future + # to make things work + schema_version_future = Future() + + def _set_result(result, result_future=schema_version_future, response_future=response_future): + if result_future.done(): + return + try: + result_future.set_result(ResultSet(response_future, result)) + except Exception as exc: + result_future.set_exception(exc) + + def _set_exception(exc, result_future=schema_version_future): + if result_future.done(): + return + result_future.set_exception(exc) + + try: + response_future.add_callbacks(_set_result, _set_exception) + except Exception as exc: + log.debug("[session] Error registering schema version callback from %s: %s", host, exc) + raise + + return schema_version_future + + def _schema_agreement_query_timeout(self, remaining: float) -> float: + control_timeout = self.cluster.control_connection._timeout + if control_timeout is None: + return max(0.0, remaining) + return max(0.0, min(control_timeout, remaining)) + def user_type_registered(self, keyspace, user_type, klass): """ Called by the parent Cluster instance when the user registers a new @@ -4079,7 +4267,6 @@ def _handle_schema_change(self, event): self._cluster.scheduler.schedule_unique(delay, self.refresh_schema, **event) def wait_for_schema_agreement(self, connection=None, preloaded_results=None, wait_time=None): - total_timeout = wait_time if wait_time is not None else self._cluster.max_schema_agreement_wait if total_timeout <= 0: return True diff --git a/docs/api/cassandra/cluster.rst b/docs/api/cassandra/cluster.rst index 51f03f3d97..de8518d271 100644 --- a/docs/api/cassandra/cluster.rst +++ b/docs/api/cassandra/cluster.rst @@ -169,6 +169,8 @@ Clusters and Sessions .. automethod:: set_keyspace(keyspace) + .. automethod:: wait_for_schema_agreement + .. automethod:: get_execution_profile .. automethod:: execution_profile_clone_update diff --git a/tests/integration/long/test_schema.py b/tests/integration/long/test_schema.py index f892acba52..3b4dcd33d5 100644 --- a/tests/integration/long/test_schema.py +++ b/tests/integration/long/test_schema.py @@ -158,4 +158,4 @@ def check_and_wait_for_agreement(self, session, rs, exepected): time.sleep(1) assert rs.response_future.is_schema_agreed == exepected if not rs.response_future.is_schema_agreed: - session.cluster.control_connection.wait_for_schema_agreement(wait_time=1000) + session.wait_for_schema_agreement(wait_time=1000) diff --git a/tests/integration/standard/test_udts.py b/tests/integration/standard/test_udts.py index e608a9610b..18f3dfb298 100644 --- a/tests/integration/standard/test_udts.py +++ b/tests/integration/standard/test_udts.py @@ -147,7 +147,7 @@ def test_can_register_udt_before_connecting(self): c.register_user_type("udt_test_register_before_connecting2", "user", User2) s = c.connect(wait_for_all_pools=True) - c.control_connection.wait_for_schema_agreement() + s.wait_for_schema_agreement() s.execute("INSERT INTO udt_test_register_before_connecting.mytable (a, b) VALUES (%s, %s)", (0, User1(42, 'bob'))) result = s.execute("SELECT b FROM udt_test_register_before_connecting.mytable WHERE a=0") diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index a4f0ebc4d3..b6f2da5372 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -15,14 +15,16 @@ import logging import socket +from types import SimpleNamespace from unittest.mock import patch, Mock import uuid from cassandra import ConsistencyLevel, DriverException, Timeout, Unavailable, RequestExecutionException, ReadTimeout, WriteTimeout, CoordinationFailure, ReadFailure, WriteFailure, FunctionFailure, AlreadyExists,\ InvalidRequest, Unauthorized, AuthenticationFailed, OperationTimedOut, UnsupportedOperation, RequestValidationException, ConfigurationException, ProtocolVersion -from cassandra.cluster import _Scheduler, Session, Cluster, default_lbp_factory, \ +from cassandra.cluster import _Scheduler, Session, Cluster, ResultSet, SchemaAgreementScope, default_lbp_factory, \ ExecutionProfile, _ConfigMode, EXEC_PROFILE_DEFAULT +from cassandra.connection import ConnectionBusy from cassandra.pool import Host from cassandra.policies import HostDistance, RetryPolicy, RoundRobinPolicy, DowngradingConsistencyRetryPolicy, SimpleConvictionPolicy from cassandra.query import SimpleStatement, named_tuple_factory, tuple_factory @@ -247,11 +249,123 @@ def test_event_delay_timing(self, *_): class SessionTest(unittest.TestCase): + class FakeTime(object): + + def __init__(self): + self.clock = 0 + + def time(self): + return self.clock + + def sleep(self, amount): + self.clock += amount + + class MockPool(object): + + def __init__(self, host, connection): + self.host = host + self.host_distance = HostDistance.LOCAL + self.is_shutdown = False + self.connection = connection + + def _get_connection_for_routing_key(self): + return self.connection + + class MockSchemaVersionFuture(object): + + def __init__(self, outcome, auto_complete=True): + self._outcome = outcome + self._auto_complete = auto_complete + self._delivered = False + self._callback_state = None + self._col_names = ("schema_version",) + self._col_types = None + self.has_more_pages = False + self._continuous_paging_session = None + + def _deliver(self): + if self._delivered or self._callback_state is None: + return + + self._delivered = True + callback, errback, callback_args, callback_kwargs, errback_args, errback_kwargs = self._callback_state + if isinstance(self._outcome, Exception): + errback(self._outcome, *errback_args, **errback_kwargs) + else: + row = SimpleNamespace(schema_version=self._outcome) + callback([row], *callback_args, **callback_kwargs) + + def add_callbacks(self, callback, errback, + callback_args=(), callback_kwargs=None, + errback_args=(), errback_kwargs=None): + self._callback_state = ( + callback, + errback, + callback_args, + callback_kwargs or {}, + errback_args, + errback_kwargs or {}, + ) + if self._auto_complete: + self._deliver() + return self + + def complete(self): + self._deliver() + + def result(self): + if isinstance(self._outcome, Exception): + raise self._outcome + return ResultSet(self, [SimpleNamespace(schema_version=self._outcome)]) + def setUp(self): if connection_class is None: raise unittest.SkipTest('libev does not appear to be installed correctly') connection_class.initialize_reactor() + def _mock_schema_future(self, outcome): + return self.MockSchemaVersionFuture(outcome) + + def _host_query_count(self, session, target_host): + return sum(1 for call in session.execute_async.call_args_list if call.kwargs.get('host') is target_host) + + def _new_schema_agreement_session(self, schema_versions, distances=None): + hosts = [] + connections = {} + distance_map = {} + if distances is None: + distances = [HostDistance.LOCAL] * len(schema_versions) + + for index, schema_version in enumerate(schema_versions): + host = Host("127.0.0.%d" % (index + 1), SimpleConvictionPolicy, host_id=uuid.uuid4()) + host.set_up() + hosts.append(host) + distance_map[host] = distances[index] + + cluster = Cluster(protocol_version=4) + for host in hosts: + cluster.metadata.add_or_return_host(host) + + session = Session(cluster, hosts) + session._profile_manager.distance = Mock(side_effect=lambda host: distance_map.get(host, HostDistance.LOCAL)) + session._pools = {} + for host, schema_version in zip(hosts, schema_versions): + connection = Mock(endpoint=host.endpoint) + connection.future_outcomes = [schema_version] + session._pools[host] = self.MockPool(host, connection) + connections[host] = connection + + def execute_async(query, parameters=None, trace=False, + custom_payload=None, execution_profile=None, + paging_state=None, timeout=None, host=None, execute_as=None): + connection = connections[host] + outcome = connection.future_outcomes.pop(0) if len(connection.future_outcomes) > 1 else connection.future_outcomes[0] + return self._mock_schema_future(outcome) + + session.execute_async = Mock(side_effect=execute_async) + + return session, hosts, connections + # TODO: this suite could be expanded; for now just adding a test covering a PR @mock_session_pools def test_default_serial_consistency_level_ep(self, *_): @@ -339,6 +453,104 @@ def test_set_keyspace_escapes_quotes(self, *_): assert query == 'USE simple_ks', ( "Simple keyspace names should not be quoted, got: %r" % query) + @mock_session_pools + def test_wait_for_schema_agreement_default_scope_queries_all_connected_hosts(self, *_): + session, hosts, _ = self._new_schema_agreement_session( + ["a", "a"], + distances=[HostDistance.LOCAL_RACK, HostDistance.REMOTE]) + + assert session.wait_for_schema_agreement(wait_time=1) + + for host in hosts: + assert self._host_query_count(session, host) == 1 + + @mock_session_pools + def test_wait_for_schema_agreement_retries_until_local_hosts_match(self, *_): + session, hosts, connections = self._new_schema_agreement_session(["a", "b"]) + clock = self.FakeTime() + connections[hosts[1]].future_outcomes = ["b", "a"] + + with patch('cassandra.cluster.time', new=clock): + assert session.wait_for_schema_agreement(wait_time=1) + for host in hosts: + assert self._host_query_count(session, host) == 2 + assert clock.clock == 0.2 + + @mock_session_pools + def test_wait_for_schema_agreement_retries_when_local_connection_is_busy(self, *_): + session, hosts, connections = self._new_schema_agreement_session(["a", "a"]) + clock = self.FakeTime() + connections[hosts[1]].future_outcomes = [ + ConnectionBusy("connection overloaded"), + "a"] + + with patch('cassandra.cluster.time', new=clock): + assert session.wait_for_schema_agreement(wait_time=1) + for host in hosts: + assert self._host_query_count(session, host) == 2 + assert clock.clock == 0.2 + + @mock_session_pools + def test_wait_for_schema_agreement_ignores_local_hosts_without_session_pool(self, *_): + session, hosts, _ = self._new_schema_agreement_session(["a"]) + + unconnected_host = Host("127.0.0.2", SimpleConvictionPolicy, host_id=uuid.uuid4()) + unconnected_host.set_up() + session.cluster.metadata.add_or_return_host(unconnected_host) + + assert session.wait_for_schema_agreement(wait_time=1) + assert self._host_query_count(session, hosts[0]) == 1 + + @mock_session_pools + def test_wait_for_schema_agreement_queries_hosts_in_order(self, *_): + session, hosts, _ = self._new_schema_agreement_session(["a"] * 11) + + assert session.wait_for_schema_agreement(wait_time=1) + assert [call.kwargs['host'] for call in session.execute_async.call_args_list] == list(hosts) + + @mock_session_pools + def test_wait_for_schema_agreement_rack_scope_only_queries_local_rack_connections(self, *_): + session, hosts, _ = self._new_schema_agreement_session( + ["a", "a", "a"], + distances=[HostDistance.LOCAL_RACK, HostDistance.LOCAL, HostDistance.REMOTE]) + + assert session.wait_for_schema_agreement(wait_time=1, scope=SchemaAgreementScope.RACK) + + assert self._host_query_count(session, hosts[0]) == 1 + assert self._host_query_count(session, hosts[1]) == 0 + assert self._host_query_count(session, hosts[2]) == 0 + + @mock_session_pools + def test_wait_for_schema_agreement_cluster_scope_skips_ignored_hosts(self, *_): + session, hosts, _ = self._new_schema_agreement_session( + ["a", "a"], + distances=[HostDistance.IGNORED, HostDistance.LOCAL]) + + assert session.wait_for_schema_agreement(wait_time=1, scope=SchemaAgreementScope.CLUSTER) + + assert self._host_query_count(session, hosts[0]) == 0 + assert self._host_query_count(session, hosts[1]) == 1 + + @mock_session_pools + def test_wait_for_schema_agreement_cluster_scope_excludes_hosts_with_unknown_status(self, *_): + session, hosts, _ = self._new_schema_agreement_session( + ["a", "a"], + distances=[HostDistance.LOCAL_RACK, HostDistance.LOCAL]) + + hosts[0].is_up = None + + assert session.wait_for_schema_agreement(wait_time=1, scope=SchemaAgreementScope.CLUSTER) + + assert self._host_query_count(session, hosts[0]) == 0 + assert self._host_query_count(session, hosts[1]) == 1 + + @mock_session_pools + def test_wait_for_schema_agreement_rejects_unknown_scope(self, *_): + session, _, _ = self._new_schema_agreement_session(["a"]) + + with pytest.raises(ValueError): + session.wait_for_schema_agreement(wait_time=1, scope='planet') + class ProtocolVersionTests(unittest.TestCase): def test_protocol_downgrade_test(self): diff --git a/tests/unit/test_session_schema_agreement.py b/tests/unit/test_session_schema_agreement.py new file mode 100644 index 0000000000..ffad687fcc --- /dev/null +++ b/tests/unit/test_session_schema_agreement.py @@ -0,0 +1,204 @@ +from datetime import timedelta +from types import SimpleNamespace +from unittest.mock import Mock +import uuid + +import pytest + +import cassandra.cluster as cluster_module +from cassandra.connection import ConnectionBusy +from cassandra.cluster import ControlConnection, Session, ResultSet +from cassandra.policies import HostDistance, SimpleConvictionPolicy +from cassandra.pool import Host +from cassandra.util import maybe_add_timeout_to_query + + +class FakeTime: + def __init__(self): + self.clock = 0 + + def time(self): + return self.clock + + def sleep(self, amount): + self.clock += amount + + +class MockPool: + def __init__(self, host): + self.host = host + self.is_shutdown = False + + +class MockSchemaVersionFuture: + def __init__(self, outcome, auto_complete=True): + self._outcome = outcome + self._auto_complete = auto_complete + self._delivered = False + self._callback_state = None + self._col_names = ("schema_version",) + self._col_types = None + self.has_more_pages = False + self._continuous_paging_session = None + + def _deliver(self): + if self._delivered or self._callback_state is None: + return + + self._delivered = True + callback, errback, callback_args, callback_kwargs, errback_args, errback_kwargs = self._callback_state + if isinstance(self._outcome, Exception): + errback(self._outcome, *errback_args, **errback_kwargs) + else: + row = SimpleNamespace(schema_version=self._outcome) + callback([row], *callback_args, **callback_kwargs) + + def add_callbacks(self, callback, errback, + callback_args=(), callback_kwargs=None, + errback_args=(), errback_kwargs=None): + self._callback_state = ( + callback, + errback, + callback_args, + callback_kwargs or {}, + errback_args, + errback_kwargs or {}, + ) + if self._auto_complete: + self._deliver() + return self + + def complete(self): + self._deliver() + + def result(self): + if isinstance(self._outcome, Exception): + raise self._outcome + return ResultSet(self, [SimpleNamespace(schema_version=self._outcome)]) + + +def _host_query_count(session, target_host): + return sum(1 for call in session.execute_async.call_args_list if call.kwargs.get("host") is target_host) + + +def _new_session(schema_versions, distances=None, metadata_request_timeout=timedelta(seconds=2), timeout=2.0): + hosts = [] + connections = {} + distance_map = {} + + if distances is None: + distances = [HostDistance.LOCAL] * len(schema_versions) + + for index, schema_version in enumerate(schema_versions): + host = Host("127.0.0.%d" % (index + 1), SimpleConvictionPolicy, host_id=uuid.uuid4()) + host.set_up() + hosts.append(host) + distance_map[host] = distances[index] + + cluster = SimpleNamespace( + max_schema_agreement_wait=10, + control_connection=SimpleNamespace( + _timeout=timeout, + _metadata_request_timeout=metadata_request_timeout, + ), + ) + + session = Session.__new__(Session) + session.cluster = cluster + session._profile_manager = SimpleNamespace(distance=lambda host: distance_map.get(host, HostDistance.LOCAL)) + session._pools = {} + session.is_shutdown = False + + for host, schema_version in zip(hosts, schema_versions): + connection = Mock(endpoint=host.endpoint) + connection.future_outcomes = [schema_version] + session._pools[host] = MockPool(host) + connections[host] = connection + + def execute_async(query, parameters=None, trace=False, + custom_payload=None, execution_profile=None, + paging_state=None, timeout=None, host=None, execute_as=None): + connection = connections[host] + outcome = connection.future_outcomes.pop(0) if len(connection.future_outcomes) > 1 else connection.future_outcomes[0] + return MockSchemaVersionFuture(outcome) + + session.execute_async = Mock(side_effect=execute_async) + + return session, hosts, connections + + +def test_wait_for_schema_agreement_retries_with_module_time(monkeypatch): + session, hosts, connections = _new_session(["a", "b"]) + clock = FakeTime() + monkeypatch.setattr(cluster_module, "time", clock) + connections[hosts[1]].future_outcomes = ["b", "a"] + + assert session.wait_for_schema_agreement(wait_time=1) + assert clock.clock == pytest.approx(0.2) + for host in hosts: + assert _host_query_count(session, host) == 2 + + +@pytest.mark.parametrize("wait_time", [0, -1]) +def test_wait_for_schema_agreement_rejects_non_positive_wait_time(wait_time): + session, _, _ = _new_session(["a"]) + + with pytest.raises(ValueError, match="wait_time must be greater than 0"): + session.wait_for_schema_agreement(wait_time=wait_time) + + assert session.execute_async.call_count == 0 + + +def test_wait_for_schema_agreement_returns_false_when_no_hosts_match_scope(monkeypatch): + session, _, _ = _new_session(["a"], distances=[HostDistance.IGNORED]) + clock = FakeTime() + monkeypatch.setattr(cluster_module, "time", clock) + + assert session.wait_for_schema_agreement(wait_time=1) is False + assert session.execute_async.call_count == 0 + assert clock.clock == pytest.approx(1.0) + + +def test_wait_for_schema_agreement_uses_host_targeted_session_queries(): + session, hosts, _ = _new_session(["a", "a"]) + + assert session.wait_for_schema_agreement(wait_time=0.1) + + expected_query = maybe_add_timeout_to_query( + ControlConnection._SELECT_SCHEMA_LOCAL, + timedelta(seconds=2), + ) + assert session.execute_async.call_count == 2 + assert [call.args[0] for call in session.execute_async.call_args_list] == [expected_query, expected_query] + assert [call.kwargs["host"] for call in session.execute_async.call_args_list] == hosts + for call in session.execute_async.call_args_list: + assert 0 < call.kwargs["timeout"] <= 0.1 + + +def test_wait_for_schema_agreement_retries_after_host_targeted_query_error(monkeypatch): + session, hosts, connections = _new_session(["a", "a"]) + clock = FakeTime() + monkeypatch.setattr(cluster_module, "time", clock) + connections[hosts[1]].future_outcomes = [ConnectionBusy("connection overloaded"), "a"] + + assert session.wait_for_schema_agreement(wait_time=1) + assert clock.clock == pytest.approx(0.2) + for host in hosts: + assert _host_query_count(session, host) == 2 + + +def test_wait_for_schema_agreement_queries_hosts_in_order_under_one_deadline(monkeypatch): + session, hosts, _ = _new_session(["a", "a", "a"]) + clock = FakeTime() + monkeypatch.setattr(cluster_module, "time", clock) + + def execute_async(query, parameters=None, trace=False, + custom_payload=None, execution_profile=None, + paging_state=None, timeout=None, host=None, execute_as=None): + clock.sleep(0.01) + return MockSchemaVersionFuture("a") + + session.execute_async = Mock(side_effect=execute_async) + + assert session.wait_for_schema_agreement(wait_time=1) + assert [call.kwargs["host"] for call in session.execute_async.call_args_list] == hosts From ef7c2d0f2ae557210cdb738f2587bcb2d97fddd7 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 7 May 2026 00:46:34 -0400 Subject: [PATCH 283/298] control-connection: deprecate ControlConnection.wait_for_schema_agreement Keep ControlConnection.wait_for_schema_agreement() as a compatibility wrapper, but move the existing implementation to _wait_for_schema_agreement() and deprecate the public method in favor of Session.wait_for_schema_agreement(). This lets the control-connection refresh path continue using the old logic internally without emitting warnings. The control-connection wait path was designed for internal metadata refresh use, not as a user-facing schema agreement API. It observes schema agreement from one single node, assuming that schema change statement have been ran on that host. Using it by users will lead to false positives, if user ran statement on a host different from host of control connection. Update the unit tests to call the internal helper everywhere a warning is not expected, add explicit deprecation coverage for the public wrapper, and set stacklevel=2 so the warning points at the caller instead of inside the driver. --- cassandra/cluster.py | 26 ++++++++++++++++++++- tests/unit/test_control_connection.py | 33 +++++++++++++++++++-------- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index b55fbd5172..483843c2a6 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -3974,7 +3974,7 @@ def _refresh_schema(self, connection, preloaded_results=None, schema_agreement_w if self._cluster.is_shutdown: return False - agreed = self.wait_for_schema_agreement(connection, + agreed = self._wait_for_schema_agreement(connection=connection, preloaded_results=preloaded_results, wait_time=schema_agreement_wait) @@ -4267,6 +4267,30 @@ def _handle_schema_change(self, event): self._cluster.scheduler.schedule_unique(delay, self.refresh_schema, **event) def wait_for_schema_agreement(self, connection=None, preloaded_results=None, wait_time=None): + """ + Wait for schema agreement from the control connection's metadata view. + + This method is intended for internal metadata refresh flows. External + callers should use :meth:`.Session.wait_for_schema_agreement` instead. + + The control connection observes schema agreement from its own + perspective, which may include hosts the session is not using, and it + may fail when the control connection itself is transiently unhealthy. + That can produce false positives or failures that do not reflect + whether a session can safely proceed. + + .. deprecated:: 3.30.0 + Use :meth:`.Session.wait_for_schema_agreement` instead. + """ + warn("ControlConnection.wait_for_schema_agreement is deprecated and will be removed in 4.0. " + "Use Session.wait_for_schema_agreement instead. " + "This method is for internal metadata refresh use only.", + DeprecationWarning, stacklevel=2) + return self._wait_for_schema_agreement(connection=connection, + preloaded_results=preloaded_results, + wait_time=wait_time) + + def _wait_for_schema_agreement(self, connection=None, preloaded_results=None, wait_time=None): total_timeout = wait_time if wait_time is not None else self._cluster.max_schema_agreement_wait if total_timeout <= 0: return True diff --git a/tests/unit/test_control_connection.py b/tests/unit/test_control_connection.py index 037d4a8888..fd62323f33 100644 --- a/tests/unit/test_control_connection.py +++ b/tests/unit/test_control_connection.py @@ -15,7 +15,7 @@ import unittest from concurrent.futures import ThreadPoolExecutor -from unittest.mock import Mock, ANY, call +from unittest.mock import Mock, ANY, call, patch from cassandra import OperationTimedOut, SchemaTargetType, SchemaChangeType from cassandra.protocol import ResultMessage, RESULT_KIND_ROWS @@ -210,16 +210,27 @@ def test_wait_for_schema_agreement(self): """ Basic test with all schema versions agreeing """ - assert self.control_connection.wait_for_schema_agreement() + assert self.control_connection._wait_for_schema_agreement() # the control connection should not have slept at all assert self.time.clock == 0 + @patch('cassandra.cluster.warn') + def test_wait_for_schema_agreement_warns_about_deprecation(self, mocked_warn): + assert self.control_connection.wait_for_schema_agreement() + + mocked_warn.assert_called_once() + warning_args, warning_kwargs = mocked_warn.call_args + assert 'ControlConnection.wait_for_schema_agreement is deprecated' in str(warning_args[0]) + assert 'Use Session.wait_for_schema_agreement instead.' in str(warning_args[0]) + assert warning_args[1] is DeprecationWarning + assert warning_kwargs['stacklevel'] == 2 + def test_wait_for_schema_agreement_uses_preloaded_results_if_given(self): """ wait_for_schema_agreement uses preloaded results if given for shared table queries """ preloaded_results = self._matching_schema_preloaded_results - assert self.control_connection.wait_for_schema_agreement(preloaded_results=preloaded_results) + assert self.control_connection._wait_for_schema_agreement(preloaded_results=preloaded_results) # the control connection should not have slept at all assert self.time.clock == 0 # the connection should not have made any queries if given preloaded results @@ -230,7 +241,7 @@ def test_wait_for_schema_agreement_falls_back_to_querying_if_schemas_dont_match_ wait_for_schema_agreement requery if schema does not match using preloaded results """ preloaded_results = self._nonmatching_schema_preloaded_results - assert self.control_connection.wait_for_schema_agreement(preloaded_results=preloaded_results) + assert self.control_connection._wait_for_schema_agreement(preloaded_results=preloaded_results) # the control connection should not have slept at all assert self.time.clock == 0 assert self.connection.wait_for_responses.call_count == 1 @@ -241,7 +252,7 @@ def test_wait_for_schema_agreement_fails(self): """ # change the schema version on one node self.connection.peer_results[1][1][2] = 'b' - assert not self.control_connection.wait_for_schema_agreement() + assert not self.control_connection._wait_for_schema_agreement() # the control connection should have slept until it hit the limit assert self.time.clock >= self.cluster.max_schema_agreement_wait @@ -262,7 +273,7 @@ def test_wait_for_schema_agreement_skipping(self): self.connection.peer_results[1][1][3] = 'c' self.cluster.metadata.get_host(DefaultEndPoint('192.168.1.1')).is_up = False - assert self.control_connection.wait_for_schema_agreement() + assert self.control_connection._wait_for_schema_agreement() assert self.time.clock == 0 def test_wait_for_schema_agreement_rpc_lookup(self): @@ -279,12 +290,12 @@ def test_wait_for_schema_agreement_rpc_lookup(self): # even though the new host has a different schema version, it's # marked as down, so the control connection shouldn't care - assert self.control_connection.wait_for_schema_agreement() + assert self.control_connection._wait_for_schema_agreement() assert self.time.clock == 0 # but once we mark it up, the control connection will care host.is_up = True - assert not self.control_connection.wait_for_schema_agreement() + assert not self.control_connection._wait_for_schema_agreement() assert self.time.clock >= self.cluster.max_schema_agreement_wait @@ -299,7 +310,7 @@ def test_wait_for_schema_agreement_none_timeout(self): status_event_refresh_window=0) cc._connection = self.connection cc._time = self.time - assert cc.wait_for_schema_agreement() + assert cc._wait_for_schema_agreement() def test_refresh_nodes_and_tokens(self): self.control_connection.refresh_node_list_and_token_map() @@ -441,7 +452,8 @@ def bad_wait_for_responses(*args, **kwargs): self.control_connection.refresh_node_list_and_token_map() self.cluster.executor.submit.assert_called_with(self.control_connection._reconnect) - def test_refresh_schema_timeout(self): + @patch('cassandra.cluster.warn') + def test_refresh_schema_timeout(self, mocked_warn): def bad_wait_for_responses(*args, **kwargs): self.time.sleep(kwargs['timeout']) @@ -451,6 +463,7 @@ def bad_wait_for_responses(*args, **kwargs): self.control_connection.refresh_schema() assert self.connection.wait_for_responses.call_count == self.cluster.max_schema_agreement_wait / self.control_connection._timeout assert self.connection.wait_for_responses.call_args[1]['timeout'] == self.control_connection._timeout + mocked_warn.assert_not_called() def test_handle_topology_change(self): event = { From 51dd3668d6d16339832e2fc22fd7112dfb636670 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 7 May 2026 09:55:28 -0400 Subject: [PATCH 284/298] connection: clean up failed heartbeat sends Keep heartbeat request-id and in-flight bookkeeping consistent when send_msg() fails.\n\nHandle the control-connection in_flight release separately from HostConnection cleanup. --- cassandra/connection.py | 14 +++++++++++++- tests/unit/test_connection.py | 27 ++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/cassandra/connection.py b/cassandra/connection.py index 08501d0a2b..f07160e385 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -1816,7 +1816,19 @@ def __init__(self, connection, owner): with connection.lock: if connection.in_flight < connection.max_request_id: connection.in_flight += 1 - connection.send_msg(OptionsMessage(), connection.get_request_id(), self._options_callback) + request_id = connection.get_request_id() + try: + connection.send_msg(OptionsMessage(), request_id, self._options_callback) + except Exception as exc: + if connection.is_control_connection: + connection.in_flight -= 1 + # send_msg() registers the callback before writing to the socket, + # so a write failure must unwind that registration here. + connection._requests.pop(request_id, None) + if request_id not in connection.request_ids: + connection.request_ids.append(request_id) + self._exception = exc + self._event.set() else: self._exception = Exception("Failed to send heartbeat because connection 'in_flight' exceeds threshold") self._event.set() diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 2fa7c71196..cf4607fbed 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -21,7 +21,7 @@ from cassandra import OperationTimedOut from cassandra.cluster import Cluster from cassandra.connection import (Connection, HEADER_DIRECTION_TO_CLIENT, ProtocolError, - locally_supported_compressions, ConnectionHeartbeat, _Frame, Timer, TimerManager, + locally_supported_compressions, ConnectionHeartbeat, HeartbeatFuture, _Frame, Timer, TimerManager, ConnectionException, ConnectionShutdown, DefaultEndPoint, ShardAwarePortGenerator) from cassandra.marshal import uint8_pack, uint32_pack, int32_pack from cassandra.protocol import (write_stringmultimap, write_int, write_string, @@ -463,6 +463,31 @@ def test_no_req_ids(self, *args): holder.return_connection.assert_has_calls( [call(max_connection)] * get_holders.call_count) + def test_heartbeat_future_releases_request_id_when_send_fails(self, *args): + connection = Connection(DefaultEndPoint('1.2.3.4')) + connection.push = Mock(side_effect=ConnectionException("write failed")) + owner = Mock() + initial_in_flight = connection.in_flight + initial_request_ids = len(connection.request_ids) + + # HostConnection.return_connection releases the heartbeat's in-flight slot. + def return_connection(conn): + with conn.lock: + conn.in_flight -= 1 + + owner.return_connection.side_effect = return_connection + + future = HeartbeatFuture(connection, owner) + + with pytest.raises(ConnectionException): + future.wait(0) + + owner.return_connection(connection) + + assert connection.in_flight == initial_in_flight + assert len(connection.request_ids) == initial_request_ids + assert not connection._requests + def test_unexpected_response(self, *args): request_id = 999 From 84b599c21946b3f832b682d8377bcfbb67037a72 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Thu, 7 May 2026 01:58:39 -0400 Subject: [PATCH 285/298] cluster: add control-connection query fallback Add an opt-in control-connection fallback for application queries when the driver cannot populate normal node pools, which happens in deployments that expose the cluster through a non-broadcast IP address such as a TCP proxy or a node public IP. In that mode the driver can still execute queries over the single control connection, but throughput is poor and connection churn increases the chance of request errors. This option is intentionally disabled by default and should not be used in production. Also propagate keyspace updates on the fallback path so USE keeps the control connection in sync. Tests: - tests/unit/test_cluster.py::ClusterTest::test_set_keyspace_for_all_pools_reports_all_errors - tests/unit/test_response_future.py::ResponseFutureTests::test_control_connection_fallback_updates_connection_keyspace --- cassandra/cluster.py | 233 +++++++++++++-- docs/api/cassandra/cluster.rst | 5 + .../integration/cqlengine/model/test_model.py | 10 +- tests/integration/standard/conftest.py | 1 + .../test_control_connection_query_fallback.py | 115 +++++++ tests/unit/test_cluster.py | 77 ++++- tests/unit/test_response_future.py | 281 +++++++++++++++++- 7 files changed, 689 insertions(+), 33 deletions(-) create mode 100644 tests/integration/standard/test_control_connection_query_fallback.py diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 483843c2a6..1181c6f686 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -28,6 +28,7 @@ from copy import copy from functools import partial, reduce, wraps from itertools import groupby, count, chain +import enum import json import logging from typing import Any, Dict, Optional, Union, Tuple @@ -514,8 +515,9 @@ def __init__(self, load_balancing_policy=None, retry_policy=None, class ProfileManager(object): - def __init__(self): + def __init__(self, pools_allowed: bool=True): self.profiles = dict() + self.pools_allowed = pools_allowed def _profiles_without_explicit_lbps(self): names = (profile_name for @@ -527,6 +529,8 @@ def _profiles_without_explicit_lbps(self): ) def distance(self, host): + if not self.pools_allowed: + return HostDistance.IGNORED distances = set(p.load_balancing_policy.distance(host) for p in self.profiles.values()) return HostDistance.LOCAL_RACK if HostDistance.LOCAL_RACK in distances else \ HostDistance.LOCAL if HostDistance.LOCAL in distances else \ @@ -542,10 +546,14 @@ def check_supported(self): p.load_balancing_policy.check_supported() def on_up(self, host): + if not self.pools_allowed: + return for p in self.profiles.values(): p.load_balancing_policy.on_up(host) def on_down(self, host): + if not self.pools_allowed: + return for p in self.profiles.values(): p.load_balancing_policy.on_down(host) @@ -619,6 +627,31 @@ class _ConfigMode(object): PROFILES = 2 +class ControlConnectionQueryFallback(enum.Enum): + """ + Controls how application queries use the control connection when node pools + are unavailable. + + ``Disabled`` requires a usable node pool for application queries. If the + driver cannot establish one during session startup, it raises + :class:`NoHostAvailable`. + + ``Fallback`` still attempts to create node pools, but allows application + queries to fall back to the control connection when no usable node pool is + available. Session startup is allowed to proceed even if the initial pool + attempts all fail. + + ``SkipPoolCreation`` disables node-pool creation for the session and uses + the control-connection fallback path for application queries. + + The fallback path is not used for requests targeted to an explicit host. + """ + + Disabled = "Disabled" + Fallback = "Fallback" + SkipPoolCreation = "SkipPoolCreation" + + class Cluster(object): """ The main class to use when interacting with a Cassandra cluster. @@ -939,6 +972,16 @@ def default_retry_policy(self, policy): If set to :const:`None`, there will be no timeout for these queries. """ + allow_control_connection_query_fallback: ControlConnectionQueryFallback = ControlConnectionQueryFallback.Disabled + """ + Controls whether application queries may fall back to the control connection. + + ``Disabled`` keeps the old behavior. + ``Fallback`` enables control-connection fallback when no usable node pools exist. + ``SkipPoolCreation`` skips node-pool creation and uses the control connection fallback path. + This fallback is still not used for requests targeted to an explicit host. + """ + idle_heartbeat_interval = 30 """ Interval, in seconds, on which to heartbeat idle connections. This helps @@ -1225,7 +1268,8 @@ def __init__(self, metadata_request_timeout: Optional[float] = None, column_encryption_policy=None, application_info:Optional[ApplicationInfoBase]=None, - client_routes_config:Optional[ClientRoutesConfig]=None + client_routes_config:Optional[ClientRoutesConfig]=None, + allow_control_connection_query_fallback:Optional[ControlConnectionQueryFallback]=ControlConnectionQueryFallback.Disabled ): """ ``executor_threads`` defines the number of threads in a pool for handling asynchronous tasks such as @@ -1243,6 +1287,10 @@ def __init__(self, if port < 1 or port > 65535: raise ValueError("Invalid port number (%s) (1-65535)" % port) + if not isinstance(allow_control_connection_query_fallback, ControlConnectionQueryFallback): + raise TypeError( + "allow_control_connection_query_fallback must be a ControlConnectionQueryFallback value") + if connection_class is not None: self.connection_class = connection_class @@ -1404,7 +1452,8 @@ def __init__(self, else: self.timestamp_generator = MonotonicTimestampGenerator() - self.profile_manager = ProfileManager() + self.profile_manager = ProfileManager( + pools_allowed=allow_control_connection_query_fallback != ControlConnectionQueryFallback.SkipPoolCreation) self.profile_manager.profiles[EXEC_PROFILE_DEFAULT] = ExecutionProfile( self.load_balancing_policy, self.default_retry_policy, @@ -1473,6 +1522,7 @@ def __init__(self, self.cql_version = cql_version self.max_schema_agreement_wait = max_schema_agreement_wait self.control_connection_timeout = control_connection_timeout + self.allow_control_connection_query_fallback = allow_control_connection_query_fallback self.metadata_request_timeout = self.control_connection_timeout if metadata_request_timeout is None else metadata_request_timeout self.idle_heartbeat_interval = idle_heartbeat_interval self.idle_heartbeat_timeout = idle_heartbeat_timeout @@ -1815,7 +1865,8 @@ def get_all_pools(self): return pools def is_shard_aware(self): - return bool(self.get_all_pools()[0].host.sharding_info) + pools = self.get_all_pools() + return bool(pools and pools[0].host.sharding_info) def shard_aware_stats(self): if self.is_shard_aware(): @@ -1920,7 +1971,7 @@ def on_up(self, host): """ Intended for internal use only. """ - if self.is_shutdown: + if self.is_shutdown or self.allow_control_connection_query_fallback == ControlConnectionQueryFallback.SkipPoolCreation: return log.debug("Waiting to acquire lock for handling up status of node %s", host) @@ -2028,7 +2079,7 @@ def on_down(self, host, is_host_addition, expect_host_to_be_down=False): """ Intended for internal use only. """ - if self.is_shutdown: + if self.is_shutdown or self.allow_control_connection_query_fallback == ControlConnectionQueryFallback.SkipPoolCreation: return with host.lock: @@ -2633,20 +2684,24 @@ def __init__(self, cluster, hosts, keyspace=None): # create connection pools in parallel self._initial_connect_futures = set() - for host in hosts: - future = self.add_or_renew_pool(host, is_host_addition=False) - if future: - self._initial_connect_futures.add(future) - - futures = wait_futures(self._initial_connect_futures, return_when=FIRST_COMPLETED) - while futures.not_done and not any(f.result() for f in futures.done): - futures = wait_futures(futures.not_done, return_when=FIRST_COMPLETED) - - if not any(f.result() for f in self._initial_connect_futures): - msg = "Unable to connect to any servers" - if self.keyspace: - msg += " using keyspace '%s'" % self.keyspace - raise NoHostAvailable(msg, [h.address for h in hosts]) + fallback_mode = self.cluster.allow_control_connection_query_fallback + if fallback_mode is not ControlConnectionQueryFallback.SkipPoolCreation: + for host in hosts: + future = self.add_or_renew_pool(host, is_host_addition=False) + if future: + self._initial_connect_futures.add(future) + + futures = wait_futures(self._initial_connect_futures, return_when=FIRST_COMPLETED) + while futures.not_done and not any(f.result() for f in futures.done): + futures = wait_futures(futures.not_done, return_when=FIRST_COMPLETED) + + # Only Disabled requires an initial pool to come up. + if not any(f.result() for f in self._initial_connect_futures) and \ + fallback_mode is ControlConnectionQueryFallback.Disabled: + msg = "Unable to connect to any servers" + if self.keyspace: + msg += " using keyspace '%s'" % self.keyspace + raise NoHostAvailable(msg, [h.address for h in hosts]) self.session_id = uuid.uuid4() @@ -3245,6 +3300,9 @@ def add_or_renew_pool(self, host, is_host_addition): """ For internal use only. """ + if self.cluster.allow_control_connection_query_fallback is ControlConnectionQueryFallback.SkipPoolCreation: + return None + distance = self._profile_manager.distance(host) if distance == HostDistance.IGNORED: return None @@ -3315,6 +3373,9 @@ def update_created_pools(self): For internal use only. """ + if self.cluster.allow_control_connection_query_fallback is ControlConnectionQueryFallback.SkipPoolCreation: + return set() + futures = set() for host in self.cluster.metadata.all_hosts(): distance = self._profile_manager.distance(host) @@ -4650,6 +4711,7 @@ class ResponseFuture(object): _spec_execution_plan = NoSpeculativeExecutionPlan() _continuous_paging_session = None _host = None + _control_connection_query_attempted = False _TABLET_ROUTING_CTYPE = None _warned_timeout = False @@ -4670,6 +4732,7 @@ def __init__(self, session, message, query, timeout, metrics=None, prepared_stat self._callback_lock = Lock() self._start_time = start_time or time.time() self._host = host + self._control_connection_query_attempted = False self._spec_execution_plan = speculative_execution_plan or self._spec_execution_plan self._make_query_plan() self._event = Event() @@ -4748,11 +4811,22 @@ def _on_timeout(self, _attempts=0): self._connection.orphaned_threshold_reached = True pool.return_connection(self._connection, stream_was_orphaned=True) + elif self._connection.is_control_connection: + with self._connection.lock: + self._connection.orphaned_request_ids.add(self._req_id) + if len(self._connection.orphaned_request_ids) >= self._connection.orphaned_threshold: + self._connection.orphaned_threshold_reached = True errors = self._errors if not errors: if self.is_schema_agreed: - key = str(self._current_host.endpoint) if self._current_host else 'no host queried before timeout' + if self._current_host is None: + key = 'no host queried before timeout' + elif self._connection is not None and self._connection.is_control_connection: + control_host = self.session.cluster.get_control_connection_host() + key = str(control_host.endpoint) if control_host is not None else str(self._connection.endpoint) + else: + key = str(self._current_host.endpoint) errors = {key: "Client request timeout. See Session.execute[_async](timeout)"} else: connection = self.session.cluster.control_connection._connection @@ -4810,14 +4884,110 @@ def send_request(self, error_no_hosts=True): self._on_timeout() return True if error_no_hosts: + if self._fallback_to_control_connection(): + req_id = self._query_control_connection() + if req_id is not None: + self._req_id = req_id + return True + self._set_final_exception(NoHostAvailable( "Unable to complete the operation against any hosts", self._errors)) return False + def _has_usable_node_pool(self): + try: + pools = tuple(self.session._pools.values()) + except (AttributeError, TypeError): + return False + + return any(pool and not pool.is_shutdown for pool in pools) + + def _fallback_to_control_connection(self): + fallback_mode = self.session.cluster.allow_control_connection_query_fallback + if fallback_mode is ControlConnectionQueryFallback.Disabled: + return False + if self._host or self._control_connection_query_attempted: + return False + if fallback_mode is ControlConnectionQueryFallback.SkipPoolCreation: + return True + return not self._has_usable_node_pool() + + def _borrow_control_connection(self, connection): + with connection.lock: + if connection.in_flight >= connection.max_request_id: + raise NoConnectionsAvailable("All request IDs are currently in use") + connection.in_flight += 1 + return connection.get_request_id() + + def _release_control_connection_request(self, connection, request_id): + with connection.lock: + connection.in_flight -= 1 + connection.request_ids.append(request_id) + connection._requests.pop(request_id, None) + + def _handle_control_connection_response(self, connection, cb, response): + with connection.lock: + connection.in_flight -= 1 + cb(response) + + def _query_control_connection(self, message=None, cb=None, connection=None, host=None): + self._control_connection_query_attempted = True + + if message is None: + message = self.message + + if connection is None: + control_connection = self.session.cluster.control_connection + connection = control_connection._connection if control_connection else None + if not connection: + self._errors['control connection'] = ConnectionException("Control connection is not connected") + return None + + if host is None: + host = self.session.cluster.get_control_connection_host() or connection.endpoint + self._current_host = host + + request_id = None + request_sent = False + try: + request_id = self._borrow_control_connection(connection) + self._connection = connection + result_meta = self.prepared_statement.result_metadata if self.prepared_statement else [] + if cb is None: + cb = partial(self._set_result, host, connection, None) + cb = partial(self._handle_control_connection_response, connection, cb) + + log.debug("No usable node pools; falling back to control connection for host %s", host) + self.request_encoded_size = connection.send_msg(message, request_id, cb=cb, + encoder=self._protocol_handler.encode_message, + decoder=self._protocol_handler.decode_message, + result_metadata=result_meta) + request_sent = True + self.attempted_hosts.append(host) + return request_id + except NoConnectionsAvailable as exc: + log.debug("Control connection is at capacity") + self._errors[host] = exc + except ConnectionBusy as exc: + log.debug("Control connection is busy") + self._errors[host] = exc + except Exception as exc: + log.debug("Error querying control connection", exc_info=True) + self._errors[host] = exc + if self._metrics is not None: + self._metrics.on_connection_error() + finally: + if request_id is not None and not request_sent: + self._release_control_connection_request(connection, request_id) + + return None + def _query(self, host, message=None, cb=None): if message is None: message = self.message + self._control_connection_query_attempted = False + pool = self.session._pools.get(host) if not pool: self._errors[host] = ConnectionException("Host has been marked down or removed") @@ -4928,12 +5098,17 @@ def start_fetching_next_page(self): self._event.clear() self._final_result = _NOT_SET self._final_exception = None + self._control_connection_query_attempted = False self._start_timer() self.send_request() def _reprepare(self, prepare_message, host, connection, pool): cb = partial(self.session.submit, self._execute_after_prepare, host, connection, pool) - request_id = self._query(host, prepare_message, cb=cb) + if pool is None and connection is not None and connection.is_control_connection: + request_id = self._query_control_connection(prepare_message, cb=cb, + connection=connection, host=host) + else: + request_id = self._query(host, prepare_message, cb=cb) if request_id is None: # try to submit the original prepared statement on some other host self.send_request() @@ -4972,6 +5147,8 @@ def _set_result(self, host, connection, pool, response): if isinstance(response, ResultMessage): if response.kind == RESULT_KIND_SET_KEYSPACE: session = getattr(self, 'session', None) + if connection is not None: + connection.keyspace = response.new_keyspace # since we're running on the event loop thread, we need to # use a non-blocking method for setting the keyspace on # all connections in this session, otherwise the event @@ -5148,10 +5325,13 @@ def _execute_after_prepare(self, host, connection, pool, response): new_metadata_id = response.result_metadata_id if new_metadata_id is not None: self.prepared_statement.result_metadata_id = new_metadata_id - + # use self._query to re-use the same host and # at the same time properly borrow the connection - request_id = self._query(host) + if pool is None and connection is not None and connection.is_control_connection: + request_id = self._query_control_connection(connection=connection, host=host) + else: + request_id = self._query(host) if request_id is None: # this host errored out, move on to the next self.send_request() @@ -5264,6 +5444,11 @@ def _retry_task(self, reuse_connection, host): # to retry the operation return + if self._control_connection_query_attempted: + self._control_connection_query_attempted = False + self.send_request() + return + if reuse_connection and self._query(host) is not None: return diff --git a/docs/api/cassandra/cluster.rst b/docs/api/cassandra/cluster.rst index de8518d271..44b7b63f67 100644 --- a/docs/api/cassandra/cluster.rst +++ b/docs/api/cassandra/cluster.rst @@ -48,6 +48,8 @@ Clusters and Sessions .. autoattribute:: control_connection_timeout + .. autoattribute:: allow_control_connection_query_fallback + .. autoattribute:: idle_heartbeat_interval .. autoattribute:: idle_heartbeat_timeout @@ -106,6 +108,9 @@ Clusters and Sessions .. automethod:: set_meta_refresh_enabled +.. autoclass:: ControlConnectionQueryFallback + :members: + .. autoclass:: ExecutionProfile (load_balancing_policy=, retry_policy=None, consistency_level=ConsistencyLevel.LOCAL_ONE, serial_consistency_level=None, request_timeout=10.0, row_factory=, speculative_execution_policy=None) :members: :exclude-members: consistency_level diff --git a/tests/integration/cqlengine/model/test_model.py b/tests/integration/cqlengine/model/test_model.py index cafe6ae9c9..98d71993fd 100644 --- a/tests/integration/cqlengine/model/test_model.py +++ b/tests/integration/cqlengine/model/test_model.py @@ -259,10 +259,8 @@ class SensitiveModel(Model): rows[-1] rows[-1:] - # ignore DeprecationWarning('The loop argument is deprecated since Python 3.8, and scheduled for removal in Python 3.10.') - relevant_warnings = [warn for warn in w if "The loop argument is deprecated" not in str(warn.message)] + warning_messages = [str(warn.message) for warn in w] - assert "__table_name_case_sensitive__ will be removed in 4.0." in str(relevant_warnings[0].message) - assert "__table_name_case_sensitive__ will be removed in 4.0." in str(relevant_warnings[1].message) - assert "ModelQuerySet indexing with negative indices support will be removed in 4.0." in str(relevant_warnings[2].message) - assert "ModelQuerySet slicing with negative indices support will be removed in 4.0." in str(relevant_warnings[3].message) + assert sum("__table_name_case_sensitive__ will be removed in 4.0." in message for message in warning_messages) == 2 + assert sum("ModelQuerySet indexing with negative indices support will be removed in 4.0." in message for message in warning_messages) == 1 + assert sum("ModelQuerySet slicing with negative indices support will be removed in 4.0." in message for message in warning_messages) == 1 diff --git a/tests/integration/standard/conftest.py b/tests/integration/standard/conftest.py index 3adaf371b0..9934cfcbbb 100644 --- a/tests/integration/standard/conftest.py +++ b/tests/integration/standard/conftest.py @@ -37,6 +37,7 @@ "test_ip_change": 4, "test_authentication": 4, "test_authentication_misconfiguration": 4, + "test_control_connection_query_fallback": 4, "test_custom_cluster": 4, "test_query": 4, # Group 5: tablets (destructive — decommissions a node) diff --git a/tests/integration/standard/test_control_connection_query_fallback.py b/tests/integration/standard/test_control_connection_query_fallback.py new file mode 100644 index 0000000000..e64763a72c --- /dev/null +++ b/tests/integration/standard/test_control_connection_query_fallback.py @@ -0,0 +1,115 @@ +# Copyright DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +import pytest + +from cassandra.cluster import ControlConnectionQueryFallback, NoHostAvailable + +from tests.integration import USE_CASS_EXTERNAL, TestCluster, local, remove_cluster, use_cluster + + +_CLUSTER_NAME = "control_connection_query_fallback" +_UNREACHABLE_BROADCAST_RPC_ADDRESS = "127.255.255.1" + + +def setup_module(): + if USE_CASS_EXTERNAL: + return + + remove_cluster() + + ccm_cluster = use_cluster(_CLUSTER_NAME, [1], start=False) + ccm_cluster.nodes["node1"].set_configuration_options(values={ + "broadcast_rpc_address": _UNREACHABLE_BROADCAST_RPC_ADDRESS, + }) + ccm_cluster.start(wait_for_binary_proto=True, wait_other_notice=True) + + +def teardown_module(): + if USE_CASS_EXTERNAL: + return + + remove_cluster() + + +@local +class ControlConnectionQueryFallbackIntegrationTests(unittest.TestCase): + + def setUp(self): + self.cluster = None + + def tearDown(self): + if self.cluster is not None: + self.cluster.shutdown() + + def _assert_unreachable_broadcast_rpc_metadata(self): + hosts = self.cluster.metadata.all_hosts() + assert len(hosts) == 1 + + host = hosts[0] + assert host.broadcast_rpc_address == _UNREACHABLE_BROADCAST_RPC_ADDRESS + assert host.endpoint.address == _UNREACHABLE_BROADCAST_RPC_ADDRESS + return host + + def test_disabled_raises_when_broadcast_rpc_address_is_unreachable(self): + self.cluster = TestCluster( + allow_control_connection_query_fallback=ControlConnectionQueryFallback.Disabled, + connect_timeout=1, + monitor_reporting_enabled=False, + ) + + with pytest.raises(NoHostAvailable): + self.cluster.connect() + + self._assert_unreachable_broadcast_rpc_metadata() + assert self.cluster.control_connection._connection is not None + assert self.cluster.get_all_pools() == [] + + def test_fallback_executes_queries_when_broadcast_rpc_address_is_unreachable(self): + self.cluster = TestCluster( + allow_control_connection_query_fallback=ControlConnectionQueryFallback.Fallback, + connect_timeout=1, + monitor_reporting_enabled=False, + ) + + session = self.cluster.connect() + + self._assert_unreachable_broadcast_rpc_metadata() + assert session._initial_connect_futures + assert list(session.get_pools()) == [] + + row = session.execute( + "SELECT release_version, rpc_address FROM system.local WHERE key='local'").one() + assert str(row.rpc_address) == _UNREACHABLE_BROADCAST_RPC_ADDRESS + assert row.release_version + + def test_no_node_pool_fallback_executes_queries_without_creating_pools(self): + self.cluster = TestCluster( + allow_control_connection_query_fallback=ControlConnectionQueryFallback.SkipPoolCreation, + connect_timeout=1, + monitor_reporting_enabled=False, + ) + + session = self.cluster.connect() + + self._assert_unreachable_broadcast_rpc_metadata() + assert session._initial_connect_futures == set() + assert list(session.get_pools()) == [] + + row = session.execute( + "SELECT release_version, rpc_address FROM system.local WHERE key='local'").one() + assert str(row.rpc_address) == _UNREACHABLE_BROADCAST_RPC_ADDRESS + assert row.release_version diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index b6f2da5372..3d55bc1860 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -13,6 +13,7 @@ # limitations under the License. import unittest +from concurrent.futures import Future import logging import socket from types import SimpleNamespace @@ -22,9 +23,9 @@ from cassandra import ConsistencyLevel, DriverException, Timeout, Unavailable, RequestExecutionException, ReadTimeout, WriteTimeout, CoordinationFailure, ReadFailure, WriteFailure, FunctionFailure, AlreadyExists,\ InvalidRequest, Unauthorized, AuthenticationFailed, OperationTimedOut, UnsupportedOperation, RequestValidationException, ConfigurationException, ProtocolVersion -from cassandra.cluster import _Scheduler, Session, Cluster, ResultSet, SchemaAgreementScope, default_lbp_factory, \ +from cassandra.cluster import _Scheduler, Session, Cluster, ResultSet, SchemaAgreementScope, ControlConnectionQueryFallback, default_lbp_factory, \ ExecutionProfile, _ConfigMode, EXEC_PROFILE_DEFAULT -from cassandra.connection import ConnectionBusy +from cassandra.connection import ConnectionBusy, ConnectionException from cassandra.pool import Host from cassandra.policies import HostDistance, RetryPolicy, RoundRobinPolicy, DowngradingConsistencyRetryPolicy, SimpleConvictionPolicy from cassandra.query import SimpleStatement, named_tuple_factory, tuple_factory @@ -186,6 +187,52 @@ def test_port_range(self): with pytest.raises(ValueError): cluster = Cluster(contact_points=['127.0.0.1'], port=invalid_port) + def test_control_connection_query_fallback_modes(self): + assert Cluster().allow_control_connection_query_fallback is ControlConnectionQueryFallback.Disabled + with pytest.raises(TypeError): + Cluster(allow_control_connection_query_fallback=False) + with pytest.raises(TypeError): + Cluster(allow_control_connection_query_fallback=True) + assert ( + Cluster(allow_control_connection_query_fallback=ControlConnectionQueryFallback.Fallback) + .allow_control_connection_query_fallback + is ControlConnectionQueryFallback.Fallback + ) + assert Cluster( + allow_control_connection_query_fallback=ControlConnectionQueryFallback.SkipPoolCreation + ).allow_control_connection_query_fallback is ControlConnectionQueryFallback.SkipPoolCreation + + def test_control_connection_query_fallback_no_node_pool_mode_skips_pool_creation(self): + cluster = Cluster( + allow_control_connection_query_fallback=ControlConnectionQueryFallback.SkipPoolCreation, + monitor_reporting_enabled=False, + ) + host = Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4()) + + with patch.object(Session, "add_or_renew_pool") as mocked_add_or_renew_pool: + session = Session(cluster, [host]) + + mocked_add_or_renew_pool.assert_not_called() + assert session._initial_connect_futures == set() + assert session._pools == {} + assert session.update_created_pools() == set() + + def test_control_connection_query_fallback_fallback_tolerates_empty_initial_pools(self): + cluster = Cluster( + allow_control_connection_query_fallback=ControlConnectionQueryFallback.Fallback, + monitor_reporting_enabled=False, + ) + host = Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4()) + future = Future() + future.set_result(False) + + with patch.object(Session, "add_or_renew_pool", return_value=future) as mocked_add_or_renew_pool: + session = Session(cluster, [host]) + + mocked_add_or_renew_pool.assert_called_once_with(host, is_host_addition=False) + assert session._initial_connect_futures == {future} + assert session._pools == {} + def test_compression_autodisabled_without_libraries(self): with patch.dict('cassandra.cluster.locally_supported_compressions', {}, clear=True): with patch('cassandra.cluster.log') as patched_logger: @@ -551,6 +598,32 @@ def test_wait_for_schema_agreement_rejects_unknown_scope(self, *_): with pytest.raises(ValueError): session.wait_for_schema_agreement(wait_time=1, scope='planet') + @mock_session_pools + def test_set_keyspace_for_all_pools_reports_all_errors(self, *_): + cluster = Cluster() + session = Session( + cluster, + [Host("127.0.0.1", SimpleConvictionPolicy, host_id=uuid.uuid4())], + ) + + pool1 = Mock(host='host1') + pool2 = Mock(host='host2') + keyspace_error = ConnectionException("boom") + + pool1._set_keyspace_for_all_conns.side_effect = ( + lambda keyspace, callback: callback(pool1, [keyspace_error]) + ) + pool2._set_keyspace_for_all_conns.side_effect = ( + lambda keyspace, callback: callback(pool2, []) + ) + session._pools = {'host1': pool1, 'host2': pool2} + + callback = Mock() + session._set_keyspace_for_all_pools('ks', callback) + + callback.assert_called_once() + assert callback.call_args.args[0] == {'host1': [keyspace_error]} + class ProtocolVersionTests(unittest.TestCase): def test_protocol_downgrade_test(self): diff --git a/tests/unit/test_response_future.py b/tests/unit/test_response_future.py index dd7fa75045..9673b0d634 100644 --- a/tests/unit/test_response_future.py +++ b/tests/unit/test_response_future.py @@ -19,7 +19,7 @@ from unittest.mock import Mock, MagicMock, ANY from cassandra import ConsistencyLevel, Unavailable, SchemaTargetType, SchemaChangeType, OperationTimedOut -from cassandra.cluster import Session, ResponseFuture, NoHostAvailable, ProtocolVersion +from cassandra.cluster import Session, ResponseFuture, NoHostAvailable, ProtocolVersion, ControlConnectionQueryFallback from cassandra.connection import Connection, ConnectionException from cassandra.protocol import (ReadTimeoutErrorMessage, WriteTimeoutErrorMessage, UnavailableErrorMessage, ResultMessage, QueryMessage, @@ -41,6 +41,7 @@ def make_basic_session(self): s = Mock(spec=Session) s.row_factory = lambda col_names, rows: [(col_names, rows)] s.cluster.control_connection._tablets_routing_v1 = False + s.cluster.allow_control_connection_query_fallback = ControlConnectionQueryFallback.Disabled return s def make_pool(self): @@ -49,6 +50,22 @@ def make_pool(self): pool.borrow_connection.return_value = [Mock(), Mock()] return pool + def make_control_connection(self): + connection = Mock(spec=Connection) + connection.endpoint = 'control-host' + connection.lock = RLock() + connection.in_flight = 0 + connection.max_request_id = 100 + connection.request_ids = deque() + connection._requests = {} + connection.orphaned_request_ids = set() + connection.orphaned_threshold = 75 + connection.orphaned_threshold_reached = False + connection.is_control_connection = True + connection.get_request_id.return_value = 7 + connection.send_msg.return_value = 128 + return connection + def make_session(self): session = self.make_basic_session() session.cluster._default_load_balancing_policy.make_query_plan.return_value = ['ip1', 'ip2'] @@ -391,6 +408,268 @@ def test_all_pools_shutdown(self): with pytest.raises(NoHostAvailable): rf.result() + def test_control_connection_fallback_disabled_by_default(self): + session = self.make_basic_session() + session.cluster._default_load_balancing_policy.make_query_plan.return_value = ['ip1'] + session._pools = {} + connection = self.make_control_connection() + session.cluster.control_connection._connection = connection + + rf = self.make_response_future(session) + rf.send_request() + + connection.send_msg.assert_not_called() + with pytest.raises(NoHostAvailable): + rf.result() + + def test_control_connection_fallback_updates_connection_keyspace(self): + session = self.make_basic_session() + session.cluster.allow_control_connection_query_fallback = ControlConnectionQueryFallback.Fallback + session.cluster._default_load_balancing_policy.make_query_plan.return_value = ['ip1'] + session._pools = {} + + def set_keyspace_for_all_pools(keyspace, callback): + session.keyspace = keyspace + callback({}) + + session._set_keyspace_for_all_pools.side_effect = set_keyspace_for_all_pools + + connection = self.make_control_connection() + connection.keyspace = 'oldks' + session.cluster.control_connection._connection = connection + control_host = Mock(endpoint=connection.endpoint) + session.cluster.get_control_connection_host.return_value = control_host + + rf = self.make_response_future(session) + assert rf.send_request() + + result = Mock(spec=ResultMessage, kind=RESULT_KIND_SET_KEYSPACE, new_keyspace='newks') + connection.send_msg.call_args[1]['cb'](result) + + assert connection.keyspace == 'newks' + assert session.keyspace == 'newks' + assert rf.result().current_rows == [] + + def test_control_connection_fallback_when_no_usable_pools(self): + session = self.make_basic_session() + session.cluster.allow_control_connection_query_fallback = ControlConnectionQueryFallback.SkipPoolCreation + session.cluster._default_load_balancing_policy.make_query_plan.return_value = ['ip1', 'ip2'] + session._pools = {} + connection = self.make_control_connection() + session.cluster.control_connection._connection = connection + control_host = Mock(endpoint=connection.endpoint) + session.cluster.get_control_connection_host.return_value = control_host + + rf = self.make_response_future(session) + assert rf.send_request() + + connection.send_msg.assert_called_once_with( + rf.message, 7, cb=ANY, encoder=ProtocolHandler.encode_message, + decoder=ProtocolHandler.decode_message, result_metadata=[]) + assert connection.in_flight == 1 + assert rf.attempted_hosts == [control_host] + + cb = connection.send_msg.call_args[1]['cb'] + expected_result = (object(), object()) + cb(self.make_mock_response(expected_result[0], expected_result[1])) + + assert connection.in_flight == 0 + assert rf.result()[0] == expected_result + + def test_control_connection_fallback_retries_after_server_error(self): + session = self.make_basic_session() + session.cluster.allow_control_connection_query_fallback = ControlConnectionQueryFallback.Fallback + session.cluster._default_load_balancing_policy.make_query_plan.return_value = ['ip1'] + session._pools = {} + connection = self.make_control_connection() + connection.get_request_id.side_effect = [7, 8] + session.cluster.control_connection._connection = connection + control_host = Mock(endpoint=connection.endpoint) + session.cluster.get_control_connection_host.return_value = control_host + + rf = self.make_response_future(session) + assert rf.send_request() + + first_response = Mock(spec=ServerError, info={}) + first_response.summary = 'boom' + first_response.to_exception.return_value = first_response + connection.send_msg.call_args[1]['cb'](first_response) + + rf.session.cluster.scheduler.schedule.assert_called_once_with(ANY, rf._retry_task, False, control_host) + + # The retry decision must come from the future state, not the live connection reference. + rf._connection = Mock(is_control_connection=False) + + rf._retry_task(False, control_host) + + assert connection.send_msg.call_count == 2 + assert connection.send_msg.call_args_list[1][0][0] is rf.message + assert connection.send_msg.call_args_list[1][0][1] == 8 + assert rf.attempted_hosts == [control_host, control_host] + + expected_result = (object(), object()) + connection.send_msg.call_args_list[1][1]['cb']( + self.make_mock_response(expected_result[0], expected_result[1])) + + assert connection.in_flight == 0 + assert rf.result()[0] == expected_result + + def test_control_connection_fallback_fetches_next_page(self): + session = self.make_basic_session() + session.cluster.allow_control_connection_query_fallback = ControlConnectionQueryFallback.Fallback + session.cluster._default_load_balancing_policy.make_query_plan.return_value = ['ip1'] + session._pools = {} + connection = self.make_control_connection() + connection.get_request_id.side_effect = [7, 8] + session.cluster.control_connection._connection = connection + control_host = Mock(endpoint=connection.endpoint) + session.cluster.get_control_connection_host.return_value = control_host + + rf = self.make_response_future(session) + assert rf.send_request() + + first_response = self.make_mock_response(['col'], [(1,)]) + first_response.paging_state = b'next-page' + connection.send_msg.call_args[1]['cb'](first_response) + + assert rf.result().current_rows == [(['col'], [(1,)])] + assert rf.has_more_pages + + rf.start_fetching_next_page() + + assert connection.send_msg.call_count == 2 + assert connection.send_msg.call_args_list[1][0][0] is rf.message + assert connection.send_msg.call_args_list[1][0][1] == 8 + assert rf.message.paging_state == b'next-page' + + second_response = self.make_mock_response(['col'], [(2,)]) + connection.send_msg.call_args_list[1][1]['cb'](second_response) + + assert connection.in_flight == 0 + assert rf.result().current_rows == [(['col'], [(2,)])] + + def test_control_connection_fallback_reprepares_prepared_statement(self): + session = self.make_basic_session() + session.cluster.allow_control_connection_query_fallback = ControlConnectionQueryFallback.Fallback + session.cluster.protocol_version = ProtocolVersion.V4 + session.cluster._default_load_balancing_policy.make_query_plan.return_value = ['ip1'] + session._pools = {} + session.submit.side_effect = lambda fn, *args, **kwargs: fn(*args, **kwargs) + + query_id = b'a' * 16 + prepared_statement = Mock( + query_id=query_id, + query_string="SELECT * FROM foobar", + keyspace="FooKeyspace", + result_metadata=[], + result_metadata_id=None) + session.cluster._prepared_statements = {query_id: prepared_statement} + + connection = self.make_control_connection() + connection.keyspace = "FooKeyspace" + connection.get_request_id.side_effect = [7, 8, 9] + session.cluster.control_connection._connection = connection + control_host = Mock(endpoint=connection.endpoint) + session.cluster.get_control_connection_host.return_value = control_host + + rf = self.make_response_future(session) + rf.prepared_statement = prepared_statement + assert rf.send_request() + + missing = Mock(spec=PreparedQueryNotFound, info=query_id) + connection.send_msg.call_args_list[0][1]['cb'](missing) + + assert connection.send_msg.call_count == 2 + prepare_message = connection.send_msg.call_args_list[1][0][0] + assert isinstance(prepare_message, PrepareMessage) + assert prepare_message.query == "SELECT * FROM foobar" + assert connection.send_msg.call_args_list[1][0][1] == 8 + + prepared_response = Mock( + spec=ResultMessage, + kind=RESULT_KIND_PREPARED, + query_id=query_id, + column_metadata=[], + result_metadata_id=None) + connection.send_msg.call_args_list[1][1]['cb'](prepared_response) + + assert connection.send_msg.call_count == 3 + assert connection.send_msg.call_args_list[2][0][0] is rf.message + assert connection.send_msg.call_args_list[2][0][1] == 9 + + expected_result = (['col'], [(1,)]) + connection.send_msg.call_args_list[2][1]['cb']( + self.make_mock_response(expected_result[0], expected_result[1])) + + assert connection.in_flight == 0 + assert rf.result()[0] == expected_result + + def test_control_connection_fallback_not_used_when_pool_can_serve(self): + session = self.make_basic_session() + session.cluster.allow_control_connection_query_fallback = ControlConnectionQueryFallback.Fallback + session.cluster._default_load_balancing_policy.make_query_plan.return_value = ['ip1'] + pool = Mock(is_shutdown=False) + pool.borrow_connection.side_effect = NoConnectionsAvailable() + session._pools = {'ip1': pool} + connection = self.make_control_connection() + session.cluster.control_connection._connection = connection + + rf = self.make_response_future(session) + rf.send_request() + + connection.send_msg.assert_not_called() + with pytest.raises(NoHostAvailable): + rf.result() + + def test_control_connection_fallback_orphans_stream_on_timeout(self): + session = self.make_basic_session() + session.cluster.allow_control_connection_query_fallback = ControlConnectionQueryFallback.Fallback + session.cluster._default_load_balancing_policy.make_query_plan.return_value = ['ip1'] + session._pools = {} + connection = self.make_control_connection() + session.cluster.control_connection._connection = connection + + def send_msg(message, request_id, cb, **kwargs): + connection._requests[request_id] = (cb, kwargs.get('decoder'), kwargs.get('result_metadata')) + return 128 + + connection.send_msg.side_effect = send_msg + + rf = self.make_response_future(session) + rf.send_request() + rf._on_timeout() + + assert 7 in connection.orphaned_request_ids + assert connection.in_flight == 1 + with pytest.raises(OperationTimedOut): + rf.result() + + def test_control_connection_fallback_timeout_without_metadata_host_uses_connection_endpoint(self): + session = self.make_basic_session() + session.cluster.allow_control_connection_query_fallback = ControlConnectionQueryFallback.Fallback + session.cluster._default_load_balancing_policy.make_query_plan.return_value = [] + session._pools = {} + session.cluster.get_control_connection_host.return_value = None + connection = self.make_control_connection() + session.cluster.control_connection._connection = connection + + def send_msg(message, request_id, cb, **kwargs): + connection._requests[request_id] = (cb, kwargs.get('decoder'), kwargs.get('result_metadata')) + return 128 + + connection.send_msg.side_effect = send_msg + + rf = self.make_response_future(session) + assert rf.send_request() + rf._on_timeout() + + with pytest.raises(OperationTimedOut) as exc_info: + rf.result() + + assert exc_info.value.errors == { + 'control-host': 'Client request timeout. See Session.execute[_async](timeout)' + } + def test_first_pool_shutdown(self): session = self.make_basic_session() session.cluster._default_load_balancing_policy.make_query_plan.return_value = ['ip1', 'ip2'] From 442f1edd7412049d438b021b1d83e7a5e2ce6f17 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 10 May 2026 00:55:32 -0400 Subject: [PATCH 286/298] Release 3.29.10: changelog, version and documentation --- CHANGELOG.rst | 25 +++++++++++++++++++++++++ cassandra/__init__.py | 2 +- docs/conf.py | 4 ++-- docs/installation.rst | 4 ++-- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3ae00a7ee8..39a8aca069 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,28 @@ +3.29.10 +======= +May 10, 2026 + +Features +-------- +* Fast-path ``lookup_casstype()`` for simple type names +* Add ``Session.wait_for_schema_agreement`` + +Bug Fixes +--------- +* Fix CQL injection in ``Connection.set_keyspace_blocking`` and ``Connection.set_keyspace_async`` +* Fix libev shutdown crashes by correcting atexit registration +* Handle ``None`` ``control_connection_timeout`` in ``wait_for_schema_agreement`` +* Clean up failed heartbeat sends +* Fix ``ExponentialBackoffRetryPolicy.__init__`` super() call +* Correct ``clustering_key`` to ``clustering`` in column kind filter +* Fix inverted cooldown check in ``_get_shard_aware_endpoint`` + +Others +------ +* Deprecate ``ControlConnection.wait_for_schema_agreement`` +* Add timeout and in-flight observability to ``OperationTimedOut`` +* Drop per-query connection log + 3.29.9 ====== March 18, 2026 diff --git a/cassandra/__init__.py b/cassandra/__init__.py index 46de7daaf0..1286f20e9b 100644 --- a/cassandra/__init__.py +++ b/cassandra/__init__.py @@ -23,7 +23,7 @@ def emit(self, record): logging.getLogger('cassandra').addHandler(NullHandler()) -__version_info__ = (3, 29, 9) +__version_info__ = (3, 29, 10) __version__ = '.'.join(map(str, __version_info__)) diff --git a/docs/conf.py b/docs/conf.py index 87a38c6add..34ef31ccae 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,11 +29,11 @@ '3.29.6-scylla', '3.29.7-scylla', '3.29.8-scylla', - '3.29.9-scylla', + '3.29.10-scylla', ] BRANCHES = ['master'] # Set the latest version. -LATEST_VERSION = '3.29.9-scylla' +LATEST_VERSION = '3.29.10-scylla' # Set which versions are not released yet. UNSTABLE_VERSIONS = ['master'] # Set which versions are deprecated diff --git a/docs/installation.rst b/docs/installation.rst index fbb9ac4043..6a4b38ea80 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -26,7 +26,7 @@ To check if the installation was successful, you can run:: python -c 'import cassandra; print(cassandra.__version__)' -It should print something like "3.29.9". +It should print something like "3.29.10". (*Optional*) Compression Support -------------------------------- @@ -190,7 +190,7 @@ through `Homebrew `_. For example, on Mac OS X:: $ brew install libev -The libev extension can now be built for Windows as of Python driver version 3.29.9. You can +The libev extension can now be built for Windows as of Python driver version 3.29.10. You can install libev using any Windows package manager. For example, to install using `vcpkg `_: $ vcpkg install libev From 69bb8efc9a3742de4de3b4fac61086b5b310db08 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Wed, 22 Apr 2026 10:43:51 +0200 Subject: [PATCH 287/298] tests: replace SimpleStrategy with NetworkTopologyStrategy Replace SimpleStrategy with NetworkTopologyStrategy across integration tests to align with ScyllaDB's tablet-based replication defaults. In the tablets test module, skip default keyspace creation (set_keyspace=False) to avoid RF=3 keyspaces that block node decommission when all nodes already hold replicas. --- tests/integration/__init__.py | 8 ++--- .../column_encryption/test_policies.py | 2 +- .../standard/test_client_routes.py | 2 +- tests/integration/standard/test_cluster.py | 4 +-- ..._concurrent_schema_change_and_node_kill.py | 2 +- .../standard/test_control_connection.py | 2 +- .../standard/test_custom_protocol_handler.py | 11 ++++--- .../standard/test_cython_protocol_handlers.py | 4 +-- tests/integration/standard/test_metadata.py | 32 ++++++++++++------- tests/integration/standard/test_policies.py | 2 +- .../standard/test_prepared_statements.py | 4 +-- tests/integration/standard/test_query.py | 4 +-- .../standard/test_rate_limit_exceeded.py | 2 +- .../integration/standard/test_shard_aware.py | 4 ++- tests/integration/standard/test_tablets.py | 2 +- tests/integration/standard/test_udts.py | 10 +++--- .../integration/standard/test_use_keyspace.py | 2 +- 17 files changed, 54 insertions(+), 43 deletions(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index 6a809bded4..7d4d47c9a7 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -651,17 +651,17 @@ def setup_keyspace(ipformat=None, protocol_version=None, port=9042): ddl = ''' CREATE KEYSPACE test3rf - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'}''' + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '3'}''' execute_with_long_wait_retry(session, ddl) ddl = ''' CREATE KEYSPACE test2rf - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '2'}''' + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '2'}''' execute_with_long_wait_retry(session, ddl) ddl = ''' CREATE KEYSPACE test1rf - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}''' + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'}''' execute_with_long_wait_retry(session, ddl) ddl_3f = ''' @@ -774,7 +774,7 @@ def drop_keyspace(cls): @classmethod def create_keyspace(cls, rf): - ddl = "CREATE KEYSPACE {0} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': '{1}'}}".format(cls.ks_name, rf) + ddl = "CREATE KEYSPACE {0} WITH replication = {{'class': 'NetworkTopologyStrategy', 'replication_factor': '{1}'}}".format(cls.ks_name, rf) execute_with_long_wait_retry(cls.session, ddl) @classmethod diff --git a/tests/integration/standard/column_encryption/test_policies.py b/tests/integration/standard/column_encryption/test_policies.py index 9a1d186895..4b12fa135a 100644 --- a/tests/integration/standard/column_encryption/test_policies.py +++ b/tests/integration/standard/column_encryption/test_policies.py @@ -30,7 +30,7 @@ class ColumnEncryptionPolicyTest(unittest.TestCase): def _recreate_keyspace(self, session): session.execute("drop keyspace if exists foo") - session.execute("CREATE KEYSPACE foo WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}") + session.execute("CREATE KEYSPACE foo WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'}") session.execute("CREATE TABLE foo.bar(encrypted blob, unencrypted int, primary key(unencrypted))") def _create_policy(self, key, iv = None): diff --git a/tests/integration/standard/test_client_routes.py b/tests/integration/standard/test_client_routes.py index 5a20421276..290d1741f7 100644 --- a/tests/integration/standard/test_client_routes.py +++ b/tests/integration/standard/test_client_routes.py @@ -741,7 +741,7 @@ def test_queries_succeed_through_proxy(self): session = cluster.connect() session.execute( "CREATE KEYSPACE IF NOT EXISTS test_cr_ks " - "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}" + "WITH replication = {'class':'NetworkTopologyStrategy', 'replication_factor': 3}" ) session.execute( "CREATE TABLE IF NOT EXISTS test_cr_ks.t (k int PRIMARY KEY, v text)" diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 08b823d716..15e525f43c 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -180,7 +180,7 @@ def test_basic(self): result = execute_until_pass(session, """ CREATE KEYSPACE clustertests - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'} """) assert not result @@ -1506,7 +1506,7 @@ def test_prepare_on_ignored_hosts(self): hosts = cluster.metadata.all_hosts() session.execute("CREATE KEYSPACE clustertests " "WITH replication = " - "{'class': 'SimpleStrategy', 'replication_factor': '1'}") + "{'class': 'NetworkTopologyStrategy', 'replication_factor': '1'}") session.execute("CREATE TABLE clustertests.tab (a text, PRIMARY KEY (a))") # assign to an unused variable so cluster._prepared_statements retains # reference diff --git a/tests/integration/standard/test_concurrent_schema_change_and_node_kill.py b/tests/integration/standard/test_concurrent_schema_change_and_node_kill.py index 910dcaa9fe..9a9a3d325f 100644 --- a/tests/integration/standard/test_concurrent_schema_change_and_node_kill.py +++ b/tests/integration/standard/test_concurrent_schema_change_and_node_kill.py @@ -27,7 +27,7 @@ def test_schema_change_after_node_kill(self): "DROP KEYSPACE IF EXISTS ks_deadlock;") self.session.execute( "CREATE KEYSPACE IF NOT EXISTS ks_deadlock " - "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '2' };") + "WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '2' };") self.session.set_keyspace('ks_deadlock') self.session.execute("CREATE TABLE IF NOT EXISTS some_table(k int, c int, v int, PRIMARY KEY (k, v));") self.session.execute("INSERT INTO some_table (k, c, v) VALUES (1, 2, 3);") diff --git a/tests/integration/standard/test_control_connection.py b/tests/integration/standard/test_control_connection.py index c4463e17fd..f0c41dde14 100644 --- a/tests/integration/standard/test_control_connection.py +++ b/tests/integration/standard/test_control_connection.py @@ -68,7 +68,7 @@ def test_drop_keyspace(self): self.session = self.cluster.connect() self.session.execute(""" CREATE KEYSPACE keyspacetodrop - WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor': '1' } + WITH replication = { 'class' : 'NetworkTopologyStrategy', 'replication_factor': '1' } """) self.session.set_keyspace("keyspacetodrop") self.session.execute("CREATE TYPE user (age int, name text)") diff --git a/tests/integration/standard/test_custom_protocol_handler.py b/tests/integration/standard/test_custom_protocol_handler.py index e123f2050e..e7d336014f 100644 --- a/tests/integration/standard/test_custom_protocol_handler.py +++ b/tests/integration/standard/test_custom_protocol_handler.py @@ -42,8 +42,9 @@ class CustomProtocolHandlerTest(unittest.TestCase): def setUpClass(cls): cls.cluster = TestCluster() cls.session = cls.cluster.connect() - cls.session.execute("CREATE KEYSPACE custserdes WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor': '1'}") + cls.session.execute("CREATE KEYSPACE custserdes WITH replication = { 'class' : 'NetworkTopologyStrategy', 'replication_factor': '1'}") cls.session.set_keyspace("custserdes") + cls.session.execute("CREATE TABLE IF NOT EXISTS custserdes.test (k int PRIMARY KEY, v int)") @classmethod def tearDownClass(cls): @@ -165,7 +166,7 @@ def test_protocol_divergence_v5_fail_by_flag_uses_int(self): int_flag=False) def _send_query_message(self, session, timeout, **kwargs): - query = "SELECT * FROM test3rf.test" + query = "SELECT * FROM custserdes.test" message = QueryMessage(query=query, **kwargs) future = ResponseFuture(session, message, query=None, timeout=timeout) future.send_request() @@ -175,8 +176,8 @@ def _protocol_divergence_fail_by_flag_uses_int(self, version, uses_int_query_fla cluster = TestCluster(protocol_version=version, allow_beta_protocol_version=beta) session = cluster.connect() - query_one = SimpleStatement("INSERT INTO test3rf.test (k, v) VALUES (1, 1)") - query_two = SimpleStatement("INSERT INTO test3rf.test (k, v) VALUES (2, 2)") + query_one = SimpleStatement("INSERT INTO custserdes.test (k, v) VALUES (1, 1)") + query_two = SimpleStatement("INSERT INTO custserdes.test (k, v) VALUES (2, 2)") execute_with_long_wait_retry(session, query_one) execute_with_long_wait_retry(session, query_two) @@ -190,7 +191,7 @@ def _protocol_divergence_fail_by_flag_uses_int(self, version, uses_int_query_fla # This means the flag are not handled as they are meant by the server if uses_int=False assert response.has_more_pages == uses_int_query_flag - execute_with_long_wait_retry(session, SimpleStatement("TRUNCATE test3rf.test")) + execute_with_long_wait_retry(session, SimpleStatement("TRUNCATE custserdes.test")) cluster.shutdown() diff --git a/tests/integration/standard/test_cython_protocol_handlers.py b/tests/integration/standard/test_cython_protocol_handlers.py index 9c94b2ac77..49a13ac23a 100644 --- a/tests/integration/standard/test_cython_protocol_handlers.py +++ b/tests/integration/standard/test_cython_protocol_handlers.py @@ -34,7 +34,7 @@ def setUpClass(cls): cls.cluster = TestCluster() cls.session = cls.cluster.connect() cls.session.execute("CREATE KEYSPACE testspace WITH replication = " - "{ 'class' : 'SimpleStrategy', 'replication_factor': '1'}") + "{ 'class' : 'NetworkTopologyStrategy', 'replication_factor': '1'}") cls.session.set_keyspace("testspace") cls.colnames = create_table_with_all_types("test_table", cls.session, cls.N_ITEMS) @@ -225,7 +225,7 @@ def setUpClass(cls): cls.cluster = TestCluster() cls.session = cls.cluster.connect() cls.session.execute("CREATE KEYSPACE IF NOT EXISTS test_wide_table WITH replication = " - "{ 'class' : 'SimpleStrategy', 'replication_factor': '1'}") + "{ 'class' : 'NetworkTopologyStrategy', 'replication_factor': '1'}") cls.session.set_keyspace("test_wide_table") # Create a wide table with many int columns diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 6e64401a75..d34b81d44d 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -230,8 +230,8 @@ def test_basic_table_meta_properties(self): assert ksmeta.name == self.keyspace_name assert ksmeta.durable_writes - assert ksmeta.replication_strategy.name == 'SimpleStrategy' - assert ksmeta.replication_strategy.replication_factor == 1 + assert ksmeta.replication_strategy.name == 'NetworkTopologyStrategy' + assert ksmeta.replication_strategy.dc_replication_factors["dc1"] == 1 assert self.function_table_name in ksmeta.tables tablemeta = ksmeta.tables[self.function_table_name] @@ -448,6 +448,8 @@ def test_dense_compact_storage(self): tablemeta = self.get_table_metadata() self.check_create_statement(tablemeta, create_statement) + @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Counters are not yet supported with tablets', + oss_scylla_version="7.0", ent_scylla_version="2026.1") def test_counter(self): create_statement = ( "CREATE TABLE {keyspace}.{table} (" @@ -601,7 +603,7 @@ def test_refresh_schema_metadata(self): assert "new_keyspace" not in cluster2.metadata.keyspaces # Cluster metadata modification - self.session.execute("CREATE KEYSPACE new_keyspace WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}") + self.session.execute("CREATE KEYSPACE new_keyspace WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'}") assert "new_keyspace" not in cluster2.metadata.keyspaces cluster2.refresh_schema_metadata() @@ -722,6 +724,8 @@ def test_refresh_table_metadata(self): cluster2.shutdown() @greaterthanorequalcass30 + @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', + oss_scylla_version="7.0", ent_scylla_version="2026.1") def test_refresh_metadata_for_mv(self): """ test for synchronously refreshing materialized view metadata @@ -931,6 +935,8 @@ def test_refresh_user_aggregate_metadata(self): @greaterthanorequalcass30 @requires_collection_indexes + @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', + oss_scylla_version="7.0", ent_scylla_version="2026.1") def test_multiple_indices(self): """ test multiple indices on the same column. @@ -964,6 +970,8 @@ def test_multiple_indices(self): assert index_2.keyspace_name == "schemametadatatests" @greaterthanorequalcass30 + @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', + oss_scylla_version="7.0", ent_scylla_version="2026.1") def test_table_extensions(self): s = self.session ks = self.keyspace_name @@ -1077,7 +1085,7 @@ def test_metadata_pagination_keyspaces(self): for ks in keyspaces: self.session.execute( - f"CREATE KEYSPACE IF NOT EXISTS {ks} WITH REPLICATION = {{ 'class' : 'SimpleStrategy', 'replication_factor' : 3 }}" + f"CREATE KEYSPACE IF NOT EXISTS {ks} WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', 'replication_factor' : 3 }}" ) self.cluster.schema_metadata_page_size = 2000 @@ -1138,7 +1146,7 @@ def test_export_keyspace_schema_udts(self): session.execute(""" CREATE KEYSPACE export_udts - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'} AND durable_writes = true; """) session.execute(""" @@ -1162,7 +1170,7 @@ def test_export_keyspace_schema_udts(self): addresses map>) """) - expected_prefix = """CREATE KEYSPACE export_udts WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true; + expected_prefix = """CREATE KEYSPACE export_udts WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'} AND durable_writes = true; CREATE TYPE export_udts.street ( street_number int, @@ -1212,7 +1220,7 @@ def test_case_sensitivity(self): session.execute("DROP KEYSPACE IF EXISTS {0}".format(ksname)) session.execute(""" CREATE KEYSPACE "%s" - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'} """ % (ksname,)) session.execute(""" CREATE TABLE "%s"."%s" ( @@ -1256,7 +1264,7 @@ def test_already_exists_exceptions(self): ddl = ''' CREATE KEYSPACE %s - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'}''' + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '3'}''' with pytest.raises(AlreadyExists): session.execute(ddl % ksname) @@ -1387,7 +1395,7 @@ def setUp(self): self.session = self.cluster.connect() name = self._testMethodName.lower() crt_ks = ''' - CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1} AND durable_writes = true''' % name + CREATE KEYSPACE %s WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1} AND durable_writes = true''' % name self.session.execute(crt_ks) def tearDown(self): @@ -1437,7 +1445,7 @@ def setup_class(cls): cls.session.execute( """ CREATE KEYSPACE %s - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}; + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'}; """ % cls.keyspace_name) cls.session.set_keyspace(cls.keyspace_name) except Exception: @@ -1540,7 +1548,7 @@ def setup_class(cls): cls.cluster = TestCluster() cls.keyspace_name = cls.__name__.lower() cls.session = cls.cluster.connect() - cls.session.execute("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}" % cls.keyspace_name) + cls.session.execute("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}" % cls.keyspace_name) cls.session.set_keyspace(cls.keyspace_name) cls.keyspace_function_meta = cls.cluster.metadata.keyspaces[cls.keyspace_name].functions cls.keyspace_aggregate_meta = cls.cluster.metadata.keyspaces[cls.keyspace_name].aggregates @@ -2007,7 +2015,7 @@ def setup_class(cls): cls.cluster = TestCluster() cls.keyspace_name = cls.__name__.lower() cls.session = cls.cluster.connect() - cls.session.execute("CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}" % cls.keyspace_name) + cls.session.execute("CREATE KEYSPACE %s WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'}" % cls.keyspace_name) cls.session.set_keyspace(cls.keyspace_name) connection = cls.cluster.control_connection._connection diff --git a/tests/integration/standard/test_policies.py b/tests/integration/standard/test_policies.py index 2de12f7b7f..50b431e3c9 100644 --- a/tests/integration/standard/test_policies.py +++ b/tests/integration/standard/test_policies.py @@ -104,5 +104,5 @@ def test_exponential_retries(self): self.session.execute( """ CREATE KEYSPACE preparedtests - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'} """) diff --git a/tests/integration/standard/test_prepared_statements.py b/tests/integration/standard/test_prepared_statements.py index 3f63b881ef..37f93c94c6 100644 --- a/tests/integration/standard/test_prepared_statements.py +++ b/tests/integration/standard/test_prepared_statements.py @@ -62,7 +62,7 @@ def test_basic(self): self.session.execute( """ CREATE KEYSPACE preparedtests - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'} """) self.session.set_keyspace("preparedtests") @@ -437,7 +437,7 @@ def test_fail_if_different_query_id_on_reprepare(self): keyspace = "test_fail_if_different_query_id_on_reprepare" self.session.execute( "CREATE KEYSPACE IF NOT EXISTS {} WITH replication = " - "{{'class': 'SimpleStrategy', 'replication_factor': 1}}".format(keyspace) + "{{'class': 'NetworkTopologyStrategy', 'replication_factor': 1}}".format(keyspace) ) self.session.execute("CREATE TABLE IF NOT EXISTS {}.foo(k int PRIMARY KEY)".format(keyspace)) prepared = self.session.prepare("SELECT * FROM {}.foo WHERE k=?".format(keyspace)) diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index f9d3dc26bc..91ad4fa559 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -1359,12 +1359,12 @@ def setUpClass(cls): cls.table_name = "table_query_keyspace_tests" ddl = """CREATE KEYSPACE {0} WITH replication = - {{'class': 'SimpleStrategy', + {{'class': 'NetworkTopologyStrategy', 'replication_factor': '{1}'}}""".format(cls.ks_name, 1) cls.session.execute(ddl) ddl = """CREATE KEYSPACE {0} WITH replication = - {{'class': 'SimpleStrategy', + {{'class': 'NetworkTopologyStrategy', 'replication_factor': '{1}'}}""".format(cls.alternative_ks, 1) cls.session.execute(ddl) diff --git a/tests/integration/standard/test_rate_limit_exceeded.py b/tests/integration/standard/test_rate_limit_exceeded.py index ea7dfc7d61..5a7fc5dc74 100644 --- a/tests/integration/standard/test_rate_limit_exceeded.py +++ b/tests/integration/standard/test_rate_limit_exceeded.py @@ -33,7 +33,7 @@ def test_rate_limit_exceeded(self): self.session.execute( """ CREATE KEYSPACE IF NOT EXISTS ratetests - WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1} + WITH REPLICATION = {'class' : 'NetworkTopologyStrategy', 'replication_factor' : 1} """) self.session.execute("USE ratetests") diff --git a/tests/integration/standard/test_shard_aware.py b/tests/integration/standard/test_shard_aware.py index d1f3e27abd..4a6c7887d8 100644 --- a/tests/integration/standard/test_shard_aware.py +++ b/tests/integration/standard/test_shard_aware.py @@ -89,7 +89,7 @@ def create_ks_and_cf(self): self.session.execute( """ CREATE KEYSPACE preparedtests - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'} + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '3'} AND tablets = {'enabled': false} """) self.session.execute("USE preparedtests") @@ -174,6 +174,8 @@ def test_all_tracing_coming_one_shard(self): using the traces to validate that all the action been executed on the the same shard. this test is using prepared SELECT statements for this validation + + Requires tablets to be disabled to ensure shard consistency. """ self.create_ks_and_cf() diff --git a/tests/integration/standard/test_tablets.py b/tests/integration/standard/test_tablets.py index d969140339..45e8a807ea 100644 --- a/tests/integration/standard/test_tablets.py +++ b/tests/integration/standard/test_tablets.py @@ -9,7 +9,7 @@ def setup_module(): - use_cluster('tablets', [3], start=True) + use_cluster('tablets', [3], start=True, set_keyspace=False) class TestTabletsIntegration: diff --git a/tests/integration/standard/test_udts.py b/tests/integration/standard/test_udts.py index 18f3dfb298..11888adda4 100644 --- a/tests/integration/standard/test_udts.py +++ b/tests/integration/standard/test_udts.py @@ -94,7 +94,7 @@ def test_can_insert_unprepared_registered_udts(self): # use the same UDT name in a different keyspace s.execute(""" CREATE KEYSPACE udt_test_unprepared_registered2 - WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor': '1' } + WITH replication = { 'class' : 'NetworkTopologyStrategy', 'replication_factor': '1' } """) s.set_keyspace("udt_test_unprepared_registered2") s.execute("CREATE TYPE user (state text, is_cool boolean)") @@ -124,14 +124,14 @@ def test_can_register_udt_before_connecting(self): s.execute(""" CREATE KEYSPACE udt_test_register_before_connecting - WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor': '1' } + WITH replication = { 'class' : 'NetworkTopologyStrategy', 'replication_factor': '1' } """) s.execute("CREATE TYPE udt_test_register_before_connecting.user (age int, name text)") s.execute("CREATE TABLE udt_test_register_before_connecting.mytable (a int PRIMARY KEY, b frozen)") s.execute(""" CREATE KEYSPACE udt_test_register_before_connecting2 - WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor': '1' } + WITH replication = { 'class' : 'NetworkTopologyStrategy', 'replication_factor': '1' } """) s.execute("CREATE TYPE udt_test_register_before_connecting2.user (state text, is_cool boolean)") s.execute("CREATE TABLE udt_test_register_before_connecting2.mytable (a int PRIMARY KEY, b frozen)") @@ -193,7 +193,7 @@ def test_can_insert_prepared_unregistered_udts(self): # use the same UDT name in a different keyspace s.execute(""" CREATE KEYSPACE udt_test_prepared_unregistered2 - WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor': '1' } + WITH replication = { 'class' : 'NetworkTopologyStrategy', 'replication_factor': '1' } """) s.set_keyspace("udt_test_prepared_unregistered2") s.execute("CREATE TYPE user (state text, is_cool boolean)") @@ -240,7 +240,7 @@ def test_can_insert_prepared_registered_udts(self): # use the same UDT name in a different keyspace s.execute(""" CREATE KEYSPACE udt_test_prepared_registered2 - WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor': '1' } + WITH replication = { 'class' : 'NetworkTopologyStrategy', 'replication_factor': '1' } """) s.set_keyspace("udt_test_prepared_registered2") s.execute("CREATE TYPE user (state text, is_cool boolean)") diff --git a/tests/integration/standard/test_use_keyspace.py b/tests/integration/standard/test_use_keyspace.py index 80e7cfe5f3..9eb3f5be36 100644 --- a/tests/integration/standard/test_use_keyspace.py +++ b/tests/integration/standard/test_use_keyspace.py @@ -65,7 +65,7 @@ def patched_set_keyspace_blocking(*args, **kwargs): return original_set_keyspace_blocking(*args, **kwargs) with patch.object(Connection, "set_keyspace_blocking", patched_set_keyspace_blocking): - self.session.execute("CREATE KEYSPACE test_set_keyspace WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}") + self.session.execute("CREATE KEYSPACE test_set_keyspace WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}") self.session.execute("CREATE TABLE test_set_keyspace.set_keyspace_slow_connection(pk int, PRIMARY KEY(pk))") session2 = self.cluster.connect() From 445b5afb4a08690f933beb6e4b70cdd1b1e8d8fb Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Wed, 22 Apr 2026 14:10:57 +0200 Subject: [PATCH 288/298] tests: bootstrap 3 new nodes in full node replacement test With tablets enabled, decommissioning a node from a 3-node cluster with RF=3 fails because there is no available node to receive tablet replicas. Bootstrap 3 replacement nodes instead of 2 so that each original node can be decommissioned while sufficient replicas remain. --- tests/integration/standard/test_client_routes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/standard/test_client_routes.py b/tests/integration/standard/test_client_routes.py index 290d1741f7..292eabca30 100644 --- a/tests/integration/standard/test_client_routes.py +++ b/tests/integration/standard/test_client_routes.py @@ -1154,7 +1154,7 @@ def tearDownClass(cls): def test_should_survive_full_node_replacement_through_nlb(self): """ 1. Start with 3 nodes behind the NLB - 2. Bootstrap 2 new nodes, add to NLB, update routes + 2. Bootstrap 3 new nodes, add to NLB, update routes 3. Decommission the original 3 nodes one-by-one, updating NLB/routes 4. Verify the session survives with only new nodes """ @@ -1190,7 +1190,7 @@ def test_should_survive_full_node_replacement_through_nlb(self): len(original_node_ids)) # ---- Stage 3: Bootstrap new nodes ---- - new_node_ids = [max(original_node_ids) + 1, max(original_node_ids) + 2] + new_node_ids = [max(original_node_ids) + 1, max(original_node_ids) + 2, max(original_node_ids) + 3] log.info("Stage 3: Adding nodes %s", new_node_ids) ccm_cluster = get_cluster() From 2b5dd164a3c047fe4826b2df1815562842fd1e4c Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Thu, 23 Apr 2026 08:16:51 +0200 Subject: [PATCH 289/298] tests: xfail LWT tests on Scylla versions without tablet LWT support LWT is not supported with tablets on ScyllaDB < 2025.4. Mark the affected SerialConsistencyTests and LightweightTransactionTests as xfail for those versions. --- tests/integration/standard/test_query.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 91ad4fa559..4f460459c0 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -26,7 +26,7 @@ from cassandra.policies import HostDistance, RoundRobinPolicy, WhiteListRoundRobinPolicy from tests.integration import use_singledc, PROTOCOL_VERSION, BasicSharedKeyspaceUnitTestCase, \ greaterthanprotocolv3, MockLoggingHandler, get_supported_protocol_versions, local, get_cluster, setup_keyspace, \ - USE_CASS_EXTERNAL, greaterthanorequalcass40, TestCluster, xfail_scylla + USE_CASS_EXTERNAL, greaterthanorequalcass40, TestCluster, xfail_scylla, xfail_scylla_version_lt from tests import notwindows from tests.integration import greaterthanorequalcass30, get_node from tests.util import assertListEqual, wait_until @@ -804,6 +804,9 @@ def setUp(self): def tearDown(self): self.cluster.shutdown() + @xfail_scylla_version_lt(reason='scylladb/scylladb#18068 - LWT is not yet supported with tablets', + scylla_version='2025.4', + raises=InvalidRequest) def test_conditional_update(self): self.session.execute("INSERT INTO test3rf.test (k, v) VALUES (0, 0)") statement = SimpleStatement( @@ -828,6 +831,9 @@ def test_conditional_update(self): assert result assert result.one().applied + @xfail_scylla_version_lt(reason='scylladb/scylladb#18068 - LWT is not yet supported with tablets', + scylla_version='2025.4', + raises=InvalidRequest) def test_conditional_update_with_prepared_statements(self): self.session.execute("INSERT INTO test3rf.test (k, v) VALUES (0, 0)") statement = self.session.prepare( @@ -850,6 +856,9 @@ def test_conditional_update_with_prepared_statements(self): assert result assert result.one().applied + @xfail_scylla_version_lt(reason='scylladb/scylladb#18068 - LWT is not yet supported with tablets', + scylla_version='2025.4', + raises=InvalidRequest) def test_conditional_update_with_batch_statements(self): self.session.execute("INSERT INTO test3rf.test (k, v) VALUES (0, 0)") statement = BatchStatement(serial_consistency_level=ConsistencyLevel.SERIAL) @@ -915,6 +924,9 @@ def tearDown(self): self.session.execute("DROP TABLE test3rf.lwt_clustering") self.cluster.shutdown() + @xfail_scylla_version_lt(reason='scylladb/scylladb#18068 - LWT is not yet supported with tablets', + scylla_version='2025.4', + raises=AttributeError) def test_no_connection_refused_on_timeout(self): """ Test for PYTHON-91 "Connection closed after LWT timeout" From e7cb651ad863f60c48eca1b924b1236445507eb2 Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Mon, 4 May 2026 16:04:03 +0200 Subject: [PATCH 290/298] tests: xfail tests on Scylla version without indexes tablet support Secondary indexes are not supported on base tables with tablets for Scylla versions < 2026.1. --- .../integration/cqlengine/query/test_named.py | 4 +++- tests/integration/standard/test_metadata.py | 22 ++++++++++++++----- tests/integration/standard/test_query.py | 2 ++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/tests/integration/cqlengine/query/test_named.py b/tests/integration/cqlengine/query/test_named.py index 24a6802b47..4923a8a583 100644 --- a/tests/integration/cqlengine/query/test_named.py +++ b/tests/integration/cqlengine/query/test_named.py @@ -27,7 +27,7 @@ from tests.integration.cqlengine.query.test_queryset import BaseQuerySetUsage -from tests.integration import BasicSharedKeyspaceUnitTestCase, greaterthanorequalcass30, requires_collection_indexes +from tests.integration import BasicSharedKeyspaceUnitTestCase, greaterthanorequalcass30, requires_collection_indexes, xfail_scylla_version_lt import pytest @@ -292,6 +292,8 @@ def tearDownClass(cls): super(TestNamedWithMV, cls).tearDownClass() @greaterthanorequalcass30 + @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Materialized views and secondary indexes are not supported on base tables with tablets.', + scylla_version='2026.1') @execute_count(5) def test_named_table_with_mv(self): """ diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index d34b81d44d..84ec6c9ea5 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -449,7 +449,7 @@ def test_dense_compact_storage(self): self.check_create_statement(tablemeta, create_statement) @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Counters are not yet supported with tablets', - oss_scylla_version="7.0", ent_scylla_version="2026.1") + scylla_version="2026.1") def test_counter(self): create_statement = ( "CREATE TABLE {keyspace}.{table} (" @@ -725,7 +725,7 @@ def test_refresh_table_metadata(self): @greaterthanorequalcass30 @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', - oss_scylla_version="7.0", ent_scylla_version="2026.1") + scylla_version="2026.1") def test_refresh_metadata_for_mv(self): """ test for synchronously refreshing materialized view metadata @@ -936,7 +936,7 @@ def test_refresh_user_aggregate_metadata(self): @greaterthanorequalcass30 @requires_collection_indexes @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', - oss_scylla_version="7.0", ent_scylla_version="2026.1") + scylla_version="2026.1") def test_multiple_indices(self): """ test multiple indices on the same column. @@ -971,7 +971,7 @@ def test_multiple_indices(self): @greaterthanorequalcass30 @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', - oss_scylla_version="7.0", ent_scylla_version="2026.1") + scylla_version="2026.1") def test_table_extensions(self): s = self.session ks = self.keyspace_name @@ -1204,8 +1204,8 @@ def test_export_keyspace_schema_udts(self): cluster.shutdown() @greaterthancass21 - @xfail_scylla_version_lt(reason='scylladb/scylladb#10707 - Column name in CREATE INDEX is not quoted', - scylla_version="2023.1.1") + @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', + scylla_version="2026.1") def test_case_sensitivity(self): """ Test that names that need to be escaped in CREATE statements are @@ -1465,6 +1465,8 @@ def create_basic_table(self): def drop_basic_table(self): self.session.execute("DROP TABLE %s" % self.table_name) + @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', + scylla_version="2026.1") def test_index_updates(self): self.create_basic_table() @@ -1506,6 +1508,8 @@ def test_index_updates(self): assert 'a_idx' not in ks_meta.indexes assert 'b_idx' not in ks_meta.indexes + @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', + scylla_version="2026.1") def test_index_follows_alter(self): self.create_basic_table() @@ -2047,6 +2051,8 @@ def test_bad_table(self): assert m._exc_info[0] is self.BadMetaException assert "/*\nWarning:" in m.export_as_string() + @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', + scylla_version="2026.1") def test_bad_index(self): self.session.execute('CREATE TABLE %s (k int PRIMARY KEY, v int)' % self.function_name) self.session.execute('CREATE INDEX ON %s(v)' % self.function_name) @@ -2138,6 +2144,8 @@ def test_dct_alias(self): @greaterthanorequalcass30 +@xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', + scylla_version="2026.1") class MaterializedViewMetadataTestSimple(BasicSharedKeyspaceUnitTestCase): def setUp(self): @@ -2226,6 +2234,8 @@ def test_materialized_view_metadata_drop(self): @greaterthanorequalcass30 +@xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', + scylla_version="2026.1") class MaterializedViewMetadataTestComplex(BasicSegregatedKeyspaceUnitTestCase): def test_create_view_metadata(self): """ diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 4f460459c0..5ae9242ac0 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -1166,6 +1166,8 @@ def test_inherit_first_rk_prepared_param(self): @greaterthanorequalcass30 +@xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Materialized views and secondary indexes are not supported on base tables with tablets.', + scylla_version='2026.1') class MaterializedViewQueryTest(BasicSharedKeyspaceUnitTestCase): def test_mv_filtering(self): From fe2a9432bbc8f77cd22f215d54ef9ac57b578b8f Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Tue, 5 May 2026 08:59:48 +0200 Subject: [PATCH 291/298] test_replicas_are_queried: use dedicated keyspace with RF=1 and tablets disabled --- tests/integration/standard/test_cluster.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 15e525f43c..00ea11ea27 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -1195,27 +1195,35 @@ def test_replicas_are_queried(self): Then using HostFilterPolicy the replica is excluded from the considered hosts. By checking the trace we verify that there are no more replicas. + Requires tablets feature disabled. + @since 3.5 @jira_ticket PYTHON-653 @expected_result the replicas are queried for HostFilterPolicy @test_category metadata """ + ks_name = 'test_replicas_queried_ks' queried_hosts = set() tap_profile = ExecutionProfile( load_balancing_policy=TokenAwarePolicy(RoundRobinPolicy()) ) with TestCluster(execution_profiles={EXEC_PROFILE_DEFAULT: tap_profile}) as cluster: session = cluster.connect(wait_for_all_pools=True) + session.execute("DROP KEYSPACE IF EXISTS {}".format(ks_name)) + session.execute( + "CREATE KEYSPACE {} WITH replication = {{'class': 'NetworkTopologyStrategy', " + "'replication_factor': '1'}} AND tablets = {{'enabled': false}}".format(ks_name) + ) session.execute(''' - CREATE TABLE test1rf.table_with_big_key ( + CREATE TABLE {}.table_with_big_key ( k1 int, k2 int, k3 int, k4 int, - PRIMARY KEY((k1, k2, k3), k4))''') - prepared = session.prepare("""SELECT * from test1rf.table_with_big_key - WHERE k1 = ? AND k2 = ? AND k3 = ? AND k4 = ?""") + PRIMARY KEY((k1, k2, k3), k4))'''.format(ks_name)) + prepared = session.prepare("""SELECT * from {}.table_with_big_key + WHERE k1 = ? AND k2 = ? AND k3 = ? AND k4 = ?""".format(ks_name)) for i in range(10): result = session.execute(prepared, (i, i, i, i), trace=True) trace = result.response_future.get_query_trace(query_cl=ConsistencyLevel.ALL) @@ -1234,14 +1242,14 @@ def test_replicas_are_queried(self): execution_profiles={EXEC_PROFILE_DEFAULT: hfp_profile}) as cluster: session = cluster.connect(wait_for_all_pools=True) - prepared = session.prepare("""SELECT * from test1rf.table_with_big_key - WHERE k1 = ? AND k2 = ? AND k3 = ? AND k4 = ?""") + prepared = session.prepare("""SELECT * from {}.table_with_big_key + WHERE k1 = ? AND k2 = ? AND k3 = ? AND k4 = ?""".format(ks_name)) for _ in range(10): result = session.execute(prepared, (last_i, last_i, last_i, last_i), trace=True) trace = result.response_future.get_query_trace(query_cl=ConsistencyLevel.ALL) self._assert_replica_queried(trace, only_replicas=False) - session.execute('''DROP TABLE test1rf.table_with_big_key''') + session.execute('DROP KEYSPACE {}'.format(ks_name)) @greaterthanorequalcass30 @lessthanorequalcass40 From b9813e7be1dc139595dd977cf05251431b3ec716 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Sun, 29 Mar 2026 10:05:23 +0300 Subject: [PATCH 292/298] ci: update Scylla test version from 2025.2 to 2026.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The integration test suite was pinned to release:2025.2 which is no longer the latest LTS branch. Update to release:2026.1 so CI covers the newest ScyllaDB features and catches regressions earlier. Tests gated by @skip_scylla_version_lt(2026.1.0) — such as the client_routes tests — will now actually execute in CI. --- .github/workflows/integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index fde1ab3e1d..61261aadf8 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -38,7 +38,7 @@ jobs: if: "!contains(github.event.pull_request.labels.*.name, 'disable-integration-tests')" runs-on: ubuntu-24.04 env: - SCYLLA_VERSION: release:2025.2 + SCYLLA_VERSION: release:2026.1 strategy: fail-fast: false matrix: From fb13815c7622f5ec65e8655d667171dd72503afe Mon Sep 17 00:00:00 2001 From: Roy Dahan Date: Mon, 11 May 2026 19:53:47 +0300 Subject: [PATCH 293/298] Replace SimpleStrategy with NetworkTopologyStrategy across codebase ScyllaDB has dropped support for SimpleStrategy. Update all CQL statements, test fixtures, examples, benchmarks, and management utilities to use NetworkTopologyStrategy instead. The SimpleStrategy class definition in cassandra/metadata.py is preserved for backward compatibility with Cassandra clusters. --- benchmarks/base.py | 2 +- cassandra/cqlengine/management.py | 6 +- docs/scylla-specific.rst | 2 +- .../execute_async_with_queue.py | 2 +- .../execute_with_threads.py | 2 +- examples/example_core.py | 2 +- .../cqlengine/connections/test_connection.py | 4 +- tests/integration/long/test_failure_types.py | 2 +- tests/integration/long/test_policies.py | 2 +- tests/integration/long/test_schema.py | 12 ++-- tests/integration/long/test_ssl.py | 4 +- tests/integration/long/utils.py | 2 +- .../simulacron/test_empty_column.py | 4 +- tests/unit/advanced/test_metadata.py | 4 +- tests/unit/test_metadata.py | 66 +++++++++---------- 15 files changed, 58 insertions(+), 58 deletions(-) diff --git a/benchmarks/base.py b/benchmarks/base.py index d9cd004474..3922eefad5 100644 --- a/benchmarks/base.py +++ b/benchmarks/base.py @@ -97,7 +97,7 @@ def setup(options): try: session.execute(""" CREATE KEYSPACE %s - WITH replication = { 'class': 'SimpleStrategy', 'replication_factor': '2' } + WITH replication = { 'class': 'NetworkTopologyStrategy', 'replication_factor': '2' } """ % options.keyspace) log.debug("Setting keyspace...") diff --git a/cassandra/cqlengine/management.py b/cassandra/cqlengine/management.py index d6dc44119a..684bc50b8a 100644 --- a/cassandra/cqlengine/management.py +++ b/cassandra/cqlengine/management.py @@ -56,7 +56,7 @@ def _get_context(keyspaces, connections): def create_keyspace_simple(name, replication_factor, durable_writes=True, connections=None): """ - Creates a keyspace with SimpleStrategy for replica placement + Creates a keyspace with NetworkTopologyStrategy for replica placement If the keyspace already exists, it will not be modified. @@ -66,11 +66,11 @@ def create_keyspace_simple(name, replication_factor, durable_writes=True, connec *There are plans to guard schema-modifying functions with an environment-driven conditional.* :param str name: name of keyspace to create - :param int replication_factor: keyspace replication factor, used with :attr:`~.SimpleStrategy` + :param int replication_factor: keyspace replication factor, used with :attr:`~.NetworkTopologyStrategy` :param bool durable_writes: Write log is bypassed if set to False :param list connections: List of connection names """ - _create_keyspace(name, durable_writes, 'SimpleStrategy', + _create_keyspace(name, durable_writes, 'NetworkTopologyStrategy', {'replication_factor': replication_factor}, connections=connections) diff --git a/docs/scylla-specific.rst b/docs/scylla-specific.rst index e9fe695f8f..4b28781f1c 100644 --- a/docs/scylla-specific.rst +++ b/docs/scylla-specific.rst @@ -91,7 +91,7 @@ New Error Types session = cluster.connect() session.execute(""" CREATE KEYSPACE IF NOT EXISTS keyspace1 - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'} """) session.execute("USE keyspace1") diff --git a/examples/concurrent_executions/execute_async_with_queue.py b/examples/concurrent_executions/execute_async_with_queue.py index 72d2c101cb..794ac78818 100644 --- a/examples/concurrent_executions/execute_async_with_queue.py +++ b/examples/concurrent_executions/execute_async_with_queue.py @@ -31,7 +31,7 @@ session = cluster.connect() session.execute(("CREATE KEYSPACE IF NOT EXISTS examples " - "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1' }")) + "WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1' }")) session.execute("USE examples") session.execute("CREATE TABLE IF NOT EXISTS tbl_sample_kv (id uuid, value text, PRIMARY KEY (id))") prepared_insert = session.prepare("INSERT INTO tbl_sample_kv (id, value) VALUES (?, ?)") diff --git a/examples/concurrent_executions/execute_with_threads.py b/examples/concurrent_executions/execute_with_threads.py index e3c80f5d6b..70893bd5be 100644 --- a/examples/concurrent_executions/execute_with_threads.py +++ b/examples/concurrent_executions/execute_with_threads.py @@ -34,7 +34,7 @@ session = cluster.connect() session.execute(("CREATE KEYSPACE IF NOT EXISTS examples " - "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1' }")) + "WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1' }")) session.execute("USE examples") session.execute("CREATE TABLE IF NOT EXISTS tbl_sample_kv (id uuid, value text, PRIMARY KEY (id))") prepared_insert = session.prepare("INSERT INTO tbl_sample_kv (id, value) VALUES (?, ?)") diff --git a/examples/example_core.py b/examples/example_core.py index 01c766e109..ec41ca7fd5 100644 --- a/examples/example_core.py +++ b/examples/example_core.py @@ -36,7 +36,7 @@ def main(): log.info("creating keyspace...") session.execute(""" CREATE KEYSPACE IF NOT EXISTS %s - WITH replication = { 'class': 'SimpleStrategy', 'replication_factor': '2' } + WITH replication = { 'class': 'NetworkTopologyStrategy', 'replication_factor': '2' } """ % KEYSPACE) log.info("setting keyspace...") diff --git a/tests/integration/cqlengine/connections/test_connection.py b/tests/integration/cqlengine/connections/test_connection.py index 78d5133e63..640c953285 100644 --- a/tests/integration/cqlengine/connections/test_connection.py +++ b/tests/integration/cqlengine/connections/test_connection.py @@ -76,9 +76,9 @@ def setUpClass(cls): super(SeveralConnectionsTest, cls).setUpClass() cls.setup_cluster = TestCluster() cls.setup_session = cls.setup_cluster.connect() - ddl = "CREATE KEYSPACE {0} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': '{1}'}}".format(cls.keyspace1, 1) + ddl = "CREATE KEYSPACE {0} WITH replication = {{'class': 'NetworkTopologyStrategy', 'replication_factor': '{1}'}}".format(cls.keyspace1, 1) execute_with_long_wait_retry(cls.setup_session, ddl) - ddl = "CREATE KEYSPACE {0} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': '{1}'}}".format(cls.keyspace2, 1) + ddl = "CREATE KEYSPACE {0} WITH replication = {{'class': 'NetworkTopologyStrategy', 'replication_factor': '{1}'}}".format(cls.keyspace2, 1) execute_with_long_wait_retry(cls.setup_session, ddl) @classmethod diff --git a/tests/integration/long/test_failure_types.py b/tests/integration/long/test_failure_types.py index beb10f02c0..04d75555f5 100644 --- a/tests/integration/long/test_failure_types.py +++ b/tests/integration/long/test_failure_types.py @@ -187,7 +187,7 @@ def test_write_failures_from_coordinator(self): self._perform_cql_statement( """ CREATE KEYSPACE testksfail - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'} + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '3'} """, consistency_level=ConsistencyLevel.ALL, expected_exception=None) # create table diff --git a/tests/integration/long/test_policies.py b/tests/integration/long/test_policies.py index ab8d125ab1..5cada34d8b 100644 --- a/tests/integration/long/test_policies.py +++ b/tests/integration/long/test_policies.py @@ -48,7 +48,7 @@ def test_should_rethrow_on_unvailable_with_default_policy_if_cas(self): cluster = TestCluster(execution_profiles={EXEC_PROFILE_DEFAULT: ep}) session = cluster.connect() - session.execute("CREATE KEYSPACE test_retry_policy_cas WITH replication = {'class':'SimpleStrategy','replication_factor': 3};") + session.execute("CREATE KEYSPACE test_retry_policy_cas WITH replication = {'class':'NetworkTopologyStrategy','replication_factor': 3};") session.execute("CREATE TABLE test_retry_policy_cas.t (id int PRIMARY KEY, data text);") session.execute('INSERT INTO test_retry_policy_cas.t ("id", "data") VALUES (%(0)s, %(1)s)', {'0': 42, '1': 'testing'}) diff --git a/tests/integration/long/test_schema.py b/tests/integration/long/test_schema.py index 3b4dcd33d5..d60ff775c4 100644 --- a/tests/integration/long/test_schema.py +++ b/tests/integration/long/test_schema.py @@ -57,7 +57,7 @@ def test_recreates(self): log.debug(drop) execute_until_pass(session, drop) - create = "CREATE KEYSPACE {0} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': 3}}".format(keyspace) + create = "CREATE KEYSPACE {0} WITH replication = {{'class': 'NetworkTopologyStrategy', 'replication_factor': 3}}".format(keyspace) log.debug(create) execute_until_pass(session, create) @@ -82,7 +82,7 @@ def test_for_schema_disagreements_different_keyspaces(self): session = self.session for i in range(30): - execute_until_pass(session, "CREATE KEYSPACE test_{0} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': 1}}".format(i)) + execute_until_pass(session, "CREATE KEYSPACE test_{0} WITH replication = {{'class': 'NetworkTopologyStrategy', 'replication_factor': 1}}".format(i)) execute_until_pass(session, "CREATE TABLE test_{0}.cf (key int PRIMARY KEY, value int)".format(i)) for j in range(100): @@ -100,10 +100,10 @@ def test_for_schema_disagreements_same_keyspace(self): for i in range(30): try: - execute_until_pass(session, "CREATE KEYSPACE test WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}") + execute_until_pass(session, "CREATE KEYSPACE test WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}") except AlreadyExists: execute_until_pass(session, "DROP KEYSPACE test") - execute_until_pass(session, "CREATE KEYSPACE test WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}") + execute_until_pass(session, "CREATE KEYSPACE test WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 1}") execute_until_pass(session, "CREATE TABLE test.cf (key int PRIMARY KEY, value int)") @@ -132,7 +132,7 @@ def test_for_schema_disagreement_attribute(self): cluster = TestCluster(max_schema_agreement_wait=0.001) session = cluster.connect(wait_for_all_pools=True) - rs = session.execute("CREATE KEYSPACE test_schema_disagreement WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}") + rs = session.execute("CREATE KEYSPACE test_schema_disagreement WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 3}") self.check_and_wait_for_agreement(session, rs, False) rs = session.execute(SimpleStatement("CREATE TABLE test_schema_disagreement.cf (key int PRIMARY KEY, value int)", consistency_level=ConsistencyLevel.ALL)) @@ -144,7 +144,7 @@ def test_for_schema_disagreement_attribute(self): # These should have schema agreement cluster = TestCluster(max_schema_agreement_wait=100) session = cluster.connect() - rs = session.execute("CREATE KEYSPACE test_schema_disagreement WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}") + rs = session.execute("CREATE KEYSPACE test_schema_disagreement WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 3}") self.check_and_wait_for_agreement(session, rs, True) rs = session.execute(SimpleStatement("CREATE TABLE test_schema_disagreement.cf (key int PRIMARY KEY, value int)", consistency_level=ConsistencyLevel.ALL)) diff --git a/tests/integration/long/test_ssl.py b/tests/integration/long/test_ssl.py index 56dc6a5c2d..0170f56fa1 100644 --- a/tests/integration/long/test_ssl.py +++ b/tests/integration/long/test_ssl.py @@ -116,7 +116,7 @@ def validate_ssl_options(**kwargs): # attempt a few simple commands. insert_keyspace = """CREATE KEYSPACE ssltest - WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'} + WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '3'} """ statement = SimpleStatement(insert_keyspace) statement.consistency_level = 3 @@ -369,7 +369,7 @@ def test_ssl_want_write_errors_are_retried(self): except: pass session.execute( - "CREATE KEYSPACE ssl_error_test WITH replication = {'class':'SimpleStrategy','replication_factor':1};") + "CREATE KEYSPACE ssl_error_test WITH replication = {'class':'NetworkTopologyStrategy','replication_factor':1};") session.execute("CREATE TABLE ssl_error_test.big_text (id uuid PRIMARY KEY, data text);") params = { diff --git a/tests/integration/long/utils.py b/tests/integration/long/utils.py index 93464df8ff..ba9351828e 100644 --- a/tests/integration/long/utils.py +++ b/tests/integration/long/utils.py @@ -63,7 +63,7 @@ def create_schema(cluster, session, keyspace, simple_strategy=True, if simple_strategy: ddl = "CREATE KEYSPACE %s WITH replication" \ - " = {'class': 'SimpleStrategy', 'replication_factor': '%s'}" + " = {'class': 'NetworkTopologyStrategy', 'replication_factor': '%s'}" session.execute(ddl % (keyspace, replication_factor), timeout=10) else: if not replication_strategy: diff --git a/tests/integration/simulacron/test_empty_column.py b/tests/integration/simulacron/test_empty_column.py index 2dbf3985ad..daa9f20fa8 100644 --- a/tests/integration/simulacron/test_empty_column.py +++ b/tests/integration/simulacron/test_empty_column.py @@ -140,9 +140,9 @@ def test_empty_columns_in_system_schema(self): 'delay_in_ms': 0, 'rows': [ { - "strategy_class": "SimpleStrategy", # C* 2.2 + "strategy_class": "NetworkTopologyStrategy", # C* 2.2 "strategy_options": '{}', # C* 2.2 - "replication": {'strategy': 'SimpleStrategy', 'replication_factor': 1}, + "replication": {'strategy': 'NetworkTopologyStrategy', 'replication_factor': 1}, "durable_writes": True, "keyspace_name": "testks" } diff --git a/tests/unit/advanced/test_metadata.py b/tests/unit/advanced/test_metadata.py index 5ccfa5e477..d68a87961d 100644 --- a/tests/unit/advanced/test_metadata.py +++ b/tests/unit/advanced/test_metadata.py @@ -34,8 +34,8 @@ def _create_vertex_metadata(self, label_name='label'): def _create_keyspace_metadata(self, graph_engine): return KeyspaceMetadata( - 'keyspace', True, 'org.apache.cassandra.locator.SimpleStrategy', - {'replication_factor': 1}, graph_engine=graph_engine) + 'keyspace', True, 'org.apache.cassandra.locator.NetworkTopologyStrategy', + {'dc1': 1}, graph_engine=graph_engine) def _create_table_metadata(self, with_vertex=False, with_edge=False): tm = TableMetadataDSE68('keyspace', 'table') diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index dcbb840447..15cf283777 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -25,7 +25,7 @@ from cassandra.marshal import uint16_unpack, uint16_pack from cassandra.metadata import (Murmur3Token, MD5Token, BytesToken, ReplicationStrategy, - NetworkTopologyStrategy, SimpleStrategy, + NetworkTopologyStrategy, LocalStrategy, protect_name, protect_names, protect_value, is_valid_name, UserType, KeyspaceMetadata, get_schema_parser, @@ -96,14 +96,14 @@ def test_replication_strategy(self): assert rs.create('NetworkTopologyStrategy', fake_options_map).dc_replication_factors == NetworkTopologyStrategy(fake_options_map).dc_replication_factors fake_options_map = {'options': 'map'} - assert rs.create('SimpleStrategy', fake_options_map) is None + assert rs.create('NetworkTopologyStrategy', fake_options_map) is None fake_options_map = {'options': 'map'} assert isinstance(rs.create('LocalStrategy', fake_options_map), LocalStrategy) - fake_options_map = {'options': 'map', 'replication_factor': 3} - assert isinstance(rs.create('SimpleStrategy', fake_options_map), SimpleStrategy) - assert rs.create('SimpleStrategy', fake_options_map).replication_factor == SimpleStrategy(fake_options_map).replication_factor + fake_options_map = {'dc1': 3} + assert isinstance(rs.create('NetworkTopologyStrategy', fake_options_map), NetworkTopologyStrategy) + assert rs.create('NetworkTopologyStrategy', fake_options_map).dc_replication_factors == NetworkTopologyStrategy(fake_options_map).dc_replication_factors assert rs.create('xxxxxxxx', fake_options_map) == _UnknownStrategy('xxxxxxxx', fake_options_map) @@ -113,38 +113,38 @@ def test_replication_strategy(self): rs.export_for_schema() def test_simple_replication_type_parsing(self): - """ Test equality between passing numeric and string replication factor for simple strategy """ + """ Test equality between passing numeric and string replication factor for NTS """ rs = ReplicationStrategy() - simple_int = rs.create('SimpleStrategy', {'replication_factor': 3}) - simple_str = rs.create('SimpleStrategy', {'replication_factor': '3'}) + nts_int = rs.create('NetworkTopologyStrategy', {'dc1': 3}) + nts_str = rs.create('NetworkTopologyStrategy', {'dc1': '3'}) - assert simple_int.export_for_schema() == simple_str.export_for_schema() - assert simple_int == simple_str + assert nts_int.export_for_schema() == nts_str.export_for_schema() + assert nts_int == nts_str # make token replica map ring = [MD5Token(0), MD5Token(1), MD5Token(2)] - hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy, host_id=uuid.uuid4()) for host in range(3)] + hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy, datacenter='dc1', rack='rack1', host_id=uuid.uuid4()) for host in range(3)] token_to_host = dict(zip(ring, hosts)) - assert simple_int.make_token_replica_map(token_to_host, ring) == simple_str.make_token_replica_map(token_to_host, ring) + assert nts_int.make_token_replica_map(token_to_host, ring) == nts_str.make_token_replica_map(token_to_host, ring) def test_transient_replication_parsing(self): - """ Test that we can PARSE a transient replication factor for SimpleStrategy """ + """ Test that we can PARSE a transient replication factor for NetworkTopologyStrategy """ rs = ReplicationStrategy() - simple_transient = rs.create('SimpleStrategy', {'replication_factor': '3/1'}) - assert simple_transient.replication_factor_info == ReplicationFactor(3, 1) - assert simple_transient.replication_factor == 2 - assert "'replication_factor': '3/1'" in simple_transient.export_for_schema() + nts_transient = rs.create('NetworkTopologyStrategy', {'dc1': '3/1'}) + assert nts_transient.dc_replication_factors_info['dc1'] == ReplicationFactor(3, 1) + assert nts_transient.dc_replication_factors['dc1'] == 2 + assert "'dc1': '3/1'" in nts_transient.export_for_schema() - simple_str = rs.create('SimpleStrategy', {'replication_factor': '2'}) - assert simple_transient != simple_str + nts_str = rs.create('NetworkTopologyStrategy', {'dc1': '2'}) + assert nts_transient != nts_str # make token replica map ring = [MD5Token(0), MD5Token(1), MD5Token(2)] - hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy, host_id=uuid.uuid4()) for host in range(3)] + hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy, datacenter='dc1', rack='rack1', host_id=uuid.uuid4()) for host in range(3)] token_to_host = dict(zip(ring, hosts)) - assert simple_transient.make_token_replica_map(token_to_host, ring) == simple_str.make_token_replica_map(token_to_host, ring) + assert nts_transient.make_token_replica_map(token_to_host, ring) == nts_str.make_token_replica_map(token_to_host, ring) def test_nts_replication_parsing(self): """ Test equality between passing numeric and string replication factor for NTS """ @@ -318,9 +318,9 @@ def test_nts_export_for_schema(self): assert "{'class': 'NetworkTopologyStrategy', 'dc1': '1', 'dc2': '2'}" == strategy.export_for_schema() def test_simple_strategy_make_token_replica_map(self): - host1 = Host('1', SimpleConvictionPolicy, host_id=uuid.uuid4()) - host2 = Host('2', SimpleConvictionPolicy, host_id=uuid.uuid4()) - host3 = Host('3', SimpleConvictionPolicy, host_id=uuid.uuid4()) + host1 = Host('1', SimpleConvictionPolicy, datacenter='dc1', rack='rack1', host_id=uuid.uuid4()) + host2 = Host('2', SimpleConvictionPolicy, datacenter='dc1', rack='rack1', host_id=uuid.uuid4()) + host3 = Host('3', SimpleConvictionPolicy, datacenter='dc1', rack='rack1', host_id=uuid.uuid4()) token_to_host_owner = { MD5Token(0): host1, MD5Token(100): host2, @@ -328,23 +328,23 @@ def test_simple_strategy_make_token_replica_map(self): } ring = [MD5Token(0), MD5Token(100), MD5Token(200)] - rf1_replicas = SimpleStrategy({'replication_factor': '1'}).make_token_replica_map(token_to_host_owner, ring) + rf1_replicas = NetworkTopologyStrategy({'dc1': '1'}).make_token_replica_map(token_to_host_owner, ring) assertCountEqual(rf1_replicas[MD5Token(0)], [host1]) assertCountEqual(rf1_replicas[MD5Token(100)], [host2]) assertCountEqual(rf1_replicas[MD5Token(200)], [host3]) - rf2_replicas = SimpleStrategy({'replication_factor': '2'}).make_token_replica_map(token_to_host_owner, ring) + rf2_replicas = NetworkTopologyStrategy({'dc1': '2'}).make_token_replica_map(token_to_host_owner, ring) assertCountEqual(rf2_replicas[MD5Token(0)], [host1, host2]) assertCountEqual(rf2_replicas[MD5Token(100)], [host2, host3]) assertCountEqual(rf2_replicas[MD5Token(200)], [host3, host1]) - rf3_replicas = SimpleStrategy({'replication_factor': '3'}).make_token_replica_map(token_to_host_owner, ring) + rf3_replicas = NetworkTopologyStrategy({'dc1': '3'}).make_token_replica_map(token_to_host_owner, ring) assertCountEqual(rf3_replicas[MD5Token(0)], [host1, host2, host3]) assertCountEqual(rf3_replicas[MD5Token(100)], [host2, host3, host1]) assertCountEqual(rf3_replicas[MD5Token(200)], [host3, host1, host2]) def test_ss_equals(self): - assert SimpleStrategy({'replication_factor': '1'}) != NetworkTopologyStrategy({'dc1': 2}) + assert NetworkTopologyStrategy({'dc1': '1'}) != NetworkTopologyStrategy({'dc1': 2}) class NameEscapingTest(unittest.TestCase): @@ -409,9 +409,9 @@ def test_is_valid_name(self): class GetReplicasTest(unittest.TestCase): def _get_replicas(self, token_klass): tokens = [token_klass(i) for i in range(0, (2 ** 127 - 1), 2 ** 125)] - hosts = [Host("ip%d" % i, SimpleConvictionPolicy, host_id=uuid.uuid4()) for i in range(len(tokens))] + hosts = [Host("ip%d" % i, SimpleConvictionPolicy, datacenter="dc1", rack="rack1", host_id=uuid.uuid4()) for i in range(len(tokens))] token_to_primary_replica = dict(zip(tokens, hosts)) - keyspace = KeyspaceMetadata("ks", True, "SimpleStrategy", {"replication_factor": "1"}) + keyspace = KeyspaceMetadata("ks", True, "NetworkTopologyStrategy", {"dc1": "1"}) metadata = Mock(spec=Metadata, keyspaces={'ks': keyspace}) token_map = TokenMap(token_klass, token_to_primary_replica, tokens, metadata) @@ -524,13 +524,13 @@ class KeyspaceMetadataTest(unittest.TestCase): def test_export_as_string_user_types(self): keyspace_name = 'test' - keyspace = KeyspaceMetadata(keyspace_name, True, 'SimpleStrategy', dict(replication_factor=3)) + keyspace = KeyspaceMetadata(keyspace_name, True, 'NetworkTopologyStrategy', dict(dc1=3)) keyspace.user_types['a'] = UserType(keyspace_name, 'a', ['one', 'two'], ['c', 'int']) keyspace.user_types['b'] = UserType(keyspace_name, 'b', ['one', 'two', 'three'], ['d', 'int', 'a']) keyspace.user_types['c'] = UserType(keyspace_name, 'c', ['one'], ['int']) keyspace.user_types['d'] = UserType(keyspace_name, 'd', ['one'], ['c']) - assert """CREATE KEYSPACE test WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'} AND durable_writes = true; + assert """CREATE KEYSPACE test WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': '3'} AND durable_writes = true; CREATE TYPE test.c ( one int @@ -662,7 +662,7 @@ class UnicodeIdentifiersTests(unittest.TestCase): name = b'\'_-()"\xc2\xac'.decode('utf-8') def test_keyspace_name(self): - km = KeyspaceMetadata(self.name, False, 'SimpleStrategy', {'replication_factor': 1}) + km = KeyspaceMetadata(self.name, False, 'NetworkTopologyStrategy', {'dc1': 1}) km.export_as_string() def test_table_name(self): From f7d945ff52df0e101c0b15070675cad0500ced11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 12:57:05 +0000 Subject: [PATCH 294/298] build(deps): bump urllib3 from 2.6.3 to 2.7.0 in /docs Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.3 to 2.7.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.3...2.7.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.7.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- docs/uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/uv.lock b/docs/uv.lock index 56b0841403..515e37abba 100644 --- a/docs/uv.lock +++ b/docs/uv.lock @@ -1067,11 +1067,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] [[package]] From cf01c3f9973388fc6b7ca8425c37deea6ff00a2f Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Fri, 15 May 2026 11:00:26 +0200 Subject: [PATCH 295/298] tests: use tablets-disabled keyspace instead of xfail for scylladb/scylladb#22677 Tests that previously xfailed on ScyllaDB < 2026.1 due to MVs, secondary indexes, and counters not being supported on tables with tablets now create their keyspace with 'AND tablets = {"enabled": false}' for those older versions, so the tests run and pass rather than being expected to fail. A new helper get_tablets_disabled_ddl_suffix() is added to tests/integration/__init__.py to return the appropriate DDL suffix. --- tests/integration/__init__.py | 11 ++++ .../integration/cqlengine/query/test_named.py | 10 +++- tests/integration/standard/test_metadata.py | 60 +++++++++---------- tests/integration/standard/test_query.py | 11 +++- 4 files changed, 55 insertions(+), 37 deletions(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index 7d4d47c9a7..5701e5b3da 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -707,6 +707,17 @@ def xfail_scylla_version_lt(reason, scylla_version, *args, **kwargs): return pytest.mark.xfail(current_version < Version(scylla_version), reason=reason, *args, **kwargs) +def get_tablets_disabled_ddl_suffix(scylla_version='2026.1'): + """ + Returns DDL option string for disabling tablets on ScyllaDB versions older than scylla_version. + Used to work around features not yet supported with tablets (e.g. MVs, secondary indexes, counters). + :param scylla_version: str, version from which tablets support the feature + """ + if SCYLLA_VERSION is not None and Version(get_scylla_version(SCYLLA_VERSION)) < Version(scylla_version): + return " AND tablets = {'enabled': false}" + return "" + + def skip_scylla_version_lt(reason, scylla_version): """ Skip tests on scylla versions older than the specified thresholds. diff --git a/tests/integration/cqlengine/query/test_named.py b/tests/integration/cqlengine/query/test_named.py index 4923a8a583..66ba8b973a 100644 --- a/tests/integration/cqlengine/query/test_named.py +++ b/tests/integration/cqlengine/query/test_named.py @@ -27,7 +27,7 @@ from tests.integration.cqlengine.query.test_queryset import BaseQuerySetUsage -from tests.integration import BasicSharedKeyspaceUnitTestCase, greaterthanorequalcass30, requires_collection_indexes, xfail_scylla_version_lt +from tests.integration import BasicSharedKeyspaceUnitTestCase, greaterthanorequalcass30, requires_collection_indexes, get_tablets_disabled_ddl_suffix, execute_with_long_wait_retry import pytest @@ -280,6 +280,12 @@ def test_get_multipleobjects_exception(self): class TestNamedWithMV(BasicSharedKeyspaceUnitTestCase): + @classmethod + def create_keyspace(cls, rf): + ddl = "CREATE KEYSPACE {0} WITH replication = {{'class': 'NetworkTopologyStrategy', 'replication_factor': '{1}'}}{2}".format( + cls.ks_name, rf, get_tablets_disabled_ddl_suffix()) + execute_with_long_wait_retry(cls.session, ddl) + @classmethod def setUpClass(cls): super(TestNamedWithMV, cls).setUpClass() @@ -292,8 +298,6 @@ def tearDownClass(cls): super(TestNamedWithMV, cls).tearDownClass() @greaterthanorequalcass30 - @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Materialized views and secondary indexes are not supported on base tables with tablets.', - scylla_version='2026.1') @execute_count(5) def test_named_table_with_mv(self): """ diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 84ec6c9ea5..f5a11dd5fe 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -45,7 +45,7 @@ lessthancass40, TestCluster, requires_java_udf, requires_composite_type, requires_collection_indexes, SCYLLA_VERSION, xfail_scylla, xfail_scylla_version_lt, - requirescompactstorage) + requirescompactstorage, get_tablets_disabled_ddl_suffix, execute_with_long_wait_retry) from tests.util import wait_until, assertRegex, assertDictEqual, assertListEqual, assert_startswith_diff @@ -141,6 +141,12 @@ def test_bad_contact_point(self): class SchemaMetadataTests(BasicSegregatedKeyspaceUnitTestCase): + @classmethod + def create_keyspace(cls, rf): + ddl = "CREATE KEYSPACE {0} WITH replication = {{'class': 'NetworkTopologyStrategy', 'replication_factor': '{1}'}}{2}".format( + cls.ks_name, rf, get_tablets_disabled_ddl_suffix()) + execute_with_long_wait_retry(cls.session, ddl) + def test_schema_metadata_disable(self): """ Checks to ensure that schema metadata_enabled, and token_metadata_enabled @@ -448,8 +454,6 @@ def test_dense_compact_storage(self): tablemeta = self.get_table_metadata() self.check_create_statement(tablemeta, create_statement) - @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Counters are not yet supported with tablets', - scylla_version="2026.1") def test_counter(self): create_statement = ( "CREATE TABLE {keyspace}.{table} (" @@ -724,8 +728,6 @@ def test_refresh_table_metadata(self): cluster2.shutdown() @greaterthanorequalcass30 - @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', - scylla_version="2026.1") def test_refresh_metadata_for_mv(self): """ test for synchronously refreshing materialized view metadata @@ -935,8 +937,6 @@ def test_refresh_user_aggregate_metadata(self): @greaterthanorequalcass30 @requires_collection_indexes - @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', - scylla_version="2026.1") def test_multiple_indices(self): """ test multiple indices on the same column. @@ -970,8 +970,6 @@ def test_multiple_indices(self): assert index_2.keyspace_name == "schemametadatatests" @greaterthanorequalcass30 - @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', - scylla_version="2026.1") def test_table_extensions(self): s = self.session ks = self.keyspace_name @@ -1204,8 +1202,6 @@ def test_export_keyspace_schema_udts(self): cluster.shutdown() @greaterthancass21 - @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', - scylla_version="2026.1") def test_case_sensitivity(self): """ Test that names that need to be escaped in CREATE statements are @@ -1218,10 +1214,9 @@ def test_case_sensitivity(self): cfname = 'AnInterestingTable' session.execute("DROP KEYSPACE IF EXISTS {0}".format(ksname)) - session.execute(""" - CREATE KEYSPACE "%s" - WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'} - """ % (ksname,)) + session.execute( + ("CREATE KEYSPACE \"%s\" WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'}" + + get_tablets_disabled_ddl_suffix()) % (ksname,)) session.execute(""" CREATE TABLE "%s"."%s" ( k int, @@ -1442,11 +1437,9 @@ def setup_class(cls): if cls.keyspace_name in cls.cluster.metadata.keyspaces: cls.session.execute("DROP KEYSPACE %s" % cls.keyspace_name) - cls.session.execute( - """ - CREATE KEYSPACE %s - WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'}; - """ % cls.keyspace_name) + ddl = ("CREATE KEYSPACE %s WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'}" + + get_tablets_disabled_ddl_suffix()) + cls.session.execute(ddl % cls.keyspace_name) cls.session.set_keyspace(cls.keyspace_name) except Exception: cls.cluster.shutdown() @@ -1465,8 +1458,6 @@ def create_basic_table(self): def drop_basic_table(self): self.session.execute("DROP TABLE %s" % self.table_name) - @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', - scylla_version="2026.1") def test_index_updates(self): self.create_basic_table() @@ -1508,8 +1499,6 @@ def test_index_updates(self): assert 'a_idx' not in ks_meta.indexes assert 'b_idx' not in ks_meta.indexes - @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', - scylla_version="2026.1") def test_index_follows_alter(self): self.create_basic_table() @@ -2019,7 +2008,8 @@ def setup_class(cls): cls.cluster = TestCluster() cls.keyspace_name = cls.__name__.lower() cls.session = cls.cluster.connect() - cls.session.execute("CREATE KEYSPACE %s WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'}" % cls.keyspace_name) + ddl = "CREATE KEYSPACE %s WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '1'}" + get_tablets_disabled_ddl_suffix() + cls.session.execute(ddl % cls.keyspace_name) cls.session.set_keyspace(cls.keyspace_name) connection = cls.cluster.control_connection._connection @@ -2051,8 +2041,6 @@ def test_bad_table(self): assert m._exc_info[0] is self.BadMetaException assert "/*\nWarning:" in m.export_as_string() - @xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', - scylla_version="2026.1") def test_bad_index(self): self.session.execute('CREATE TABLE %s (k int PRIMARY KEY, v int)' % self.function_name) self.session.execute('CREATE INDEX ON %s(v)' % self.function_name) @@ -2144,10 +2132,15 @@ def test_dct_alias(self): @greaterthanorequalcass30 -@xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', - scylla_version="2026.1") class MaterializedViewMetadataTestSimple(BasicSharedKeyspaceUnitTestCase): + @classmethod + def create_keyspace(cls, rf): + ddl = "CREATE KEYSPACE {0} WITH replication = {{'class': 'NetworkTopologyStrategy', 'replication_factor': '{1}'}}{2}".format( + cls.ks_name, rf, get_tablets_disabled_ddl_suffix()) + execute_with_long_wait_retry(cls.session, ddl) + + def setUp(self): self.session.execute("CREATE TABLE {0}.{1} (pk int PRIMARY KEY, c int)".format(self.keyspace_name, self.function_table_name)) self.session.execute( @@ -2234,9 +2227,14 @@ def test_materialized_view_metadata_drop(self): @greaterthanorequalcass30 -@xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Secondary indexes are not supported on base tables with tablets', - scylla_version="2026.1") class MaterializedViewMetadataTestComplex(BasicSegregatedKeyspaceUnitTestCase): + + @classmethod + def create_keyspace(cls, rf): + ddl = "CREATE KEYSPACE {0} WITH replication = {{'class': 'NetworkTopologyStrategy', 'replication_factor': '{1}'}}{2}".format( + cls.ks_name, rf, get_tablets_disabled_ddl_suffix()) + execute_with_long_wait_retry(cls.session, ddl) + def test_create_view_metadata(self): """ test to ensure that materialized view metadata is properly constructed diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 5ae9242ac0..210f6dacb1 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -26,7 +26,8 @@ from cassandra.policies import HostDistance, RoundRobinPolicy, WhiteListRoundRobinPolicy from tests.integration import use_singledc, PROTOCOL_VERSION, BasicSharedKeyspaceUnitTestCase, \ greaterthanprotocolv3, MockLoggingHandler, get_supported_protocol_versions, local, get_cluster, setup_keyspace, \ - USE_CASS_EXTERNAL, greaterthanorequalcass40, TestCluster, xfail_scylla, xfail_scylla_version_lt + USE_CASS_EXTERNAL, greaterthanorequalcass40, TestCluster, xfail_scylla, xfail_scylla_version_lt, \ + get_tablets_disabled_ddl_suffix, execute_with_long_wait_retry from tests import notwindows from tests.integration import greaterthanorequalcass30, get_node from tests.util import assertListEqual, wait_until @@ -1166,10 +1167,14 @@ def test_inherit_first_rk_prepared_param(self): @greaterthanorequalcass30 -@xfail_scylla_version_lt(reason='scylladb/scylladb#22677 - Materialized views and secondary indexes are not supported on base tables with tablets.', - scylla_version='2026.1') class MaterializedViewQueryTest(BasicSharedKeyspaceUnitTestCase): + @classmethod + def create_keyspace(cls, rf): + ddl = "CREATE KEYSPACE {0} WITH replication = {{'class': 'NetworkTopologyStrategy', 'replication_factor': '{1}'}}{2}".format( + cls.ks_name, rf, get_tablets_disabled_ddl_suffix()) + execute_with_long_wait_retry(cls.session, ddl) + def test_mv_filtering(self): """ Test to ensure that cql filtering where clauses are properly supported in the python driver. From a0eb30421c583d2f8985764d096148240c2ed2cc Mon Sep 17 00:00:00 2001 From: Roy Dahan Date: Mon, 11 May 2026 21:10:57 +0300 Subject: [PATCH 296/298] asyncio: fix SSL connections by using native TLS transport Python 3.8+ rejects ssl.SSLSocket in asyncio's sock_sendall/sock_recv with TypeError. This caused the driver to fail connecting to ScyllaDB clusters requiring TLS, manifesting as 'protocol version 21 not supported' errors (0x15 = TLS Alert byte misread as protocol version). Fix by using asyncio's native TLS transport (loop.create_connection with ssl= parameter) instead of wrapping sockets with ssl.SSLContext.wrap_socket(). This preserves shard-aware port binding done during _initiate_connection(). Add _AsyncioProtocol to bridge asyncio's transport/protocol API back to Connection.process_io_buffer() for SSL data reads. Non-SSL connections continue using the existing sock_recv path. Fixes #330 --- cassandra/io/asyncioreactor.py | 199 ++++++++++++++++++++++++++++----- 1 file changed, 168 insertions(+), 31 deletions(-) diff --git a/cassandra/io/asyncioreactor.py b/cassandra/io/asyncioreactor.py index 66e1d7295c..452667c8eb 100644 --- a/cassandra/io/asyncioreactor.py +++ b/cassandra/io/asyncioreactor.py @@ -23,8 +23,8 @@ asyncio.run_coroutine_threadsafe except AttributeError: raise ImportError( - 'Cannot use asyncioreactor without access to ' - 'asyncio.run_coroutine_threadsafe (added in 3.4.6 and 3.5.1)' + "Cannot use asyncioreactor without access to " + "asyncio.run_coroutine_threadsafe (added in 3.4.6 and 3.5.1)" ) @@ -38,12 +38,12 @@ class AsyncioTimer(object): @property def end(self): - raise NotImplementedError('{} is not compatible with TimerManager and ' - 'does not implement .end()') + raise NotImplementedError( + "{} is not compatible with TimerManager and does not implement .end()" + ) def __init__(self, timeout, callback, loop): - delayed = self._call_delayed_coro(timeout=timeout, - callback=callback) + delayed = self._call_delayed_coro(timeout=timeout, callback=callback) self._handle = asyncio.run_coroutine_threadsafe(delayed, loop=loop) @staticmethod @@ -63,17 +63,61 @@ def cancel(self): def finish(self): # connection.Timer method not implemented here because we can't inspect # the Handle returned from call_later - raise NotImplementedError('{} is not compatible with TimerManager and ' - 'does not implement .finish()') + raise NotImplementedError( + "{} is not compatible with TimerManager and does not implement .finish()" + ) + + +class _AsyncioProtocol(asyncio.Protocol): + """ + Protocol adapter for asyncio SSL connections. Bridges asyncio's + transport/protocol API back to AsyncioConnection's buffer processing. + """ + + def __init__(self, connection, loop_args=None): + self._connection = connection + self.transport = None + self.write_ready = asyncio.Event(**(loop_args or {})) + self.write_ready.set() + + def connection_made(self, transport): + self.transport = transport + + def data_received(self, data): + conn = self._connection + conn._iobuf.write(data) + if conn._iobuf.tell(): + conn.process_io_buffer() + + def pause_writing(self): + self.write_ready.clear() + + def resume_writing(self): + self.write_ready.set() + + def connection_lost(self, exc): + # Unblock any paused writer so shutdown does not hang + self.write_ready.set() + conn = self._connection + if exc: + log.debug("Connection %s lost: %s", conn, exc) + conn.defunct(exc) + else: + log.debug("Connection %s closed by server", conn) + conn.close() + + def eof_received(self): + return False class AsyncioConnection(Connection): """ - An experimental implementation of :class:`.Connection` that uses the - ``asyncio`` module in the Python standard library for its event loop. + An implementation of :class:`.Connection` that uses the ``asyncio`` + module in the Python standard library for its event loop. - 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. + Supports SSL connections via asyncio's native TLS transport, which + avoids the incompatibility between ``ssl.SSLSocket`` and asyncio's + low-level socket methods (``sock_sendall``, ``sock_recv``). """ _loop = None @@ -88,26 +132,109 @@ class AsyncioConnection(Connection): def __init__(self, *args, **kwargs): Connection.__init__(self, *args, **kwargs) self._background_tasks = set() + self._transport = None + self._using_ssl = bool(self.ssl_context) self._connect_socket() self._socket.setblocking(0) loop_args = dict() if sys.version_info[0] == 3 and sys.version_info[1] < 10: - loop_args['loop'] = self._loop + loop_args["loop"] = self._loop + self._protocol = _AsyncioProtocol(self, loop_args) if self._using_ssl else None + self._ssl_ready = asyncio.Event(**loop_args) if self._using_ssl else None self._write_queue = asyncio.Queue(**loop_args) self._write_queue_lock = asyncio.Lock(**loop_args) # see initialize_reactor -- loop is running in a separate thread, so we # have to use a threadsafe call - self._read_watcher = asyncio.run_coroutine_threadsafe( - self.handle_read(), loop=self._loop - ) + if self._using_ssl: + # For SSL: set up asyncio transport/protocol, then start writer + self._read_watcher = asyncio.run_coroutine_threadsafe( + self._setup_ssl_and_run(), loop=self._loop + ) + else: + # For non-SSL: use low-level sock_sendall/sock_recv as before + self._read_watcher = asyncio.run_coroutine_threadsafe( + self.handle_read(), loop=self._loop + ) self._write_watcher = asyncio.run_coroutine_threadsafe( self.handle_write(), loop=self._loop ) self._send_options_message() + def _connect_socket(self): + """ + Override base class to skip SSL wrapping of the socket. + For SSL connections, the plain TCP socket is connected here, and TLS + is set up later via asyncio's native SSL transport in _setup_ssl_and_run(). + """ + sockerr = None + addresses = self._get_socket_addresses() + for af, socktype, proto, _, sockaddr in addresses: + try: + self._socket = self._socket_impl.socket(af, socktype, proto) + # Do NOT wrap with ssl_context here -- asyncio will handle TLS + self._socket.settimeout(self.connect_timeout) + self._initiate_connection(sockaddr) + self._socket.settimeout(None) + + local_addr = self._socket.getsockname() + log.debug("Connection %s: '%s' -> '%s'", id(self), local_addr, sockaddr) + sockerr = None + break + except socket.error as err: + if self._socket: + self._socket.close() + self._socket = None + sockerr = err + + if sockerr: + raise socket.error( + sockerr.errno, + "Tried connecting to %s. Last error: %s" + % ([a[4] for a in addresses], sockerr.strerror or sockerr), + ) + + if self.sockopts: + for args in self.sockopts: + self._socket.setsockopt(*args) + + async def _setup_ssl_and_run(self): + """ + Upgrade the plain TCP connection to TLS using asyncio's native SSL + transport, then continuously read data via the protocol callbacks. + """ + try: + ssl_context = self.ssl_context + server_hostname = None + if self.ssl_options: + server_hostname = self.ssl_options.get("server_hostname", None) + if server_hostname is None: + # asyncio's create_connection requires server_hostname when + # ssl= is set. Use endpoint address for SNI/verification when + # check_hostname is enabled; otherwise pass "" to suppress SNI. + server_hostname = ( + self.endpoint.address if ssl_context.check_hostname else "" + ) + + transport, protocol = await self._loop.create_connection( + lambda: self._protocol, + sock=self._socket, + ssl=ssl_context, + server_hostname=server_hostname, + ) + self._transport = transport + + if self._check_hostname: + self._validate_hostname() + self._ssl_ready.set() + except Exception as exc: + log.debug("SSL setup failed for %s: %s", self, exc) + self.defunct(exc) + # Unblock handle_write so it can observe the defunct state and exit + self._ssl_ready.set() + return @classmethod def initialize_reactor(cls): @@ -126,8 +253,9 @@ def initialize_reactor(cls): cls._loop = asyncio.new_event_loop() # daemonize so the loop will be shut down on interpreter # shutdown - cls._loop_thread = Thread(target=cls._loop.run_forever, - daemon=True, name="asyncio_thread") + cls._loop_thread = Thread( + target=cls._loop.run_forever, daemon=True, name="asyncio_thread" + ) cls._loop_thread.start() @classmethod @@ -142,9 +270,7 @@ def close(self): # close from the loop thread to avoid races when removing file # descriptors - asyncio.run_coroutine_threadsafe( - self._close(), loop=self._loop - ) + asyncio.run_coroutine_threadsafe(self._close(), loop=self._loop) async def _close(self): log.debug("Closing connection (%s) to %s" % (id(self), self.endpoint)) @@ -152,7 +278,10 @@ async def _close(self): self._write_watcher.cancel() if self._read_watcher: self._read_watcher.cancel() - if self._socket: + if self._transport: + self._transport.close() + self._transport = None + elif self._socket: self._loop.remove_writer(self._socket.fileno()) self._loop.remove_reader(self._socket.fileno()) self._socket.close() @@ -172,15 +301,12 @@ def push(self, data): if len(data) > buff_size: chunks = [] for i in range(0, len(data), buff_size): - chunks.append(data[i:i + buff_size]) + chunks.append(data[i : i + buff_size]) else: chunks = [data] if self._loop_thread != threading.current_thread(): - asyncio.run_coroutine_threadsafe( - self._push_msg(chunks), - loop=self._loop - ) + asyncio.run_coroutine_threadsafe(self._push_msg(chunks), loop=self._loop) else: # avoid races/hangs by just scheduling this, not using threadsafe task = self._loop.create_task(self._push_msg(chunks)) @@ -194,13 +320,25 @@ async def _push_msg(self, chunks): for chunk in chunks: self._write_queue.put_nowait(chunk) - async def handle_write(self): + # For SSL connections, wait until the TLS handshake completes + if self._ssl_ready: + await self._ssl_ready.wait() + if self.is_defunct: + return while True: try: next_msg = await self._write_queue.get() if next_msg: - await self._loop.sock_sendall(self._socket, next_msg) + if self._transport: + # SSL: use asyncio transport (handles TLS transparently) + await self._protocol.write_ready.wait() + if self.is_closed or self.is_defunct or not self._transport: + return + self._transport.write(next_msg) + else: + # Non-SSL: use low-level socket API + await self._loop.sock_sendall(self._socket, next_msg) except socket.error as err: log.debug("Exception in send for %s: %s", self, err) self.defunct(err) @@ -223,8 +361,7 @@ async def handle_read(self): await asyncio.sleep(0) continue except socket.error as err: - log.debug("Exception during socket recv for %s: %s", - self, err) + log.debug("Exception during socket recv for %s: %s", self, err) self.defunct(err) return # leave the read loop except asyncio.CancelledError: From 44bc95ad6cb66f836fc501cb045bb5fdf95643ba Mon Sep 17 00:00:00 2001 From: sylwiaszunejko Date: Thu, 21 May 2026 11:24:40 +0200 Subject: [PATCH 297/298] Pin GitHub Actions to commit hashes and enforce pinning - Update all action references to use full SHA commit hashes - Configure Renovate to pin digests and require 90-day minimum age - Add github-actions ecosystem to Dependabot --- .github/workflows/build-push.yml | 4 ++-- .github/workflows/call_jira_sync.yml | 2 +- .github/workflows/docs-pages.yml | 4 ++-- .github/workflows/docs-pr.yml | 4 ++-- .github/workflows/integration-tests.yml | 8 ++++---- .github/workflows/lib-build.yml | 16 ++++++++-------- .github/workflows/publish-manually.yml | 4 ++-- renovate.json | 7 +++++++ 8 files changed, 28 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 3a3d93171a..a1a6c854c7 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -24,11 +24,11 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: dist merge-multiple: true - - uses: pypa/gh-action-pypi-publish@release/v1 + - uses: pypa/gh-action-pypi-publish@cef2210092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 with: skip-existing: true diff --git a/.github/workflows/call_jira_sync.yml b/.github/workflows/call_jira_sync.yml index 14f517df40..0855246f48 100644 --- a/.github/workflows/call_jira_sync.yml +++ b/.github/workflows/call_jira_sync.yml @@ -11,7 +11,7 @@ permissions: jobs: jira-sync: - uses: scylladb/github-automation/.github/workflows/main_pr_events_jira_sync.yml@main + uses: scylladb/github-automation/.github/workflows/main_pr_events_jira_sync.yml@83115dc2553dbf968e73271e97fc7aac16b8145a # main 2026-05-20 with: caller_action: ${{ github.event.action }} secrets: diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index 9d14b9c4d8..a413e3317e 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -24,14 +24,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.event.repository.default_branch }} persist-credentials: false fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: working-directory: docs enable-cache: true diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index f0aa64d628..1881c227ed 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -31,13 +31,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: working-directory: docs enable-cache: true diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 61261aadf8..5e76d6bbb4 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -56,10 +56,10 @@ jobs: event_loop_manager: "asyncore" steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up JDK ${{ matrix.java-version }} - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: ${{ matrix.java-version }} distribution: 'adopt' @@ -68,7 +68,7 @@ jobs: run: sudo apt-get install libev4 libev-dev - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: python-version: ${{ matrix.python-version }} @@ -78,7 +78,7 @@ jobs: run: uv sync - name: Cache Scylla download - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ~/.ccm/repository key: scylla-${{ env.SCYLLA_VERSION }}-${{ runner.os }} diff --git a/.github/workflows/lib-build.yml b/.github/workflows/lib-build.yml index 21dcc0604f..04da6cfca5 100644 --- a/.github/workflows/lib-build.yml +++ b/.github/workflows/lib-build.yml @@ -77,11 +77,11 @@ jobs: include: ${{ fromJson(needs.prepare-matrix.outputs.matrix) }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Checkout tag ${{ inputs.target_tag }} if: inputs.target_tag != '' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ inputs.target_tag }} @@ -96,7 +96,7 @@ jobs: echo "CIBW_BEFORE_TEST_WINDOWS=(exit 0)" >> $GITHUB_ENV; - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: python-version: ${{ inputs.python-version }} @@ -111,7 +111,7 @@ jobs: - name: Install Conan if: runner.os == 'Windows' - uses: turtlebrowser/get-conan@main + uses: turtlebrowser/get-conan@e41c1e039be765c0ed9d9d38cc2a287566e1d8b3 # v1.2 - name: Configure libev for Windows if: runner.os == 'Windows' @@ -147,7 +147,7 @@ jobs: run: | CIBW_BUILD="cp3*" cibuildwheel --archs aarch64 --output-dir wheelhouse - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheels-${{ matrix.target }}-${{ matrix.os }} path: ./wheelhouse/*.whl @@ -156,17 +156,17 @@ jobs: name: Build source distribution runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: python-version: ${{ inputs.python-version }} - name: Build sdist run: uv build --sdist - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: source-dist path: dist/*.tar.gz diff --git a/.github/workflows/publish-manually.yml b/.github/workflows/publish-manually.yml index 2f15c6ecda..5b9298fb7f 100644 --- a/.github/workflows/publish-manually.yml +++ b/.github/workflows/publish-manually.yml @@ -58,11 +58,11 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: dist merge-multiple: true - - uses: pypa/gh-action-pypi-publish@release/v1 + - uses: pypa/gh-action-pypi-publish@cef2210092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 with: skip-existing: true diff --git a/renovate.json b/renovate.json index 5db72dd6a9..d85ac38c01 100644 --- a/renovate.json +++ b/renovate.json @@ -2,5 +2,12 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended" + ], + "packageRules": [ + { + "matchManagers": ["github-actions"], + "pinDigests": true, + "minimumReleaseAge": "90 days" + } ] } From 037118e77ffaf82953bebc035f27ea6a533235a1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 09:18:40 +0000 Subject: [PATCH 298/298] chore(deps): update turtlebrowser/get-conan digest to c171f29 --- .github/workflows/lib-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lib-build.yml b/.github/workflows/lib-build.yml index 04da6cfca5..f6959ddfec 100644 --- a/.github/workflows/lib-build.yml +++ b/.github/workflows/lib-build.yml @@ -111,7 +111,7 @@ jobs: - name: Install Conan if: runner.os == 'Windows' - uses: turtlebrowser/get-conan@e41c1e039be765c0ed9d9d38cc2a287566e1d8b3 # v1.2 + uses: turtlebrowser/get-conan@c171f295f3f507360ee018736a6608731aa2109d # v1.2 - name: Configure libev for Windows if: runner.os == 'Windows'