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 outputmodules— Module pattern to apply the rule to (supports*wildcard)allow— Whitelist: only listed dependencies are alloweddeny— 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
allowonly — Whitelist mode. Only listed dependencies are alloweddenyonly — Blacklist mode. Listed dependencies are denied, rest allowedallow+deny— Allow first, then deny removes exceptions- If
allowexists 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 violations1— 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b8421b50675f7fdfc50d870718c388b444b10f448526406985bab75504be98cc
|
|
| MD5 |
912074f74661bd074635e07b258d80f6
|
|
| BLAKE2b-256 |
96d0cf56ade004bb8074b96829af40e5daf5b2f9367dd992bb39f37e8c4f60ff
|
Provenance
The following attestation bundles were made for python_dependency_linter-0.2.0.tar.gz:
Publisher:
publish.yaml on heumsi/python-dependency-linter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_dependency_linter-0.2.0.tar.gz -
Subject digest:
b8421b50675f7fdfc50d870718c388b444b10f448526406985bab75504be98cc - Sigstore transparency entry: 1197269554
- Sigstore integration time:
-
Permalink:
heumsi/python-dependency-linter@89233fcd85bc53d1830652b6293fb972c1c0d6cb -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/heumsi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@89233fcd85bc53d1830652b6293fb972c1c0d6cb -
Trigger Event:
push
-
Statement type:
File details
Details for the file python_dependency_linter-0.2.0-py3-none-any.whl.
File metadata
- Download URL: python_dependency_linter-0.2.0-py3-none-any.whl
- Upload date:
- Size: 10.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
333c70174e84f0c9e17f4bb37a22cb3a843aad8f140790db0fab81b79f704c72
|
|
| MD5 |
f1da148cb8d3efa2288a64a7b88f7e2d
|
|
| BLAKE2b-256 |
84acebc34a0c0e656510739eb3e6d25a5347577a500a0565f73d0ab6a4cfeb3b
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_dependency_linter-0.2.0-py3-none-any.whl -
Subject digest:
333c70174e84f0c9e17f4bb37a22cb3a843aad8f140790db0fab81b79f704c72 - Sigstore transparency entry: 1197269566
- Sigstore integration time:
-
Permalink:
heumsi/python-dependency-linter@89233fcd85bc53d1830652b6293fb972c1c0d6cb -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/heumsi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@89233fcd85bc53d1830652b6293fb972c1c0d6cb -
Trigger Event:
push
-
Statement type: