Plan 11 — Use direct subclass
Problem¶
Plan 10 fixed a circular import by wrapping SPDXMarkdown in an abc.ABC +
__new__ + register() indirection. Reproduction with a full traceback
established that the cycle only occurs when our plugin module is the first
thing in the process to import commitizen.changelog_formats — a condition met
by cold pytest collection of our own test file, but never met by real cz
usage (cz bump --dry-run --changelog succeeds unconditionally, with or without
the wrapper).
The traceback:
tests/test_spdx_markdown.py:12
→ src/spdx_markdown.py:20 from commitizen.changelog_formats.markdown import Markdown
→ .venv/.../changelog_formats/__init__.py:59 ep.name: ep.load()
→ importlib/metadata/__init__.py:181 functools.reduce(getattr, attrs, module)
→ AttributeError: partially initialized module has no attribute 'SPDXMarkdown'
Carrying the wrapper in production code is therefore unnecessary complexity for a problem that only manifests at test collection time.
Resolution¶
1. Use direct subclass¶
Replace spdx_markdown.py with a single class:
class SPDXMarkdown(Markdown):— direct inheritance, no wrapper, noabc.ABC, no__new__, noregister().- Same
__init__,get_metadata,get_latest_full_releaseoverrides as the previous_SPDXMarkdownImpl. - Same
_strip_frontmatterutility, unchanged. - Normal top-level imports — no staggered ordering, no
noqa: E402needed.
2. Fix the cold import in tests/conftest.py¶
Add one line, before any other commitizen-related import in the test suite:
This guarantees commitizen.changelog_formats's entry-point-loading dict
comprehension has already completed before tests/test_spdx_markdown.py imports
SPDXMarkdown, eliminating the cold-import path for the whole test directory.
3. Update docs¶
- Mark plan 10 (
10-collapse-impl.md) as superseded by this plan, with a one-line note linking here. - Update
AGENTS.mdarchitecture section — it still describes the old_impl.py/__new__pattern.
Test compatibility¶
tests/test_spdx_markdown.py needs no changes — it already imports
SPDXMarkdown and _strip_frontmatter from spdx_markdown, and that import
now succeeds because conftest.py establishes the correct order, not because
the class itself redirects.
Tasks¶
- Replace
spdx_markdown.py— single directclass SPDXMarkdown(Markdown):, no wrapper. - Add warm-up import to
tests/conftest.py. - Run
ruff check src/— should pass cleanly with nonoqaneeded. - Run
uv run pytest -v— confirm all tests still pass with the conftest fix in place. - Run cold
python -creproduction — confirm it now succeeds with the warm-up import (sanity check, not required for tests). - Run
cz bump --dry-run --changelog— confirm unchanged success. - Mark plan 10 as superseded — add a note at the top of
10-collapse-impl.mdlinking to this plan. - Update
AGENTS.md— replace_impl.py/__new__architecture description with the current direct-subclass pattern.
Files touched¶
| Action | File |
|---|---|
| Modify | src/commitizen_spdx_changelog/formatters/spdx_markdown.py |
| Modify | tests/conftest.py |
| Create | tests/test_plugin_integration.py |
| Modify | 10-collapse-impl.md (supersession note) |
| Modify | AGENTS.md (architecture section) |
Dependencies¶
commitizenwithMarkdownchangelog format (built-in), same version pinned as before (>=4.16.3, tested against4.16.4).
Acceptance¶
-
spdx_markdown.pycontains one class, no wrapper, noregister(). -
isinstance(formatter, SPDXMarkdown)isTruevia real inheritance. -
tests/conftest.pycontains the warm-up import. -
ruff check src/— zero warnings, nonoqa: E402needed anywhere. -
uv run pytest -v— all tests pass unchanged. -
cz bump --dry-run --changelogsucceeds. - Plan 10 marked superseded.
- AGENTS.md architecture section updated.
See also¶
- Plan 10 — Collapse
_impl.py(superseded by this plan) - GitHub issue: #6 — refactor: use direct Markdown subclass