Skip to content
Agentmart
Submit a tool

setup-python-deep-modules

Wire import-linter into a Python repo so each package is a deep module, with implementation hidden in subfolders and reachable only through its entry-point…

Skill by fderuiter

IndexedMIT★ 1
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/fderuiter-setup-python-deep-modules

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Wire import-linter into a Python repo so each package is a deep module, with implementation hidden in subfolders and reachable only through its entry-point files. User-invoked.

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/setup-python-deep-modules

What's inside

Skill name
setup-python-deep-modules
Description
Wire import-linter into a Python repo so each package is a deep module, with implementation hidden in subfolders and reachable only through its entry-point files. User-invoked.
License
MIT
Instructions
141 lines in SKILL.md · view on GitHub

Files (2)

  • SKILL.md
  • importlinter.config.ini

SKILL.md (first lines)

# Setup Python Deep Modules

Make every package in this Python repo a **deep module**: a lot of behaviour behind a small interface. A package's public surface is its **entry points** (root modules such as `__init__.py` and root files), while everything in its subfolders (such as `_lib/` or `lib/`) is hidden. This skill installs [import-linter](https://import-linter.readthedocs.io/) and the contracts that make entry points the only way in, then proves the rules bite.

For vocabulary (deep module, interface, seam, depth), call the Skill tool with "codebase-design" and use its language throughout.

## The shape this enforces

```
src/packages/
  <name>/
    __init__.py     ← primary entry point (public). Import this from outside.
    client.py       ← additional entry point (public). Packages may expose several.
    _lib/           ← implementation: hidden from outside, free to import each other.
    tests/          ← co-located tests + fixtures (private subfolder).
```

The public surface is the package's **root files**, not just one `__init__.py`. By convention implementation lives in `_lib/` (or `lib/`) and tests in `tests/`, giving every package the same clean shape. The rule itself is path-depth based: anything in subfolders is private, so you never extend the config to add a folder.

Five contracts, all enforced as errors:

1. **Entry-point boundary from app**: code outside a package (app code or root services) may import only that package's entry points (root modules), never anything in its subfolders.
2. **Entry-point boundary across packages**: a package's own internal files import each other freely, but may reach other packages only through their entry points, never their subfolder internals.
3. **Tests through the entry points**: files under `<pkg>/tests/` may import any package's entry points and their own `tests/` fixtures, but never any package's subfolder internals (not even their own). Integration tests across packages are fine; deep imports are not.
4. **Tests folder is private**: files under `<pkg>/tests/` are reachable only from test files; application code may not import test fixtures or helpers.
5. **No cycles**: no circular dependencies across packages.

**Entry points, not giant monolithic files.** Because the public surface is *every* root file, a package can expose several small entry points (`__init__.py`, `client.py`, `models.py`) instead of funnelling everything into one giant file. Keep entry points lean and hide complex implementation in `_lib/`.

## Steps

### 1. Detect the environment

- **Package manager and environment**:
  - `uv.lock` or `pyproject.toml` with `[tool.uv]` → `uv` (`uv add --dev import-linter`, `uv run lint-imports`).
  - `poetry.lock` → `poetry` (`poetry add --group dev import-linter`, `poetry run lint-imports`).
  - `pdm.lock` → `pdm` (`pdm add -d import-linter`, `pdm run lint-imports`).
  - `Pipfile.lock` → `pipenv`.
  - Fallback: standard `pip` / `venv` (`pip install import-linter`).
- **Packages root**:
  - If `src/packages/` exists use `src/packages/`, else `packages/` or `src/`. Confirm the choice with the user if the repo has an established convention.
- **Existing config**:
  - Check for existing `[tool.importlinter]` in `pyproject.toml` or a `.importlinter` file. If present, do **not** overwrite; merge the five contracts in.
- **Upstream verification**:
  - When encountering non-standard package layouts or workspace monorepos, use `search_web` and `read_url_content` to inspect upstream `import-linter` docs for custom contract syntax.

**Done when:** package manager, packages root, config location, and existing contract status are all known.

### 2. Install import-linter

Install `import-linter` as a development dependency using the detected package manager.

**Done when:** `import-linter` is installed and executable via `lint-imports --version`.

### 3. Write the config

If `pyproject.toml` exists, add the `[tool.importlinter]` configuration:

```toml
[tool.importlinter]
root_packages = ["packages"]

[[tool.importlinter.contracts]]
name = "Entry point boundary from app"
type = "forbidden"
source_modules = ["app"]
forbidden_modules = ["packages.*._lib", "packages.*.lib"]

[[tool.importlinter.contracts]]
name = "Entry point boundary across packages"
type = "forbidden"
source_modules = ["packages"]
forbidden_modules = ["packages.*._lib", "packages.*.lib"]
ignore_imports = [
    "packages.$1._lib -> packages.$1._lib",
    "packages.$1.lib -> packages.$1.lib"
]

[[tool.importlinter.contracts]]
name = "Tests through entry points"
type = "forbidden"
source_modules = ["packages.*.tests"]
forbidden_modules = ["packages.*._lib", "packages.*.lib"]

[[tool.importlinter.contracts]]
name = "Tests folder is private"
type = "forbidden"
source_modules = ["app", "packages"]
forbidden_modules = ["packages.*.tests"]
ignore_imports = ["packages.$1.tests -> packages.$1.tests"]

[[tool.importlinter.contracts]]
name = "No cycles"
type = "independence"
modules = ["packages"]
```

If the repo prefers standalone configuration, copy [`importlinter.config.ini`](./importlinter.config.ini) to `.importlinter`.

**Done when:** configuration exists with the correct `root_packages` and all five architectural contracts.

### 4. Wire it into the checks

- Add a `lint:boundaries` or `lint-imports` task in `pyproject.toml` / `Makefile` / `tox.ini` / task runner.
- Fold it into the repo's umbrella check command (e.g. `check`, `lint`, or `ci`).
- If there is no umbrella script, add `lint-imports` to CI workflows.

**Done when:** `lint-imports` runs as part of standard developer checks and CI.

### 5. Scaffold the example package

Create `<packages-root>/example/` as a starter template:

- `__init__.py`: Public entry point. Expose one function that delegates to an internal module (demonstrating that the package is visibly deep).
- `_lib/impl.py`: Internal implementation file in a subfolder, imported by `__init__.py`, hidden from external code.
- `tests/test_example.py`: Tests importing only `from packages.example import ...`, asserting against the public interface.

**Done when:** the example package exists, exposes its behavior through the root module, and hides implementation in `_lib/`.

From fderuiter/agy-skills, MIT.

More from fderuiter/agy-skills

Signals

Stars
1
Forks
0
Open issues
0
Last commit (this folder)
Aug 28, 2026 (2 months ago)
Last release
Aug 28, 2026 (2 months ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from fderuiter

Cite this page

setup-python-deep-modules skill. Agent Mart. https://agentmart.dev/skills/fderuiter-setup-python-deep-modules (last updated 2026-10-24)