From 69e7a0622dd0debe0b2d75e3986c81eaaf831b89 Mon Sep 17 00:00:00 2001 From: Pablo Caro Revuelta Date: Fri, 26 Jun 2026 11:46:51 +0200 Subject: [PATCH] LITE-33583 Fix Homebrew installation crashing on missing pkg_resources The `brew install cloudblue/connect/connect-cli` produced a binary that crashed on every invocation with "ModuleNotFoundError: No module named 'pkg_resources'". Root cause: setuptools 81 removed the legacy pkg_resources module, and the brew virtualenv ended up with setuptools 82. Several runtime dependencies (interrogatio, and the now-removed fs) import pkg_resources at module load time, so loading the CLI plugins aborted immediately. - Pin setuptools to <81 as an explicit runtime dependency so the generated Homebrew formula installs a setuptools that still ships pkg_resources. This is the actual fix for the broken brew install. - Drop the archived fs (pyfilesystem2) dependency, which also relied on pkg_resources: replace TempFS with stdlib tempfile.TemporaryDirectory in the product cloner and back the test fs fixture with pytest's tmp_path. This removes fs and its transitive appdirs from the lock file. Verified by installing the built wheel into a clean python@3.10 venv seeded with setuptools 82: the constraint downgraded setuptools to 80.10.2 and `ccli --version` runs successfully. --- connect/cli/plugins/product/clone.py | 12 ++++++-- poetry.lock | 41 +++++----------------------- pyproject.toml | 3 +- tests/conftest.py | 19 +++++++++++-- 4 files changed, 35 insertions(+), 40 deletions(-) diff --git a/connect/cli/plugins/product/clone.py b/connect/cli/plugins/product/clone.py index af7e673..eb8f469 100644 --- a/connect/cli/plugins/product/clone.py +++ b/connect/cli/plugins/product/clone.py @@ -1,8 +1,8 @@ from datetime import datetime +from tempfile import TemporaryDirectory from click import ClickException from connect.client import ClientError -from fs.tempfs import TempFS from openpyxl import load_workbook from connect.cli.plugins.product.export import dump_product @@ -20,9 +20,17 @@ from connect.cli.plugins.shared.utils import get_translation_attributes_sheets +class _TempDir: + # ponytail: stdlib stand-in for the archived pyfilesystem2 TempFS, which broke + # on setuptools>=81 (pkg_resources removed). Cleaned up on garbage collection. + def __init__(self, identifier): + self._tmp = TemporaryDirectory(suffix=identifier) + self.root_path = self._tmp.name + + class ProductCloner: def __init__(self, config, source_account, destination_account, product_id, progress, stats): - self.fs = TempFS(identifier=f'_clone_{product_id}') + self.fs = _TempDir(f'_clone_{product_id}') self.config = config self.source_account = source_account if source_account else config.active.id self.destination_account = destination_account if destination_account else config.active.id diff --git a/poetry.lock b/poetry.lock index 635820c..7aa6a35 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. [[package]] name = "anvil-uplink" @@ -38,18 +38,6 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] trio = ["trio (>=0.31.0) ; python_version < \"3.10\"", "trio (>=0.32.0) ; python_version >= \"3.10\""] -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] - [[package]] name = "argparse" version = "1.4.0" @@ -295,6 +283,11 @@ python-versions = ">=3.8" groups = ["main"] markers = "platform_python_implementation != \"CPython\"" files = [ + {file = "brotlicffi-1.2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b13fb476a96f02e477a506423cb5e7bc21e0e3ac4c060c20ba31c44056e38c68"}, + {file = "brotlicffi-1.2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17db36fb581f7b951635cd6849553a95c6f2f53c1a707817d06eae5aeff5f6af"}, + {file = "brotlicffi-1.2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:40190192790489a7b054312163d0ce82b07d1b6e706251036898ce1684ef12e9"}, + {file = "brotlicffi-1.2.0.0-cp314-cp314t-win32.whl", hash = "sha256:a8079e8ecc32ecef728036a1d9b7105991ce6a5385cf51ee8c02297c90fb08c2"}, + {file = "brotlicffi-1.2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:ca90c4266704ca0a94de8f101b4ec029624273380574e4cf19301acfa46c61a0"}, {file = "brotlicffi-1.2.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:9458d08a7ccde8e3c0afedbf2c70a8263227a68dea5ab13590593f4c0a4fd5f4"}, {file = "brotlicffi-1.2.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:84e3d0020cf1bd8b8131f4a07819edee9f283721566fe044a20ec792ca8fd8b7"}, {file = "brotlicffi-1.2.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33cfb408d0cff64cd50bef268c0fed397c46fbb53944aa37264148614a62e990"}, @@ -1208,26 +1201,6 @@ files = [ [package.dependencies] python-dateutil = ">=2.7" -[[package]] -name = "fs" -version = "2.4.16" -description = "Python's filesystem abstraction layer" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "fs-2.4.16-py2.py3-none-any.whl", hash = "sha256:660064febbccda264ae0b6bace80a8d1be9e089e0a5eb2427b7d517f9a91545c"}, - {file = "fs-2.4.16.tar.gz", hash = "sha256:ae97c7d51213f4b70b6a958292530289090de3a7e15841e108fbe144f069d313"}, -] - -[package.dependencies] -appdirs = ">=1.4.3,<1.5.0" -setuptools = "*" -six = ">=1.10,<2.0" - -[package.extras] -scandir = ["scandir (>=1.5,<2.0) ; python_version < \"3.5\""] - [[package]] name = "future" version = "1.0.0" @@ -3301,4 +3274,4 @@ test = ["pytest"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<4" -content-hash = "5d61b79f0bea2d18618350aa0c57ce6cb104d977dae08f77cf0b707ea5dfe19a" +content-hash = "5b901cb00126bc9bbe80cede73e308673accf83b29c3e193e345f5faf1191779" diff --git a/pyproject.toml b/pyproject.toml index 5f649a6..088488a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,8 +72,9 @@ connect-eaas-core = ">=37.0" rich = "^12.4.1" poetry-core = "^1.3.0" uvloop = { version = "^0.22.0", markers = "sys_platform == \"linux\" or sys_platform == \"darwin\"" } -fs = "^2.4.12" pyyaml = "^6.0" +# interrogatio (and other deps) import the legacy pkg_resources API, removed in setuptools 81. +setuptools = "<81" [tool.poetry.group.test.dependencies] black = "23.*" diff --git a/tests/conftest.py b/tests/conftest.py index 631c665..0dea825 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,6 @@ import pytest import responses -from fs.tempfs import TempFS from openpyxl import load_workbook from responses.registries import OrderedRegistry @@ -24,8 +23,22 @@ @pytest.fixture(scope='function') -def fs(): - return TempFS() +def fs(tmp_path): + # ponytail: pytest's tmp_path (auto-cleaned) replaces pyfilesystem2 TempFS; + # tests only need root_path plus these three path helpers, relative to the root. + class _FS: + root_path = str(tmp_path) + + def makedir(self, path): + os.mkdir(os.path.join(self.root_path, path)) + + def makedirs(self, path): + os.makedirs(os.path.join(self.root_path, path)) + + def create(self, path): + open(os.path.join(self.root_path, path), 'w').close() + + return _FS() @pytest.fixture(scope='session', autouse=True)