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.