Skip to main content

pymodularity

The underscore's position in a Python import path sets the import's scope. model: Rust's pub(super) and pub(crate), applied to PEP 8 underscores.

pymodularity does 3 jobs on a repo's real Python imports:

  • it draws them as mermaid diagrams.
  • it checks them against conventions derived from the code itself, with no allow-list to maintain.
  • it reports which files and directories carry the most structural coupling.

The analysis needs only the standard library, so pymodularity has no runtime dependencies. exception: check's dependency rule needs uv on PATH.

Install and run

uvx pymodularity check .
uvx pymodularity graph . --out docs/imports
uvx pymodularity metrics .

Or, inside a project: uv add --dev pymodularity and run pymodularity ... in that project's venv.

The rules check enforces

pymodularity check . exits 1 on any finding. Each finding names the importing file, the target and the owner.

  • No import cycles: between files, and between directories at every depth.
    • TYPE_CHECKING imports and in-function imports count.
    • no baseline: an existing cycle fails too.
  • Private items stay inside their owner: only code inside the owner may import a _-prefixed module or name.
    • private module: the owner is the directory holding it.
    • private name from a package: the owner is the package directory.
    • private name from a regular file: the owner is that file alone, so even a sibling file is a finding.
    • attribute access such as mod._x counts like an import.
  • Declared dependencies match the imports: deptry reports unused, missing and transitive dependencies per package root.
    • requirement: uv on PATH. Without it, check exits 2.
    • --no-deps skips this rule.
  • Structure limits: at most 500 lines per file, 10 entries per directory, 100 files per package root and 10 public names per file.
    • no config, no baseline.
    • ceiling: a limit marks the size that is clearly too much. It is no target.
    • below a limit: a smaller interface is still better. metrics reports interface width and exposure share.

Worked cases: the showcase shows each rule except the dependency rule as allowed and finding cases. Definitions: docs/rules.md holds the counting rules, the deptry run, and the ruff rules for functions and classes.

The 3 subcommands

All 3 also take a repeatable --exclude PATTERN, merged with [tool.pymodularity] exclude in <repo>'s own pyproject.toml. Pattern: fnmatch against the POSIX path relative to <repo>, matched on the path itself or any parent directory. Use case: generated code (an OpenAPI client, protobuf stubs) and vendored code. Effect: an excluded tree is invisible to every rule and report, except deptry's scan of a kept package that contains it. An import from kept code into it behaves like an import of code pymodularity can't resolve. Details: docs/rules.md.

  • pymodularity check <repo> [--no-deps]: runs the 4 rules above and prints the findings.
    • exit code: 1 on any finding.
    • --no-deps: skips the dependency rule.
  • pymodularity graph <repo> --out DIR [--html]: writes one mermaid view per directory under DIR.
    • index.md: written for every view.
    • --html: adds a clickable index.html per view. The browser needs network access to load mermaid from a CDN.
  • pymodularity metrics <repo>: prints a coupling report to stdout.
    • reach: transitive dependents and dependencies, plus a score per file and per directory.
    • interface width: the names other code imports from a unit.
    • exposure share: how much of what a file defines other code uses.
    • entry files: how much of a directory outside code reaches into.
    • misplaced files: files whose closest partners sit mostly in another directory.
    • exit code: always 0, because the report is no gate.

Blind spots

Worked cases: the showcase gap cases G1 to G8, each tagged with its outcome.

pymodularity shows import coupling only. Import coupling is a lower bound on logical coupling: "if A changes, must B change?" Coupling that bypasses Python imports stays invisible:

  • HTTP APIs, shared database schemas and tables, config keys and env vars, message and file formats, string-keyed registries, runtime sys.path edits.
  • Two files with no edge can still force each other's edits. Never read a missing edge as independence.

Other limits:

  • ambiguous import: check prints warning: ambiguous import to stderr and draws no edge.
    • cause: several import roots provide the module, and none is an ancestor of the importer.
  • runtime sys.path edit: an import that resolves only through such an edit gets an edge only if the inserted directory is an import root.
  • script sibling: python main.py importing its sibling execution.py, with no package root nearby, gets no edge.
    • reason: resolution looks only through import roots, never through the importer's own directory.
  • tests: pymodularity always skips test directories (tests/, test/) and test files (test_*.py, *_test.py, conftest.py).
    • reason: a test importing a sibling would draw a production dependency that production never has.
  • metrics as a gate: its numbers move even on a repo that passes check.
    • consequence: a gate on them rewards gaming the number, for example replacing an import with a string-keyed registry.
  • attribute form, missed cases:
    • an instance attribute such as obj._y, because pymodularity has no type information.
    • complement for that case: ruff's SLF001. It has no owner scope, so it also flags access from inside the owner.
    • a base name bound to a third-party module, or to a module outside the repo.
    • getattr(mod, "_x") and other importlib access.
    • a name the file also rebinds anywhere: a parameter, an assignment target, a for, with, except or comprehension target, a nested def or class.
    • reason for the last case: a gate must trade false findings for missed ones, so pymodularity skips such a name.

Metadata

Release files for pymodularity 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pymodularity 0.3.0
File Size Uploaded Provenance
pymodularity-0.3.0.tar.gz 39.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pymodularity 0.3.0
File Interpreter ABI Platform
pymodularity-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 86.2 kB

Release files / pymodularity-0.3.0.tar.gz

Download URL pymodularity-0.3.0.tar.gz
Size 39.1 kB
Tags Source
SHA-256 checksum
How to use checksums
be1b42e2d879c3f8c0fb7f260e53354d77689a5ae9a8a45a6049e780813a32dd
BLAKE2b-256 checksum
How to use checksums
cb3b337282f3565b83dc021acc4fd6f6a9b6a349aaeeb08d962fc1c66f983adb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Release files / pymodularity-0.3.0-py3-none-any.whl

Download URL pymodularity-0.3.0-py3-none-any.whl
Size 47.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c24f13bb9b59bde72a83ce69564418b819e7f5b7ca8c79a5c15bff2d32bf9a49
BLAKE2b-256 checksum
How to use checksums
6efa8515bbf6562c9438d7043e9fbd65c04c7e574e8756d0373bc50261721380
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Security for pymodularity 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pymodularity-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page