From d2c5874bbfefef517804f6da07f28dd88cb97093 Mon Sep 17 00:00:00 2001 From: Federico Capoano Date: Sat, 9 May 2026 14:57:29 -0300 Subject: [PATCH] [ci:fix] Fixed changelog bot commitizen circular import #669 Closes #669 --- .../generate_changelog.py | 10 +++- .../test_generate_changelog.py | 46 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/.github/actions/bot-changelog-generator/generate_changelog.py b/.github/actions/bot-changelog-generator/generate_changelog.py index e59f2eee..49ca69d5 100644 --- a/.github/actions/bot-changelog-generator/generate_changelog.py +++ b/.github/actions/bot-changelog-generator/generate_changelog.py @@ -399,7 +399,15 @@ def build_prompt( def get_openwisp_commitizen(): - """Load the local OpenWISP Commitizen plugin lazily.""" + """Load the local OpenWISP Commitizen plugin lazily. + + ``commitizen.cz`` is imported first so its plugin auto-discovery + completes before ``openwisp_utils.releaser.commitizen`` enters the + import stack. Without this ordering, discovery re-imports our + module mid-initialization and raises ``AttributeError`` on + ``OpenWispCommitizen`` (issue #669). + """ + import commitizen.cz # noqa: F401 — load order matters; see docstring from openwisp_utils.releaser.commitizen import OpenWispCommitizen return OpenWispCommitizen(_CommitizenConfig()) diff --git a/.github/actions/bot-changelog-generator/test_generate_changelog.py b/.github/actions/bot-changelog-generator/test_generate_changelog.py index b804fbd9..3b0e5585 100644 --- a/.github/actions/bot-changelog-generator/test_generate_changelog.py +++ b/.github/actions/bot-changelog-generator/test_generate_changelog.py @@ -20,6 +20,7 @@ get_changelog_validation_errors, get_env_or_exit, get_linked_issues, + get_openwisp_commitizen, get_pr_commits, get_pr_details, get_pr_diff, @@ -524,6 +525,51 @@ def test_raises_on_request_error(self, mock_request): self.assertIn("Failed to post comment", str(context.exception)) +class TestGetOpenwispCommitizen(unittest.TestCase): + """Regression tests for get_openwisp_commitizen (issue #669).""" + + _MODULE_PREFIXES = ("commitizen", "openwisp_utils.releaser.commitizen") + + def setUp(self): + """Evict cached commitizen modules to simulate a fresh process. + + The circular import only triggers on the first load of + ``commitizen.cz``; if any earlier code path cached it, the bug + is hidden and the test would falsely pass. + """ + self._saved_modules = { + name: module + for name, module in sys.modules.items() + if name == "commitizen" + or name.startswith("commitizen.") + or name == "openwisp_utils.releaser.commitizen" + } + for name in self._saved_modules: + del sys.modules[name] + + def tearDown(self): + """Restore the original module cache. + + Avoids leaving partial modules in ``sys.modules`` on failure + and keeps the test isolated from any later test that may rely + on the original commitizen module identity. + """ + for name in list(sys.modules): + if ( + name == "commitizen" + or name.startswith("commitizen.") + or name == "openwisp_utils.releaser.commitizen" + ): + del sys.modules[name] + sys.modules.update(self._saved_modules) + + def test_loads_plugin_without_circular_import(self): + plugin = get_openwisp_commitizen() + self.assertEqual(plugin.__class__.__name__, "OpenWispCommitizen") + self.assertTrue(hasattr(plugin, "schema_pattern")) + self.assertTrue(hasattr(plugin, "validate_commit_message")) + + class TestValidateChangelogOutput(unittest.TestCase): """Tests for validate_changelog_output function."""