Skip to main content

Package dependency metrics for Python — Abstractness, Instability, Distance from Main Sequence

Project description

PyMainSequence

Package dependency metrics for Python — Abstractness, Instability, and Distance from the Main Sequence, as described by Robert C. Martin (Uncle Bob) in Clean Architecture.

Think JDepend or NETDepend, but for Python.


Metrics

Metric Symbol Formula Range Meaning
Afferent Coupling Ca ≥ 0 Components that depend on this one (fan-in)
Efferent Coupling Ce ≥ 0 Components this one depends on (fan-out)
Instability I Ce / (Ca + Ce) 0–1 0 = maximally stable, 1 = maximally unstable
Abstractness A abstract / total classes 0–1 0 = fully concrete, 1 = fully abstract
Distance D |A + I - 1| 0–1 Distance from the Main Sequence; 0 = on the line

The Main Sequence is the line A + I = 1. Components on it balance stability with abstraction.

  • Zone of Pain (I≈0, A≈0): stable but fully concrete — hard to change, painful to extend.
  • Zone of Uselessness (I≈1, A≈1): unstable and fully abstract — nobody depends on it.

Abstract classes, abc.ABC subclasses, @abstractmethod methods, and typing.Protocol subclasses all count toward abstractness.


Installation

pip install pymainsequence
# or with uv:
uv add pymainsequence

Requires Python 3.12+.


CLI

pymainsequence analyze

Print metrics for every component (package) found under a path:

$ pymainsequence analyze ./src

╭───────────┬────┬────┬──────┬──────┬──────┬─────────╮
│ Component │ Ca │ Ce │    I │    A │    D │ Classes │
├───────────┼────┼────┼──────┼──────┼──────┼─────────┤
│ api       │  0 │  2 │ 1.00 │ 0.00 │ 0.00 │       1 │
│ core      │  1 │  1 │ 0.50 │ 0.50 │ 0.00 │       2 │
│ utils     │  2 │  0 │ 0.00 │ 0.00 │ 1.00 │       1 │
╰───────────┴────┴────┴──────┴──────┴──────╯─────────╯
3 component(s)

Distance is colour-coded: green (D ≤ 0.3), yellow (D ≤ 0.5), red (D > 0.5).

Options:

Flag Default Description
--depth N 1 Package nesting depth treated as components
--include-external off Count stdlib/third-party imports in Ce
--format table|json table Output format
pymainsequence analyze ./src --depth 2 --format json

pymainsequence check

Assert constraints and exit non-zero on violations — designed for CI:

pymainsequence check ./src --max-distance 0.5 --max-instability 0.8
# exit 0 on pass, exit 1 on violation

Options:

Flag Description
--max-distance F Maximum allowed D per component
--max-instability F Maximum allowed I per component
--max-ce N Maximum allowed efferent coupling
--no-dep FROM TO Forbidden dependency (repeatable)
pymainsequence check ./src --no-dep core api --no-dep core infra

Programmatic API

from pymainsequence import analyze

report = analyze("./src")

for component in report:
    print(f"{component.name}: I={component.instability:.2f} A={component.abstractness:.2f} D={component.distance:.2f}")

core = report.get("core")
print(core.ca, core.ce, core.afferent, core.efferent)

analyze() accepts an optional depth (default 1) and include_external (default False).


Constraint API

Use enforce() to write architectural tests with pytest (or any test runner):

from pymainsequence import analyze
from pymainsequence.rules import enforce

def test_architecture():
    report = analyze("./src")

    enforce(report) \
        .max_instability(0.8) \
        .max_distance(0.5) \
        .no_dependency("core", "api") \
        .check()

Or as a context manager:

def test_architecture():
    report = analyze("./src")
    with enforce(report) as e:
        e.max_instability(0.8)
        e.max_distance(0.5)
        e.no_dependency("core", "api")
    # raises ViolationError with a full list of offenders on exit

Available constraints

Method Description
.max_instability(threshold) I ≤ threshold for all (or one) component
.max_distance(threshold) D ≤ threshold
.max_abstractness(threshold) A ≤ threshold
.max_ce(n) Efferent coupling ≤ n
.max_ca(n) Afferent coupling ≤ n
.stable_abstractions_principle(threshold=0.5) Alias for max_distance
.no_dependency(from, to) No direct dependency from → to
.check() Raise ViolationError listing all violations

All methods accept an optional component="name" keyword to scope the check to a single component.


How components are determined

A component is a Python package (directory with __init__.py) or a top-level .py file directly under the analyzed path. At --depth 1 (default), only immediate sub-packages are components; files nested deeper still belong to their parent component.

Only imports between components within the analyzed path are counted. Standard library and third-party imports are ignored by default (--include-external to change this).

Relative imports (from . import x, from ..utils import y) are resolved to their absolute module paths before matching.


License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pymainsequence-0.1.0.tar.gz (23.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pymainsequence-0.1.0-py3-none-any.whl (12.0 kB view details)

Uploaded Python 3

File details

Details for the file pymainsequence-0.1.0.tar.gz.

File metadata

  • Download URL: pymainsequence-0.1.0.tar.gz
  • Upload date:
  • Size: 23.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pymainsequence-0.1.0.tar.gz
Algorithm Hash digest
SHA256 69f79f47425ebb5b7daa918acea346208088c7b53ac60072828a1b001d9325c0
MD5 d5ff580877520908089ee31372387bca
BLAKE2b-256 f23c8acb701634164523241b71154f3dcd1420dff944ea8ae03cd9ab0e2543ee

See more details on using hashes here.

File details

Details for the file pymainsequence-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pymainsequence-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 12.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pymainsequence-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 977b0ca5958f7358bed3c9b18f091f7eea9535abb8f87e4ddfa9c4f36f0ba7cf
MD5 326151e344cc43c06cb4b2731533dfde
BLAKE2b-256 db468e2461aec0c4dd29b9a4e0102d0b0ccabe1c2d3bc0c61551f023a2e451f8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page