From 1ff09116fdd4e78238c009b9ce65a8607e85a127 Mon Sep 17 00:00:00 2001 From: "QUALISYSTEMS\\nahum-t" Date: Sun, 6 Sep 2026 09:17:16 +0300 Subject: [PATCH 1/4] Support modern Python (3.12/3.13): drop pkg_resources and distutils - pkg_resources -> importlib.metadata (version lookup) and packaging.version (parse/Version). Python 3.12 venvs no longer bundle setuptools, and setuptools >=81 removed pkg_resources entirely, so shellfoundry crashed at bootstrap on any modern environment. - distutils.version.StrictVersion -> packaging.version.Version (distutils removed in 3.12; only worked via the setuptools shim). - requirements: add packaging, drop the setuptools>=3.12 marker (no longer needed once pkg_resources is gone). - setup.py: python_requires>=3.8, refresh classifiers to 3.9-3.13. Co-Authored-By: Claude Fable 5 --- requirements.txt | 2 +- setup.py | 9 ++++++--- shellfoundry/bootstrap.py | 5 +++-- shellfoundry/commands/new_command.py | 2 +- shellfoundry/utilities/__init__.py | 12 ++++++------ .../utilities/standards/standards_versions.py | 2 +- shellfoundry/utilities/template_retriever.py | 8 ++------ shellfoundry/utilities/template_versions.py | 4 ++-- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/requirements.txt b/requirements.txt index 974af63..da0b420 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ colorama giturlparse.py ruamel.yaml cryptography -setuptools; python_version >= '3.12' +packaging diff --git a/setup.py b/setup.py index a64bb84..4a90a35 100644 --- a/setup.py +++ b/setup.py @@ -41,11 +41,14 @@ def get_file_content(file_name): "vcenter cmp cloudshell quali command-line cli", classifiers=[ "Development Status :: 5 - Production/Stable", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries", "License :: OSI Approved :: Apache Software License", ], - python_requires=">=2.7", + python_requires=">=3.8", test_suite="tests", ) diff --git a/shellfoundry/bootstrap.py b/shellfoundry/bootstrap.py index 6d9f843..727a185 100644 --- a/shellfoundry/bootstrap.py +++ b/shellfoundry/bootstrap.py @@ -1,8 +1,9 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +import importlib.metadata + import click -import pkg_resources from shellfoundry.commands.config_command import ConfigCommandExecutor from shellfoundry.commands.delete_command import DeleteCommandExecutor @@ -28,7 +29,7 @@ def cli(): def version(): """Displays the shellfoundry version.""" click.echo( - "shellfoundry version " + pkg_resources.get_distribution("shellfoundry").version + "shellfoundry version " + importlib.metadata.version("shellfoundry") ) diff --git a/shellfoundry/commands/new_command.py b/shellfoundry/commands/new_command.py index fbc4de9..4e43b5b 100644 --- a/shellfoundry/commands/new_command.py +++ b/shellfoundry/commands/new_command.py @@ -8,7 +8,7 @@ import click from cloudshell.rest.exceptions import FeatureUnavailable -from pkg_resources import parse_version +from packaging.version import parse as parse_version from requests.exceptions import SSLError from ..exceptions import FatalError diff --git a/shellfoundry/utilities/__init__.py b/shellfoundry/utilities/__init__.py index 1306079..915b237 100644 --- a/shellfoundry/utilities/__init__.py +++ b/shellfoundry/utilities/__init__.py @@ -1,9 +1,9 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +import importlib.metadata import json -import pkg_resources import requests try: @@ -16,7 +16,7 @@ except ImportError: from urllib2 import HTTPError, URLError -from distutils.version import StrictVersion +from packaging.version import Version from shellfoundry import PACKAGE_NAME from shellfoundry.exceptions import ShellFoundryVersionException @@ -37,15 +37,15 @@ def __init__(self, url): def get_installed_version(package_name): - return pkg_resources.get_distribution(package_name).version + return importlib.metadata.version(package_name) def is_index_version_greater_than_current(): MAJOR_INDEX = 0 installed, index = ( - StrictVersion(get_installed_version(PACKAGE_NAME)), - StrictVersion(max_version_from_index()), + Version(get_installed_version(PACKAGE_NAME)), + Version(max_version_from_index()), ) is_major_release = False @@ -53,7 +53,7 @@ def is_index_version_greater_than_current(): if ( is_greater_version and get_index_of_biggest_component_between_two_versions( - index.version, installed.version + index.release, installed.release ) == MAJOR_INDEX ): diff --git a/shellfoundry/utilities/standards/standards_versions.py b/shellfoundry/utilities/standards/standards_versions.py index 619e875..e8070cb 100644 --- a/shellfoundry/utilities/standards/standards_versions.py +++ b/shellfoundry/utilities/standards/standards_versions.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from pkg_resources import parse_version +from packaging.version import parse as parse_version class StandardVersionsFactory(object): diff --git a/shellfoundry/utilities/template_retriever.py b/shellfoundry/utilities/template_retriever.py index 8932e07..7722bf9 100644 --- a/shellfoundry/utilities/template_retriever.py +++ b/shellfoundry/utilities/template_retriever.py @@ -10,12 +10,8 @@ import click import requests import yaml -from pkg_resources import parse_version - -try: - from pkg_resources._vendor.packaging.version import Version -except ImportError: - from packaging.version import Version +from packaging.version import Version +from packaging.version import parse as parse_version from .filters import CompositeFilter diff --git a/shellfoundry/utilities/template_versions.py b/shellfoundry/utilities/template_versions.py index f504348..23d64dd 100644 --- a/shellfoundry/utilities/template_versions.py +++ b/shellfoundry/utilities/template_versions.py @@ -10,10 +10,10 @@ def is_version(vstr): - from distutils.version import StrictVersion + from packaging.version import Version try: - StrictVersion(vstr) + Version(vstr) return True except Exception: return False From 531c3b4bb69a00e876028ad823fdeb2de9b99d3e Mon Sep 17 00:00:00 2001 From: "QUALISYSTEMS\\nahum-t" Date: Sun, 6 Sep 2026 09:28:20 +0300 Subject: [PATCH 2/4] CI green + release prep: bump 1.2.29, drop dead py3.7 CI, black - version.txt 1.2.29 (satisfies check-version; release vehicle for the modern-Python fixes) - CI: package-tox-py-37-39 -> package-tox-py-39; ubuntu-latest runners can no longer install Python 3.7, and this branch sets python_requires>=3.8 anyway. tox envlist follows. - black on bootstrap.py (this branch) and shell_package_installer.py (pre-existing from #275; pre-commit runs --all-files so CI was red on master too) Co-Authored-By: Claude Fable 5 --- .github/workflows/deploy-package.yml | 2 +- .github/workflows/github-release.yml | 2 +- .github/workflows/package.yml | 2 +- shellfoundry/bootstrap.py | 4 +--- shellfoundry/utilities/shell_package_installer.py | 4 ++-- tox.ini | 2 +- version.txt | 2 +- 7 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deploy-package.yml b/.github/workflows/deploy-package.yml index 0a5e215..025eb4c 100644 --- a/.github/workflows/deploy-package.yml +++ b/.github/workflows/deploy-package.yml @@ -4,7 +4,7 @@ on: types: [ published ] jobs: tox-ci: - uses: QualiSystems/.github/.github/workflows/package-tox-py-37-39.yml@master + uses: QualiSystems/.github/.github/workflows/package-tox-py-39.yml@master pypi-deploy: needs: tox-ci uses: QualiSystems/.github/.github/workflows/package-deploy-pypi.yml@master diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index bf380e2..7c05337 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -7,7 +7,7 @@ on: - master jobs: tox-ci: - uses: QualiSystems/.github/.github/workflows/package-tox-py-37-39.yml@master + uses: QualiSystems/.github/.github/workflows/package-tox-py-39.yml@master pypi-deploy: needs: tox-ci uses: QualiSystems/.github/.github/workflows/package-github-release.yml@master diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 34e31cc..955503b 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -7,4 +7,4 @@ on: - master jobs: tox-ci: - uses: QualiSystems/.github/.github/workflows/package-tox-py-37-39.yml@master + uses: QualiSystems/.github/.github/workflows/package-tox-py-39.yml@master diff --git a/shellfoundry/bootstrap.py b/shellfoundry/bootstrap.py index 727a185..b0d9c61 100644 --- a/shellfoundry/bootstrap.py +++ b/shellfoundry/bootstrap.py @@ -28,9 +28,7 @@ def cli(): @cli.command() def version(): """Displays the shellfoundry version.""" - click.echo( - "shellfoundry version " + importlib.metadata.version("shellfoundry") - ) + click.echo("shellfoundry version " + importlib.metadata.version("shellfoundry")) @cli.command() # noqa: A001 diff --git a/shellfoundry/utilities/shell_package_installer.py b/shellfoundry/utilities/shell_package_installer.py index 2c9c4b6..0f104f0 100644 --- a/shellfoundry/utilities/shell_package_installer.py +++ b/shellfoundry/utilities/shell_package_installer.py @@ -218,7 +218,7 @@ def _add_new_shell(self, client, package_full_path): def _parse_installation_error(self, base_message, error): error_str = str(error) cs_message = error_str # Default to full error string - + try: # Check if it's a PackagingRestApiError with embedded JSON response if "response:" in error_str: @@ -232,7 +232,7 @@ def _parse_installation_error(self, base_message, error): except (json.JSONDecodeError, KeyError, ValueError): # If JSON parsing fails, keep the full error string pass - + return "{}. CloudShell responded with: '{}'".format(base_message, cs_message) def _increase_pbar(self, pbar, time_wait): diff --git a/tox.ini b/tox.ini index a468b5b..ea3e556 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] envlist = - py{37,39}-{master} + py39-{master} pre-commit build distshare = dist diff --git a/version.txt b/version.txt index f6b839d..550c9e9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.2.28 +1.2.29 From 7e4cfddeb5efd4142fe023b2e1a412726e4d9d99 Mon Sep 17 00:00:00 2001 From: "QUALISYSTEMS\\nahum-t" Date: Sun, 6 Sep 2026 09:31:35 +0300 Subject: [PATCH 3/4] Fix test_version mock: patch importlib.metadata.version Co-Authored-By: Claude Fable 5 --- tests/test_bootstrap.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 2201fc5..ef37570 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -29,11 +29,9 @@ def setUp(self): def tearDown(self): pass - @patch("shellfoundry.bootstrap.pkg_resources") - def test_version(self, test_dist): - obj = MagicMock() - obj.version = "shellfoundry_version" - test_dist.get_distribution = MagicMock(return_value=obj) + @patch("importlib.metadata.version") + def test_version(self, version_mock): + version_mock.return_value = "shellfoundry_version" result = self.runner.invoke(version) assert result.exit_code == 0 From f8192703d909872fb6cd39c0199f9a2778a3dcbe Mon Sep 17 00:00:00 2001 From: "QUALISYSTEMS\\nahum-t" Date: Sun, 6 Sep 2026 09:33:41 +0300 Subject: [PATCH 4/4] Remove unused MagicMock import Co-Authored-By: Claude Fable 5 --- tests/test_bootstrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index ef37570..48ac9c3 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -1,7 +1,7 @@ #!/usr/bin/python import traceback import unittest -from unittest.mock import MagicMock, patch +from unittest.mock import patch from click.testing import CliRunner