Import-linter架构约束工具完全指南 - 守护代码架构

随着项目规模增长,模块间的依赖关系往往变得混乱。import-linter 是一个强大的Python架构约束检查工具,它通过定义和强制执行导入规则来保持代码库的整洁和可维护性。

一、什么是import-linter?

import-linter 是一个用于定义和检查Python项目导入约束的工具。它可以帮助你:

  • 防止循环依赖:确保模块间的依赖关系是单向的
  • 强制分层架构:确保低层模块不会导入高层模块
  • 隔离关注点:确保不同功能模块相互独立
  • 禁止危险导入:阻止在特定模块中使用某些库

核心概念

  1. Contract(合约):定义导入规则的配置
  2. Contract Type(合约类型):不同类型的检查规则
  3. Root Package(根包):被检查的Python包

合约类型

类型 说明 典型用途
forbidden 禁止导入特定模块 禁止在核心层导入Django
independence 确保模块相互独立 微服务/模块解耦
layers 强制分层架构 MVC/Clean Architecture

二、安装与配置

2.1 安装

1
2
3
4
5
6
7
8
9
10
11
# 使用pip
pip install import-linter

# 使用uv(推荐)
uv add import-linter --group dev

# 使用Poetry
poetry add import-linter --group dev

# 使用pipx
pipx install import-linter

2.2 基础配置

pyproject.toml 中配置:

1
2
3
4
5
6
7
8
[tool.importlinter]
# 定义根包(必需)
root_packages = [
"myproject",
]

# 或者指定多个包
# root_packages = ["myproject", "tests", "config"]

三、合约类型详解

3.1 Forbidden(禁止导入)合约

禁止在特定模块中导入某些包。

配置示例:

1
2
3
4
5
6
7
8
9
10
11
[[tool.importlinter.contracts]]
name = "Core should not import Django"
type = "forbidden"
source_modules = [
"myproject.core",
"myproject.domain",
]
forbidden_modules = [
"django",
"rest_framework",
]

参数说明:

参数 说明
name 合约名称(必需)
type 合约类型,这里是 forbidden
source_modules 被检查的源模块列表
forbidden_modules 禁止导入的模块列表
ignore_imports 忽略的导入(可选)

企业级示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 防止核心业务逻辑依赖外部框架
[[tool.importlinter.contracts]]
name = "Domain layer must not import infrastructure"
type = "forbidden"
source_modules = [
"myproject.domain",
"myproject.domain.entities",
"myproject.domain.services",
]
forbidden_modules = [
"django",
"rest_framework",
"sqlalchemy",
"celery",
]
# 允许特定例外
ignore_imports = [
"myproject.domain.base -> django.db.models.Model",
]

3.2 Independence(独立性)合约

确保指定的模块相互之间不存在导入关系。

配置示例:

1
2
3
4
5
6
7
8
9
[[tool.importlinter.contracts]]
name = "App modules should be independent"
type = "independence"
modules = [
"myproject.apps.users",
"myproject.apps.orders",
"myproject.apps.products",
"myproject.apps.payments",
]

参数说明:

参数 说明
name 合约名称
type 合约类型,这里是 independence
modules 必须相互独立的模块列表
ignore_imports 忽略的导入(可选)

企业级示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 微服务架构:确保各个服务模块独立
[[tool.importlinter.contracts]]
name = "Microservices must be independent"
type = "independence"
modules = [
"myproject.services.user_service",
"myproject.services.order_service",
"myproject.services.payment_service",
"myproject.services.notification_service",
]

# 只允许通过共享接口通信
ignore_imports = [
"myproject.services.* -> myproject.shared.interfaces",
"myproject.services.* -> myproject.shared.events",
]

3.3 Layers(分层)合约

强制分层架构,确保高层可以导入低层,但低层不能导入高层。

配置示例:

1
2
3
4
5
6
7
8
9
[[tool.importlinter.contracts]]
name = "Clean Architecture Layers"
type = "layers"
layers = [
"myproject.presentation", # 最高层
"myproject.application",
"myproject.domain",
"myproject.infrastructure", # 最低层
]

分层规则:

  • 列表中靠前的是高层靠后的是低层
  • 高层可以导入低层(presentation → domain ✓)
  • 低层不能导入高层(domain → presentation ✗)
  • 同层之间默认不能相互导入

参数说明:

参数 说明
name 合约名称
type 合约类型,这里是 layers
layers 从高到低排列的层级列表
containers 可选,定义包含多个层的容器
ignore_imports 忽略的导入(可选)

使用containers的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
[[tool.importlinter.contracts]]
name = "Django App Layers"
type = "layers"
containers = [
"myproject.apps.users",
"myproject.apps.orders",
]
layers = [
"views", # 最高层
"serializers",
"services",
"models", # 最低层
]

这意味着:

  • myproject.apps.users.views 可以导入 myproject.apps.users.models
  • myproject.apps.users.models 不能导入 myproject.apps.users.views
  • 同样适用于 orders 应用

四、高级配置

4.1 通配符匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
[[tool.importlinter.contracts]]
name = "No direct model imports in views"
type = "forbidden"
source_modules = [
"myproject.apps.*.views",
]
forbidden_modules = [
"myproject.apps.*.models",
]
# 允许通过service层访问
ignore_imports = [
"myproject.apps.*.views -> myproject.apps.*.services",
]

4.2 忽略特定导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[[tool.importlinter.contracts]]
name = "Core independence"
type = "independence"
modules = [
"myproject.module_a",
"myproject.module_b",
]
# 允许特定导入
ignore_imports = [
# 精确匹配
"myproject.module_a.utils -> myproject.module_b.constants",
# 通配符
"myproject.module_a.* -> myproject.shared.*",
]

4.3 未匹配忽略规则的警告级别

1
2
3
4
5
6
7
8
[tool.importlinter]
root_packages = ["myproject"]

# 当ignore_imports规则没有匹配任何导入时的行为
# error: 报错(默认)
# warn: 警告
# none: 忽略
unmatched_ignore_imports_alerting = "warn"

4.4 多包项目配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[tool.importlinter]
root_packages = [
"backend",
"config",
"tests",
]

[[tool.importlinter.contracts]]
name = "Tests should not import config directly"
type = "forbidden"
source_modules = [
"tests",
]
forbidden_modules = [
"config.settings.production",
]

五、企业级完整配置示例

以下是一个基于真实企业项目的完整配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# pyproject.toml

[tool.importlinter]
root_packages = [
"bk_itsm_apps",
"config",
"tests",
]

# ============================================
# 合约1:禁止在核心模块中使用Django ORM
# ============================================
[[tool.importlinter.contracts]]
name = "Domain should not import Django ORM"
type = "forbidden"
source_modules = [
"bk_itsm_apps.*.domain",
"bk_itsm_apps.*.domain.*",
]
forbidden_modules = [
"django.db",
"rest_framework",
]

# ============================================
# 合约2:应用模块独立性
# ============================================
[[tool.importlinter.contracts]]
name = "App modules should be independent"
type = "independence"
modules = [
"bk_itsm_apps.ticket",
"bk_itsm_apps.workflow",
"bk_itsm_apps.sla",
"bk_itsm_apps.service",
]
# 允许通过共享模块通信
ignore_imports = [
"bk_itsm_apps.* -> bk_itsm_apps.core.*",
"bk_itsm_apps.* -> bk_itsm_apps.common.*",
]

# ============================================
# 合约3:分层架构约束
# ============================================
[[tool.importlinter.contracts]]
name = "Layered Architecture"
type = "layers"
containers = [
"bk_itsm_apps.ticket",
"bk_itsm_apps.workflow",
"bk_itsm_apps.sla",
]
layers = [
"views",
"apis",
"services",
"domain",
"models",
]

# ============================================
# 合约4:测试模块约束
# ============================================
[[tool.importlinter.contracts]]
name = "Tests should not import production config"
type = "forbidden"
source_modules = [
"tests",
]
forbidden_modules = [
"config.settings.production",
"config.settings.staging",
]

# ============================================
# 合约5:配置模块独立性
# ============================================
[[tool.importlinter.contracts]]
name = "Config modules independence"
type = "independence"
modules = [
"config.settings.development",
"config.settings.production",
"config.settings.staging",
"config.settings.test",
]
# 允许继承基础配置
ignore_imports = [
"config.settings.* -> config.settings.base",
]

六、运行检查

6.1 基本使用

1
2
3
4
5
6
7
8
9
10
11
# 运行所有合约检查
lint-imports

# 使用Poetry运行
poetry run lint-imports

# 显示详细信息
lint-imports --verbose

# 指定配置文件
lint-imports --config pyproject.toml

6.2 检查结果示例

成功输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
=============
Import Linter
=============

Contracts: 5 found.

Check "Domain should not import Django ORM"... PASSED
Check "App modules should be independent"... PASSED
Check "Layered Architecture"... PASSED
Check "Tests should not import production config"... PASSED
Check "Config modules independence"... PASSED

---------
5 contracts analyzed, 5 passed, 0 broken.

失败输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
=============
Import Linter
=============

Contracts: 5 found.

Check "App modules should be independent"... BROKEN

Forbidden import:
bk_itsm_apps.ticket.views -> bk_itsm_apps.workflow.services

---------
5 contracts analyzed, 4 passed, 1 broken.

七、与其他工具集成

7.1 与pre-commit集成

.pre-commit-config.yaml 中:

1
2
3
4
5
6
7
8
9
10
repos:
- repo: local
hooks:
- id: lint-imports
name: lint-imports
entry: poetry run lint-imports
language: system
types: [python]
pass_filenames: false
always_run: true

7.2 与GitLab CI集成

.gitlab-ci.yml 中:

1
2
3
4
5
6
7
8
9
10
lint-imports:
stage: check
script:
- poetry install
- poetry run lint-imports
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- "**/*.py"
- "pyproject.toml"

7.3 与VS Code集成

创建任务配置 .vscode/tasks.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"version": "2.0.0",
"tasks": [
{
"label": "lint-imports",
"type": "shell",
"command": "poetry run lint-imports",
"problemMatcher": [],
"group": {
"kind": "test",
"isDefault": false
}
}
]
}

八、架构模式示例

8.1 Clean Architecture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[tool.importlinter]
root_packages = ["myproject"]

[[tool.importlinter.contracts]]
name = "Clean Architecture"
type = "layers"
layers = [
"myproject.presentation", # Web框架相关(Django views等)
"myproject.application", # 用例/应用服务
"myproject.domain", # 业务实体和规则
"myproject.infrastructure", # 数据库、外部服务
]

[[tool.importlinter.contracts]]
name = "Domain purity"
type = "forbidden"
source_modules = ["myproject.domain"]
forbidden_modules = [
"django",
"sqlalchemy",
"requests",
"celery",
]

8.2 Hexagonal Architecture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[tool.importlinter]
root_packages = ["myproject"]

[[tool.importlinter.contracts]]
name = "Ports and Adapters"
type = "forbidden"
source_modules = [
"myproject.core",
"myproject.core.domain",
"myproject.core.ports",
]
forbidden_modules = [
"myproject.adapters",
"django",
"sqlalchemy",
]

[[tool.importlinter.contracts]]
name = "Adapters independence"
type = "independence"
modules = [
"myproject.adapters.web",
"myproject.adapters.database",
"myproject.adapters.messaging",
"myproject.adapters.external",
]

8.3 微服务模块化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[tool.importlinter]
root_packages = ["services"]

[[tool.importlinter.contracts]]
name = "Service isolation"
type = "independence"
modules = [
"services.users",
"services.orders",
"services.products",
"services.payments",
]
ignore_imports = [
"services.* -> services.shared.events",
"services.* -> services.shared.interfaces",
]

九、常见问题与解决方案

Q1:如何处理循环导入问题?

1
2
3
4
5
6
7
8
9
# 使用layers合约确保单向依赖
[[tool.importlinter.contracts]]
name = "Prevent circular imports"
type = "layers"
layers = [
"myproject.high_level",
"myproject.mid_level",
"myproject.low_level",
]

Q2:如何允许测试导入所有模块?

1
2
3
4
5
6
7
8
9
# 测试通常需要更宽松的规则
# 不要将tests包含在independence合约中
# 但可以限制测试不能导入生产配置

[[tool.importlinter.contracts]]
name = "Tests configuration safety"
type = "forbidden"
source_modules = ["tests"]
forbidden_modules = ["config.production"]

Q3:如何处理框架集成代码?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建一个integration层
[[tool.importlinter.contracts]]
name = "Clean core with integration layer"
type = "forbidden"
source_modules = [
"myproject.domain",
]
forbidden_modules = [
"django",
]
# 允许通过适配器层访问
ignore_imports = [
"myproject.domain.base -> myproject.adapters.django_base",
]

十、最佳实践

10.1 渐进式采用

1
2
3
4
5
6
7
8
9
10
11
12
# 第一步:从简单的禁止规则开始
[[tool.importlinter.contracts]]
name = "No direct ORM in views"
type = "forbidden"
source_modules = ["myproject.views"]
forbidden_modules = ["myproject.models"]

# 第二步:添加层级约束
# [[tool.importlinter.contracts]]
# name = "Layers"
# type = "layers"
# ...

10.2 合理使用ignore_imports

1
2
3
4
5
6
7
8
9
# 好的做法:明确记录例外原因
[[tool.importlinter.contracts]]
name = "Module independence"
type = "independence"
modules = ["module_a", "module_b"]
# TODO: 重构后移除此例外
ignore_imports = [
"module_a.legacy -> module_b.utils",
]

10.3 命名规范

1
2
3
4
5
# 使用描述性的合约名称
[[tool.importlinter.contracts]]
name = "Domain layer must not depend on infrastructure"
# 而不是
# name = "contract1"

十一、总结

合约类型 用途 典型场景
forbidden 禁止特定导入 保持核心层纯净
independence 模块解耦 微服务/模块化
layers 分层架构 Clean/Hexagonal

核心价值:

  1. 架构守护:自动化检查防止架构退化
  2. 团队协作:统一的架构规范
  3. 可维护性:清晰的模块边界
  4. 早期发现:在CI中发现问题

相关文章