This is an automated email from the ASF dual-hosted git repository. mck pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/cassandra-ccm.git
commit 27e46a1625453a77b1fca108fea8704644f48c73 Author: Dmitry Kropachev <[email protected]> AuthorDate: Sun Dec 7 03:52:20 2025 -0400 Replace distutils.version with custom implementation distutils.version is getting depricated, we need to move off it. patch by Dmitry Kropachev; reviewed by Mick Semb Wever for CASSANDRA-18321 --- AGENTS.md | 30 +++++++++++++++++++ ccmlib/cluster.py | 2 +- ccmlib/cluster_factory.py | 3 +- ccmlib/common.py | 2 +- ccmlib/node.py | 2 +- ccmlib/repository.py | 2 +- ccmlib/version.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 +- tests/test_common.py | 2 +- tests/test_lib.py | 2 +- 10 files changed, 112 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6815fb5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,30 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- CLI entry points live in `ccm/` and the core implementation is under `ccmlib/` (cluster lifecycle, node management, command helpers). +- Tests are in `tests/` and mirror library modules; add new tests alongside the code they cover. +- Packaging and install metadata: `setup.py`, `setup.cfg`, and runtime deps in `requirements.txt`. Miscellaneous utilities and docs live in `misc/`, `ssl/`, and `INSTALL.md`. + +## Build, Test, and Development Commands +- Create an isolated environment: `python3 -m venv venv && source venv/bin/activate`. +- Install runtime deps and editable package: `pip install -r requirements.txt && pip install -e .`. +- Add test-only deps: `pip install mock pytest requests`. +- Run tests: `pytest` (use `pytest tests/test_node.py -k <pattern>` for focused runs). Expect integration-like cases that spawn local Cassandra nodes; keep environments clean. +- Validate a local install: `ccm --help` to confirm entry points resolve. + +## Coding Style & Naming Conventions +- Python-first codebase; follow PEP 8 with 4-space indents and line-length restraint (~100 chars). +- Maintain Python 2.7 and 3.x compatibility where practical; avoid newer syntax that breaks 2.7 and gate version-specific behavior carefully. +- Use descriptive identifiers for nodes/clusters, and align CLI option names with existing verbs/nouns (`create`, `populate`, `start`, etc.). +- Prefer pure functions for helpers; keep side effects (filesystem, subprocess) localized and well-logged. + +## Testing Guidelines +- Framework: pytest. Name files `test_*.py` and favor small, deterministic cases over long-lived clusters. +- When tests need Cassandra binaries, prefer the repository cache (`~/.ccm/repository`) to avoid repeated downloads. +- Add coverage when touching node lifecycle, logging, or install path resolution; include regression tests reproducing reported issues. +- Use markers or narrow selections for slow tests; do not assume network availability beyond local loopback. + +## Commit & Pull Request Guidelines +- Commits are short, action-oriented summaries (e.g., `Improve jdk validation`, `Fix cleanup when node start fails`); keep a single focus per commit. +- PRs should describe the change, note risk areas (filesystem writes, subprocess calls), and link related tickets/issues. +- Include how you tested (`pytest`, manual `ccm` invocation) and any environment notes (Python version, OS). Screenshots are unnecessary; logs or command transcripts help reviewers. diff --git a/ccmlib/cluster.py b/ccmlib/cluster.py index 6c2d27a..213eb85 100644 --- a/ccmlib/cluster.py +++ b/ccmlib/cluster.py @@ -28,7 +28,7 @@ import subprocess import threading import time from collections import OrderedDict, defaultdict, namedtuple -from distutils.version import LooseVersion #pylint: disable=import-error, no-name-in-module +from ccmlib.version import LooseVersion import yaml from six import print_ diff --git a/ccmlib/cluster_factory.py b/ccmlib/cluster_factory.py index fb90e59..e3b5222 100644 --- a/ccmlib/cluster_factory.py +++ b/ccmlib/cluster_factory.py @@ -24,8 +24,7 @@ import yaml from ccmlib import common, extension, repository from ccmlib.node import Node - -from distutils.version import LooseVersion #pylint: disable=import-error, no-name-in-module +from ccmlib.version import LooseVersion class ClusterFactory(): diff --git a/ccmlib/common.py b/ccmlib/common.py index c297a85..5d4b4aa 100644 --- a/ccmlib/common.py +++ b/ccmlib/common.py @@ -35,7 +35,7 @@ import subprocess import sys import time import yaml -from distutils.version import LooseVersion #pylint: disable=import-error, no-name-in-module +from ccmlib.version import LooseVersion from six import print_ from ccmlib import extension diff --git a/ccmlib/node.py b/ccmlib/node.py index 4c46a38..cc09eab 100644 --- a/ccmlib/node.py +++ b/ccmlib/node.py @@ -35,7 +35,7 @@ import time import warnings from collections import namedtuple from datetime import datetime -from distutils.version import LooseVersion #pylint: disable=import-error, no-name-in-module +from ccmlib.version import LooseVersion import yaml from six import print_, string_types diff --git a/ccmlib/repository.py b/ccmlib/repository.py index 9b41fe9..62e9c0f 100644 --- a/ccmlib/repository.py +++ b/ccmlib/repository.py @@ -30,7 +30,7 @@ import sys import tarfile import tempfile import time -from distutils.version import LooseVersion # pylint: disable=import-error, no-name-in-module +from ccmlib.version import LooseVersion from six import next, print_ diff --git a/ccmlib/version.py b/ccmlib/version.py new file mode 100644 index 0000000..7187159 --- /dev/null +++ b/ccmlib/version.py @@ -0,0 +1,74 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 __future__ import absolute_import + +import re +from functools import total_ordering + +from packaging.version import parse + + +@total_ordering +class LooseVersion(object): + """ + Lightweight compatibility wrapper to replace distutils.version.LooseVersion. + + It delegates comparison/ordering to packaging.version.parse while exposing + ``version`` and ``vstring`` attributes that the existing code expects. + """ + + __slots__ = ("_inner",) + + def __init__(self, version): + if isinstance(version, LooseVersion): + self._inner = version._inner # pylint: disable=protected-access + else: + self._inner = parse(str(version)) + + def __repr__(self): + return "LooseVersion({})".format(str(self._inner)) + + def __str__(self): + return str(self._inner) + + def __hash__(self): + return hash(self._inner) + + def _coerce_other(self, other): + if isinstance(other, LooseVersion): + return other._inner # pylint: disable=protected-access + return parse(str(other)) + + def __eq__(self, other): + return self._inner == self._coerce_other(other) + + def __lt__(self, other): + return self._inner < self._coerce_other(other) + + @property + def vstring(self): + return str(self._inner) + + @property + def version(self): + release = getattr(self._inner, "release", None) + if release: + return release + + # Fallback for legacy version objects without a release tuple + parts = re.split(r"\D+", str(self._inner)) + return tuple(int(p) for p in parts if p) diff --git a/requirements.txt b/requirements.txt index c8153c0..45e818b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,4 +18,4 @@ pyYaml<5.4; python_version < '3' pyYaml; python_version >= '3' six >=1.4.1 psutil - +packaging<21 diff --git a/tests/test_common.py b/tests/test_common.py index 0ecf52f..9760d25 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -18,7 +18,7 @@ import unittest from mock import patch -from distutils.version import LooseVersion +from ccmlib.version import LooseVersion from ccmlib import common from . import ccmtest diff --git a/tests/test_lib.py b/tests/test_lib.py index 88ce9bd..a79c64a 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -29,8 +29,8 @@ import ccmlib from ccmlib.cluster import Cluster from ccmlib.common import _update_java_version, get_supported_jdk_versions_from_dist, get_supported_jdk_versions, get_available_jdk_versions from ccmlib.node import NodeError +from ccmlib.version import LooseVersion from . import TEST_DIR, ccmtest -from distutils.version import LooseVersion # pylint: disable=import-error, no-name-in-module sys.path = [".."] + sys.path --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
