diff --git a/AUTHORS.rst b/AUTHORS.rst index 848008c153..5f3a5f3253 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -39,3 +39,4 @@ For example: * Chandler Anderson (zenibako) * Ben French (BenjaminFrench) * Rupert Barrow (rupertbarrow) +* Rupesh J (rupeshjSFDC) diff --git a/cumulusci/__init__.py b/cumulusci/__init__.py index 2dd9be88c7..2db01717a4 100644 --- a/cumulusci/__init__.py +++ b/cumulusci/__init__.py @@ -1,15 +1,15 @@ import os import sys +from importlib.metadata import PackageNotFoundError, version from simple_salesforce import api, bulk -from cumulusci.__about__ import __version__ - -__import__("pkg_resources").declare_namespace("cumulusci") - __location__ = os.path.dirname(os.path.realpath(__file__)) -__version__ = __version__ +try: + __version__ = version("cumulusci") +except PackageNotFoundError: + __version__ = "unknown" if sys.version_info < (3, 8): # pragma: no cover raise Exception("CumulusCI requires Python 3.8+.") diff --git a/cumulusci/cli/robot.py b/cumulusci/cli/robot.py index 5083a1b68d..a7409760e9 100644 --- a/cumulusci/cli/robot.py +++ b/cumulusci/cli/robot.py @@ -1,7 +1,7 @@ +import importlib.metadata import sys import click -import pkg_resources import sarge from .runtime import pass_runtime @@ -109,5 +109,8 @@ def _require_npm(): def _is_package_installed(package_name): """Return True if the given package is installed""" - # technique shamelessly stolen from https://stackoverflow.com/a/44210735/7432 - return package_name in {pkg.key for pkg in pkg_resources.working_set} + try: + importlib.metadata.distribution(package_name) + return True + except importlib.metadata.PackageNotFoundError: + return False diff --git a/cumulusci/cli/runtime.py b/cumulusci/cli/runtime.py index 16157d0930..9e596fa7da 100644 --- a/cumulusci/cli/runtime.py +++ b/cumulusci/cli/runtime.py @@ -6,7 +6,7 @@ import click import keyring -import pkg_resources +from packaging import version from cumulusci.cli.utils import get_installed_version from cumulusci.core.exceptions import ConfigError, KeychainKeyNotFound, OrgNotFound @@ -143,7 +143,7 @@ def check_cumulusci_version(self): if self.project_config: min_cci_version = self.project_config.minimum_cumulusci_version if min_cci_version: - parsed_version = pkg_resources.parse_version(min_cci_version) + parsed_version = version.parse(min_cci_version) if get_installed_version() < parsed_version: raise click.UsageError( f"This project requires CumulusCI version {min_cci_version} or later. " diff --git a/cumulusci/cli/tests/test_cci.py b/cumulusci/cli/tests/test_cci.py index ccb06adaea..c91a367dcd 100644 --- a/cumulusci/cli/tests/test_cci.py +++ b/cumulusci/cli/tests/test_cci.py @@ -8,8 +8,8 @@ from unittest import mock import click -import pkg_resources import pytest +from packaging import version from requests.exceptions import ConnectionError from rich.console import Console @@ -402,7 +402,7 @@ def test_cli(): @mock.patch( "cumulusci.cli.cci.get_latest_final_version", - mock.Mock(return_value=pkg_resources.parse_version("100")), + mock.Mock(return_value=version.parse("100")), ) def test_version(capsys): run_click_command(cci.version) @@ -413,7 +413,7 @@ def test_version(capsys): @mock.patch( "cumulusci.cli.cci.get_latest_final_version", - mock.Mock(return_value=pkg_resources.parse_version("1")), + mock.Mock(return_value=version.parse("1")), ) def test_version__latest(capsys): run_click_command(cci.version) diff --git a/cumulusci/cli/tests/test_robot.py b/cumulusci/cli/tests/test_robot.py index f9243c1ff3..50f75c700f 100644 --- a/cumulusci/cli/tests/test_robot.py +++ b/cumulusci/cli/tests/test_robot.py @@ -9,19 +9,11 @@ from .utils import run_cli_command -class MockWorkingSet(list): - """Mocks a pkg_resources.workingset object +class MockDistribution: + """Mocks an importlib.metadata.Distribution object""" - Basically, a list of mock packages representing all of the packages - installed in the current environment - """ - - def __init__(self, *package_names): - super().__init__() - for package_name in package_names: - pkg = mock.Mock() - pkg.key = package_name - self.append(pkg) + def __init__(self, name): + self.name = name def mock_Command(returncodes={}): @@ -49,16 +41,18 @@ def run(): return the_real_mock -@mock.patch("cumulusci.cli.robot.pkg_resources") -def test_is_package_installed(pkg_resources): +@mock.patch("cumulusci.cli.robot.importlib.metadata.distribution") +def test_is_package_installed(mock_distribution): """Verify that the helper _is_package_installed returns the correct result""" - pkg_resources.working_set = MockWorkingSet( - "robotframework", "robotframework-browser", "snowfakery" - ) + # Test when package is installed + mock_distribution.return_value = MockDistribution("robotframework-browser") result = _is_package_installed("robotframework-browser") assert result is True - pkg_resources.working_set = MockWorkingSet("robotframework", "snowfakery") + # Test when package is not installed + from importlib.metadata import PackageNotFoundError + + mock_distribution.side_effect = PackageNotFoundError("Package not found") result = _is_package_installed("robotframework-browser") assert result is False diff --git a/cumulusci/cli/tests/test_utils.py b/cumulusci/cli/tests/test_utils.py index 4298e890f3..79964e322a 100644 --- a/cumulusci/cli/tests/test_utils.py +++ b/cumulusci/cli/tests/test_utils.py @@ -3,10 +3,10 @@ import time from unittest import mock -import pkg_resources import pytest import requests import responses +from packaging import version import cumulusci @@ -46,8 +46,8 @@ def test_get_latest_final_version(): def test_check_latest_version(click, get_latest_final_version, get_installed_version): with utils.timestamp_file() as f: f.write(str(time.time() - 4000)) - get_latest_final_version.return_value = pkg_resources.parse_version("2") - get_installed_version.return_value = pkg_resources.parse_version("1") + get_latest_final_version.return_value = version.parse("2") + get_installed_version.return_value = version.parse("1") utils.check_latest_version() if sys.version_info > utils.LOWEST_SUPPORTED_VERSION: diff --git a/cumulusci/cli/utils.py b/cumulusci/cli/utils.py index e33d3005f5..125ba19303 100644 --- a/cumulusci/cli/utils.py +++ b/cumulusci/cli/utils.py @@ -6,8 +6,8 @@ from collections import defaultdict import click -import pkg_resources import requests +from packaging import version as packaging_version from rich.console import Console from cumulusci import __version__ @@ -74,7 +74,7 @@ def get_latest_final_version(): for versionstring in res["releases"].keys(): if not is_final_release(versionstring): continue - versions.append(pkg_resources.parse_version(versionstring)) + versions.append(packaging_version.parse(versionstring)) versions.sort(reverse=True) return versions[0] @@ -110,9 +110,14 @@ def check_latest_version(): ) -def get_installed_version(): - """returns the version name (e.g. 2.0.0b58) that is installed""" - return pkg_resources.parse_version(__version__) +def parse_version(versionstring: str) -> packaging_version.Version: + """Parse a version string into a Version object.""" + return packaging_version.parse(versionstring) + + +def get_installed_version() -> packaging_version.Version: + """Get the installed version of CumulusCI.""" + return parse_version(__version__) def win32_long_paths_enabled() -> bool: diff --git a/cumulusci/tasks/salesforce/update_profile.py b/cumulusci/tasks/salesforce/update_profile.py index 2aca7de9ab..a9f54139eb 100644 --- a/cumulusci/tasks/salesforce/update_profile.py +++ b/cumulusci/tasks/salesforce/update_profile.py @@ -1,7 +1,7 @@ import os from collections import defaultdict -import pkg_resources +from packaging import version from cumulusci.core.exceptions import CumulusCIException, TaskOptionsError from cumulusci.core.utils import process_bool_arg, process_list_arg @@ -69,10 +69,8 @@ def _init_options(self, kwargs): # not be using a custom `package.xml` min_cci_version = self.project_config.minimum_cumulusci_version if min_cci_version and "package_xml" not in self.options: - parsed_version = pkg_resources.parse_version(min_cci_version) - default_packages_arg = parsed_version >= pkg_resources.parse_version( - "3.9.0" - ) + parsed_version = version.parse(min_cci_version) + default_packages_arg = parsed_version >= version.parse("3.9.0") else: default_packages_arg = False diff --git a/pyproject.toml b/pyproject.toml index 6bd22c63e2..14ee39ce02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ dependencies = [ "defusedxml", "lxml", "MarkupSafe", + "packaging>=23.0", "psutil", "pydantic<2", "PyJWT",