Skip to main content

A dependency linter for Python projects

Project description

python-dependency-linter

A dependency linter for Python projects. Define rules for which modules can depend on what, and catch violations.

What It Does

  • Define dependency rules between modules using a simple YAML or TOML config
  • Detect imports that violate your rules with a single CLI command
  • Integrate into CI or pre-commit to keep your architecture consistent

For Python developers who care about module boundaries and dependency direction — whether you're applying Layered, Hexagonal, Clean Architecture, or your own conventions.

Installation

pip install python-dependency-linter

Or with uv:

uv add python-dependency-linter

Quick Start

Create .python-dependency-linter.yaml in your project root:

rules:
  - name: domain-isolation
    modules: contexts.*.domain
    allow:
      standard_library: [dataclasses, typing]
      third_party: [pydantic]
      local: [contexts.*.domain]

  - name: application-dependency
    modules: contexts.*.application
    allow:
      standard_library: ["*"]
      third_party: [pydantic]
      local:
        - contexts.*.application
        - contexts.*.domain

Run:

pdl check

Output:

contexts/boards/domain/models.py:6
    [domain-isolation] contexts.boards.domain.models → contexts.boards.application.service (local)

contexts/boards/domain/models.py:9
    [domain-isolation] contexts.boards.domain.models → sqlalchemy (third_party)

Found 2 violation(s).

Examples

Layered Architecture

Enforce dependency direction: presentation → application → domain, where domain has no outward dependencies.

rules:
  - name: domain-isolation
    modules: my_app.domain
    allow:
      standard_library: ["*"]
      third_party: []
      local: [my_app.domain]

  - name: application-layer
    modules: my_app.application
    allow:
      standard_library: ["*"]
      third_party: [pydantic]
      local:
        - my_app.application
        - my_app.domain

  - name: presentation-layer
    modules: my_app.presentation
    allow:
      standard_library: ["*"]
      third_party: [fastapi, pydantic]
      local:
        - my_app.presentation
        - my_app.application
        - my_app.domain

Hexagonal Architecture

Isolate domain from infrastructure. Ports (interfaces) live in domain, adapters depend on domain but not vice versa.

rules:
  - name: domain-no-infra
    modules: contexts.*.domain
    allow:
      standard_library: [dataclasses, typing, abc]
      third_party: []
      local: [contexts.*.domain]

  - name: adapters-depend-on-domain
    modules: contexts.*.adapters
    allow:
      standard_library: ["*"]
      third_party: ["*"]
      local:
        - contexts.*.adapters
        - contexts.*.domain

Configuration

Rule Structure

Each rule has:

  • name — Rule identifier, shown in violation output
  • modules — Module pattern to apply the rule to (supports * wildcard)
  • allow — Whitelist: only listed dependencies are allowed
  • deny — Blacklist: listed dependencies are denied
rules:
  - name: rule-name
    modules: my_package.*.domain
    allow:
      standard_library: [dataclasses]
      third_party: [pydantic]
      local: [my_package.*.domain]
    deny:
      third_party: [boto3]

Import Categories

Dependencies are classified into three categories (per PEP 8):

  • standard_library — Python built-in modules (os, sys, typing, ...)
  • third_party — Installed packages (pydantic, sqlalchemy, ...)
  • local — Modules in your project

Behavior

  • No rule — Everything is allowed
  • allow only — Whitelist mode. Only listed dependencies are allowed
  • deny only — Blacklist mode. Listed dependencies are denied, rest allowed
  • allow + deny — Allow first, then deny removes exceptions
  • If allow exists but a category is omitted, that category allows all. For example:
rules:
  - name: domain-isolation
    modules: contexts.*.domain
    allow:
      third_party: [pydantic]
      local: [contexts.*.domain]
      # standard_library is omitted → all standard library imports are allowed

Use "*" to allow all within a category:

allow:
  standard_library: ["*"]  # allow all standard library imports

Wildcard

* matches a single level in dotted module paths:

modules: contexts.*.domain  # matches contexts.boards.domain, contexts.auth.domain, ...

** matches one or more levels in dotted module paths:

modules: contexts.**.domain  # matches contexts.boards.domain, contexts.boards.sub.domain, ...

Submodule Matching

When a pattern is used in allow or deny, it also matches submodules of the matched module. For example:

allow:
  local: [contexts.*.domain]

This allows imports of contexts.boards.domain as well as its submodules like contexts.boards.domain.models or contexts.boards.domain.entities.metric.

Rule Merging

When multiple rules match a module, they are merged. Specific rules override wildcard rules per field:

rules:
  - name: base
    modules: contexts.*.domain
    allow:
      third_party: [pydantic]

  - name: boards-extra
    modules: contexts.boards.domain
    allow:
      third_party: [attrs]  # merged: [pydantic, attrs]

pyproject.toml

You can also configure in pyproject.toml:

[[tool.python-dependency-linter.rules]]
name = "domain-isolation"
modules = "contexts.*.domain"

[tool.python-dependency-linter.rules.allow]
standard_library = ["dataclasses", "typing"]
third_party = ["pydantic"]
local = ["contexts.*.domain"]

[[tool.python-dependency-linter.rules]]
name = "application-dependency"
modules = "contexts.*.application"

[tool.python-dependency-linter.rules.allow]
standard_library = ["*"]
third_party = ["pydantic"]
local = ["contexts.*.application", "contexts.*.domain"]

[[tool.python-dependency-linter.rules]]
name = "no-boto-in-domain"
modules = "contexts.*.domain"

[tool.python-dependency-linter.rules.deny]
third_party = ["boto3"]

CLI

# Check with default config (.python-dependency-linter.yaml)
pdl check

# Specify config file
pdl check --config path/to/config.yaml

# Specify project root
pdl check --project-root path/to/project

Exit codes:

  • 0 — No violations
  • 1 — Violations found

Pre-commit

Add to .pre-commit-config.yaml:

- repo: https://github.com/heumsi/python-dependency-linter
  rev: v0.1.0
  hooks:
    - id: python-dependency-linter

To pass custom options (e.g., a different config file or project root):

- repo: https://github.com/heumsi/python-dependency-linter
  rev: v0.1.0
  hooks:
    - id: python-dependency-linter
      args: [--config, custom-config.yaml, --project-root, src]

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

python_dependency_linter-0.2.0.tar.gz (12.4 kB view details)

Uploaded Source

Built Distribution

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

python_dependency_linter-0.2.0-py3-none-any.whl (10.0 kB view details)

Uploaded Python 3

File details

Details for the file python_dependency_linter-0.2.0.tar.gz.

File metadata

  • Download URL: python_dependency_linter-0.2.0.tar.gz
  • Upload date:
  • Size: 12.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for python_dependency_linter-0.2.0.tar.gz
Algorithm Hash digest
SHA256 b8421b50675f7fdfc50d870718c388b444b10f448526406985bab75504be98cc
MD5 912074f74661bd074635e07b258d80f6
BLAKE2b-256 96d0cf56ade004bb8074b96829af40e5daf5b2f9367dd992bb39f37e8c4f60ff

See more details on using hashes here.

Provenance

The following attestation bundles were made for python_dependency_linter-0.2.0.tar.gz:

Publisher: publish.yaml on heumsi/python-dependency-linter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file python_dependency_linter-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for python_dependency_linter-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 333c70174e84f0c9e17f4bb37a22cb3a843aad8f140790db0fab81b79f704c72
MD5 f1da148cb8d3efa2288a64a7b88f7e2d
BLAKE2b-256 84acebc34a0c0e656510739eb3e6d25a5347577a500a0565f73d0ab6a4cfeb3b

See more details on using hashes here.

Provenance

The following attestation bundles were made for python_dependency_linter-0.2.0-py3-none-any.whl:

Publisher: publish.yaml on heumsi/python-dependency-linter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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