Cyclic Package Dependencies
ID |
python.cyclic_package_dependencies |
Severity |
low |
Remediation Complexity |
hard |
Remediation Risk |
medium |
Remediation Effort |
high |
Resource |
Code Smell |
Language |
Python |
Tags |
code-style, imports |
Description
Reports a cycle in the directed graph of in-project Python module imports. When module A imports B, B imports C, and C imports A, the three are bound together: a change to one ripples through all of them, and they cannot be tested or imported in isolation.
The rule analyses relative imports only (from .x import y, from . import y, from ..pkg import y). Absolute imports (import os, from a.b import c) are ignored — resolving them needs the project’s sys.path, which the scanner does not have.
# a.py
from .b import b_func # FLAW — part of a -> b -> a cycle
# b.py
from .a import a_func # FLAW
Rationale
Cycles defeat modularity. A unit that imports something which transitively imports the unit itself cannot be loaded in any order — Python papers over this with partially-initialised modules at runtime, but the resulting AttributeError-on-first-use is one of the most maddening bug classes the language ships with.
Remediation
-
Move shared abstractions to a third module that both A and B can import without forming a cycle.
-
Convert a top-level import into a deferred local import (inside the function body) — only acceptable for genuine bootstrap cycles.
-
Re-examine the responsibilities — a cycle usually means two modules share a concept that should live in one of them.