Skip to content

refactor: enforce DDD layering with import-linter - #27

Merged
TeKrop merged 1 commit into
mainfrom
refactor/import-linter
Sep 15, 2026
Merged

TeKrop merged 1 commit into
mainfrom
refactor/import-linter

Conversation

@TeKrop

@TeKrop TeKrop commented Sep 15, 2026 •

Copy link
Copy Markdown
Owner

Summary

Enforces the hexagonal layering (main → graphql | adapters → logging_config → settings → domain) with import-linter, mirroring TeKrop/overfast-api#459. The current code already satisfies every contract: no app code changes.

  • import-linter contracts in [tool.importlinter] (pyproject.toml):
    • layers (exhaustive): graphql and adapters are independent siblings, so only main imports the concrete adapter; domain sits at the bottom and imports nothing else from app.
    • domain-pure: app.domain can't import strawberry, starlette, httpx2, cachetools or pydantic_settings.
    • acyclic: no import cycles between sibling modules.
  • Not ported from overfast-api: composition-root (covered by layers), adapters-independent (single adapter), services-independent (no services), domain-layers (3-file domain).
  • import-linter==2.15.* dev dependency; lint-imports runs in just lint and CI.
  • Docs (CLAUDE.md, README.md) updated to match.

Test plan

  • uv run lint-imports: 3 kept, 0 broken
  • Each contract fails when broken on purpose (graphql → adapters, domain → httpx2, domain → settings, unlisted top-level module)
  • ruff check, ty check
  • just test: 54 passed

🤖 Generated with Claude Code

Summary by Sourcery

Enforce the application’s hexagonal dependency boundaries with import-linter and include the checks in local linting and CI.

Enhancements:

  • Enforce the application’s inward-only hexagonal architecture with exhaustive layering, domain purity, and acyclic dependency contracts.

Build:

  • Add import-linter as a development dependency and configure the architecture contracts in pyproject.toml.

CI:

  • Run import-linter as part of the CI checks.

Documentation:

  • Document the enforced application layers and import-linter checks in the contributor and user documentation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR introduces import-linter contracts that enforce the app’s hexagonal layering, framework-independent domain, and acyclic dependencies, then wires the checks into local linting and CI; application behavior is unchanged.

Flow diagram for import-linter enforcement in development and CI

flowchart LR
    code[App imports]
    lint[lint-imports]
    contracts[Import-linter contracts]
    result[Pass or fail]

    code --> lint
    lint --> contracts
    contracts --> result
Loading

File-Level Changes

Change Details Files
Adds import-linter architecture contracts to enforce inward-only hexagonal dependencies and domain purity.
  • Defines exhaustive layer ordering with independent GraphQL and adapter siblings.
  • Forbids framework and I/O dependencies from the domain package.
  • Adds an acyclic-siblings contract for the app package.
pyproject.toml
Integrates import-linter into local and CI validation.
  • Adds the pinned development dependency and lockfile entries.
  • Runs import-linter from the lint recipe and CI workflow.
justfile
.github/workflows/build.yml
uv.lock
Updates project documentation to describe the enforced architecture and lint command.
  • Documents the dependency flow and contract implications for new app modules.
  • Updates lint command descriptions to include import-linter.
CLAUDE.md
README.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="justfile" line_range="34-35" />
<code_context>
 lint:
     @echo "Running linter..."
     uv run ruff check --fix --exit-non-zero-on-fix
+    uv run lint-imports

 # run formatter
</code_context>
<issue_to_address>
**nitpick (bug_risk):** `just lint` does not run `lint-imports` whenever Ruff applies an autofix, because the preceding `ruff check --fix --exit-non-zero-on-fix` command exits nonzero and stops the recipe before reaching this line.

**Triggers:** When the tree contains any Ruff issue that is automatically fixable.

**Suggested fix:** Run the import-linter command independently of Ruff's fix status, for example by separating the commands with a shell construct that preserves execution while still returning Ruff's failure status.

```suggestion
    uv run ruff check --fix --exit-non-zero-on-fix || (ruff_status=$?; uv run lint-imports; exit $ruff_status)
    uv run lint-imports
```
</issue_to_address>

Sourcery assessment

Approved.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread justfile
Comment on lines 34 to +35
uv run ruff check --fix --exit-non-zero-on-fix
uv run lint-imports

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (bug_risk): just lint does not run lint-imports whenever Ruff applies an autofix, because the preceding ruff check --fix --exit-non-zero-on-fix command exits nonzero and stops the recipe before reaching this line.

Triggers: When the tree contains any Ruff issue that is automatically fixable.

Suggested fix: Run the import-linter command independently of Ruff's fix status, for example by separating the commands with a shell construct that preserves execution while still returning Ruff's failure status.

Suggested change
uv run ruff check --fix --exit-non-zero-on-fix
uv run lint-imports
uv run ruff check --fix --exit-non-zero-on-fix || (ruff_status=$?; uv run lint-imports; exit $ruff_status)
uv run lint-imports

@TeKrop
TeKrop merged commit c874d97 into main Sep 15, 2026
4 checks passed
@TeKrop
TeKrop deleted the refactor/import-linter branch September 15, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant