Skip to content

Commit 3d67a73

Browse files
committed
feat: add import boundary
1 parent eb77776 commit 3d67a73

47 files changed

Lines changed: 424 additions & 2559 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.importlinter

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[importlinter]
2+
root_package = src
3+
4+
[importlinter:contract:todo-cross-module-boundary]
5+
name = Todo module only imports public contracts from other modules
6+
type = forbidden
7+
allow_indirect_imports = True
8+
source_modules =
9+
src.modules.todo
10+
forbidden_modules =
11+
src.modules.user.application
12+
src.modules.user.domain
13+
src.modules.user.infrastructure
14+
src.modules.user.presentation
15+
src.modules.authorization.application
16+
src.modules.authorization.domain
17+
src.modules.authorization.infrastructure
18+
src.modules.authorization.presenter
19+
20+
[importlinter:contract:user-cross-module-boundary]
21+
name = User module only imports public contracts from other modules
22+
type = forbidden
23+
allow_indirect_imports = True
24+
source_modules =
25+
src.modules.user
26+
forbidden_modules =
27+
src.modules.todo.application
28+
src.modules.todo.domain
29+
src.modules.todo.infrastructure
30+
src.modules.todo.presentation
31+
src.modules.authorization.application
32+
src.modules.authorization.domain
33+
src.modules.authorization.infrastructure
34+
src.modules.authorization.presenter
35+
36+
[importlinter:contract:authorization-cross-module-boundary]
37+
name = Authorization module only imports public contracts from other modules
38+
type = forbidden
39+
allow_indirect_imports = True
40+
source_modules =
41+
src.modules.authorization
42+
forbidden_modules =
43+
src.modules.todo.application
44+
src.modules.todo.domain
45+
src.modules.todo.infrastructure
46+
src.modules.todo.presentation
47+
src.modules.user.application
48+
src.modules.user.domain
49+
src.modules.user.infrastructure
50+
src.modules.user.presentation

.pre-commit-config.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
repos:
2+
- repo: local
3+
hooks:
4+
- id: import-linter
5+
name: import-linter
6+
entry: poetry run lint-imports
7+
language: system
8+
pass_filenames: false

Makefile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,23 @@ SHELL := /bin/bash
33
PYTHON := .venv/bin/python
44
PYTEST := .venv/bin/pytest
55
RUFF := .venv/bin/ruff
6+
IMPORT_LINTER := .venv/bin/lint-imports
67
UVICORN := .venv/bin/uvicorn
78
ALEMBIC := .venv/bin/alembic
89
POETRY := poetry
910
COMPOSE_FILE := docker-compose.yml
1011

1112
.DEFAULT_GOAL := help
1213

13-
.PHONY: help install run test lint import-check security-scan check migrate seed downgrade revision db-up db-down db-logs clean
14+
.PHONY: help install run test lint lint-imports import-check security-scan check migrate seed downgrade revision db-up db-down db-logs clean
1415

1516
help:
1617
@echo "[make:help] Available commands:"
1718
@echo " [make:install] Install project dependencies with Poetry"
1819
@echo " [make:run] Run the FastAPI development server"
1920
@echo " [make:test] Run pytest"
2021
@echo " [make:lint] Run Ruff checks"
22+
@echo " [make:lint-imports] Enforce import boundary contracts"
2123
@echo " [make:import-check] Verify src.main imports"
2224
@echo " [make:security-scan] Run dependency vulnerability scan with pip-audit"
2325
@echo " [make:check] Run tests, lint, and import check"
@@ -46,6 +48,10 @@ lint:
4648
@echo "[make:lint] Running Ruff checks"
4749
@$(RUFF) check src tests scripts
4850

51+
lint-imports:
52+
@echo "[make:lint-imports] Enforcing import boundary contracts"
53+
@$(IMPORT_LINTER)
54+
4955
import-check:
5056
@echo "[make:import-check] Verifying src.main imports"
5157
@PYTHONDONTWRITEBYTECODE=1 $(PYTHON) -c "import src.main; print('import ok')"
@@ -54,7 +60,7 @@ security-scan:
5460
@echo "[make:security-scan] Running dependency vulnerability scan"
5561
@PIP_CACHE_DIR=.cache/pip $(POETRY) run pip-audit --cache-dir .cache/pip-audit
5662

57-
check: test lint import-check
63+
check: test lint lint-imports import-check
5864
@echo "[make:check] All checks completed"
5965

6066
migrate:

poetry.lock

Lines changed: 142 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,6 @@ dev = [
4242
"mypy (>=2.1.0,<3.0.0)",
4343
"alembic (>=1.18.4,<2.0.0)",
4444
"httpx2 (>=2.4.0,<3.0.0)",
45-
"pip-audit (>=2.10.1,<3.0.0)"
45+
"pip-audit (>=2.10.1,<3.0.0)",
46+
"import-linter (>=2.11,<3.0)"
4647
]

src/core/authorization/domain/service.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
from datetime import datetime
33
from uuid import UUID
44

5-
from src.modules.authorization.domain.entities.permission import Permission
6-
from src.modules.authorization.domain.entities.role import Role
5+
from src.modules.authorization import Permission, Role
76
from src.shared.utils.cursor import CursorDirection
87

98

src/core/authorization/infrastructure/repositories/casbin_policy_repository.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@
2020
from src.core.authorization.infrastructure.models.user_has_role_model import (
2121
UserHasRoleModel,
2222
)
23-
from src.modules.authorization.domain.entities.permission import Permission
24-
from src.modules.authorization.domain.entities.resource import AuthorizationResource
25-
from src.modules.authorization.domain.entities.role import Role
23+
from src.modules.authorization import AuthorizationResource, Permission, Role
2624
from src.shared.utils.cursor import CursorDirection
2725

2826

src/core/authorization/infrastructure/services/casbin_authorization_service.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
SQLAlchemyCasbinPolicyRepository,
77
)
88
from src.core.authorization.permissions import permission_key
9-
from src.modules.authorization.domain.entities.permission import Permission
10-
from src.modules.authorization.domain.entities.role import Role
9+
from src.modules.authorization import Permission, Role
1110
from src.shared.utils.cursor import CursorDirection
1211

1312
CASBIN_MODEL_TEXT = """

src/core/database/unit_of_work.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
from types import TracebackType
2-
from typing import Self
2+
from typing import TYPE_CHECKING, Self
33

44
from sqlalchemy.ext.asyncio import AsyncSession
55

66
from src.shared.unit_of_work import UnitOfWork
77

8+
if TYPE_CHECKING:
9+
from src.modules.todo.domain.repositories.todo_repository import TodoRepository
10+
from src.modules.user.domain.repositories.user_repository import UserRepository
11+
812

913
class SQLAlchemyUnitOfWork(UnitOfWork):
1014
def __init__(self, session: AsyncSession):
@@ -31,3 +35,19 @@ async def commit(self) -> None:
3135

3236
async def rollback(self) -> None:
3337
await self._session.rollback()
38+
39+
@property
40+
def users(self) -> "UserRepository":
41+
from src.modules.user.infrastructure.repositories.user_repository import (
42+
SQLAlchemyUserRepository,
43+
)
44+
45+
return SQLAlchemyUserRepository(self._session)
46+
47+
@property
48+
def todos(self) -> "TodoRepository":
49+
from src.modules.todo.infrastructure.repositories.todo_repository import (
50+
SQLAlchemyTodoRepository,
51+
)
52+
53+
return SQLAlchemyTodoRepository(self._session)

src/core/dependency/providers.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from fastapi import Depends
2+
3+
from src.core.database.postgres.session import get_unit_of_work
4+
from src.modules.user.application.providers import UserModuleProvider
5+
from src.shared.unit_of_work import UnitOfWork
6+
7+
8+
def get_user_module_provider(
9+
uow: UnitOfWork = Depends(get_unit_of_work),
10+
) -> UserModuleProvider:
11+
return UserModuleProvider(user_repository=uow.users)

0 commit comments

Comments
 (0)