This walkthrough uses a small sample repository under
docs/examples/demo-shop-repo/.
Treat the commands below as if you are standing in the root of that package repository.
This page is about learning the workflow, not about tuning the perfect config. The idea is:
- confirm discovery works
- run one structural pass with no config
- add a small config only when you want architecture-aware grouping and policy findings
That same sequence scales to real repositories, but on larger repos you usually analyze only one package or one architectural slice at a time.
The package is a small order-processing toy app with a few intentional import smells:
demo_shop.api.routesimports the private moduledemo_shop.persistence._sqldemo_shop.domain.modelscontains a lazy import from persistencedemo_shop.services.notificationscontains a lazy import from infra
Key files:
.
├── pyproject.toml
├── src/
│ └── demo_shop/
│ ├── __init__.py
│ ├── app.py
│ ├── cli.py
│ ├── container.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ └── schemas.py
│ ├── domain/
│ │ ├── __init__.py
│ │ ├── errors.py
│ │ └── models.py
│ ├── infra/
│ │ ├── __init__.py
│ │ ├── email.py
│ │ └── payment.py
│ ├── persistence/
│ │ ├── __init__.py
│ │ ├── _sql.py
│ │ └── repository.py
│ ├── plugins/
│ │ ├── __init__.py
│ │ └── discounts.py
│ └── services/
│ ├── __init__.py
│ ├── notifications.py
│ ├── orders.py
│ └── pricing.py
└── tests/
└── test_smoke.py
importscope checkExpected output:
Profile: overview
Modules: 22
Edges: 37
No problems detected.
Before adding .importscope.yml, run one structural pass first.
importscope analyze \
--graph module-layout \
--out .cache/importscopeExpected output:
Wrote outputs to: /path/to/demo-shop-repo/.cache/importscope
Modules parsed: 23
Internal import edges: 38
Cycles detected: 1
Policy findings: 0
This first pass is enough to start exploring the package layout:
The default overview profile can already emit a small structural bundle even
without a config:
importscope analyze --out .cache/importscopeTypical files:
module_layout_graph.svggroup_dependency_graph.svgmodule_dependency_graph.svgarchitecture_report.md
Without .importscope.yml, the run stays structural:
- no policy findings are reported
- the grouped graphs use the default package/other grouping
- discovery excludes from the repo-specific config are not applied yet
This is the right first move on an unfamiliar repo because it answers the basic questions first:
- did
importscopediscover the package you expected? - is the repo small enough for a whole-package view?
- do you already see suspicious edges or one oversized cluster that needs better grouping?
Now generate a starter file:
importscope init --stdoutExpected output:
tool:
default_profile: overview
discovery:
exclude:
- .venv/**
- venv/**
- .tox/**
- build/**
- dist/**
- .git/**
policy:
enabled: falseThat starter config is intentionally small. For this demo repo, the checked-in config used below is generated by:
importscope init
importscope config package set demo_shop
importscope config exclude add 'docs/**' 'tests/**'
importscope config policy enable
importscope config module-group-depth set 3
importscope config forbid add demo_shop.domain demo_shop.persistence
importscope config forbid add demo_shop.api demo_shop.persistence._
importscope config forbid add demo_shop.services demo_shop.persistence._Resulting config excerpt:
tool:
default_profile: overview
discovery:
exclude:
- .venv/**
- venv/**
- .tox/**
- build/**
- dist/**
- .git/**
- docs/**
- tests/**
policy:
enabled: true
module_group_depth: 3
forbidden_imports:
- source_prefixes:
- demo_shop.domain
target_prefixes:
- demo_shop.persistence
- source_prefixes:
- demo_shop.api
target_prefixes:
- demo_shop.persistence._
- source_prefixes:
- demo_shop.services
target_prefixes:
- demo_shop.persistence._
package: demo_shopThis config does three useful things:
- narrows discovery so the counts match the intended package slice
- keeps grouping and colors automatic
- enables a small set of policy rules so findings become reviewable
profiles lists the output bundles available for the current repo and config.
In this demo repo, adding .importscope.yml does not rename or add profiles,
because the config mainly defines grouping and policy rules, not custom profile
definitions.
importscope profilesExpected output:
all: All graph families and reports.
overview: Repo overview with grouped dependency graphs and summary report.
policy: Policy-focused violation and helper cleanup outputs.
symbols: Comprehensive symbol import graph plus summary.
If you define your own outputs.profiles in .importscope.yml, this command
will reflect them.
With the repo config in place, you can ask for richer outputs:
importscope analyze \
--config .importscope.yml \
--profile all \
--graph package-dependency \
--graph symbol-labeled-module-dependency \
--graph symbol-import \
--policy \
--out .cache/importscope/full \
--format svg --format md --format jsonExpected output:
Wrote outputs to: /path/to/demo-shop-repo/.cache/importscope/full
Modules parsed: 22
Internal import edges: 37
Cycles detected: 1
Policy findings: 2
Generated files:
module_layout_graph.svggroup_dependency_graph.svgmodule_dependency_with_symbols.svgsymbol_import_graph.svgarchitecture_report.mdanalysis_snapshot.json
Compared with the no-config run, this configured run gives you:
- policy-aware edge classification on top of the structural graphs
- auto-derived grouping and coloring for the package layout
- the repo-specific discovery excludes, so the counts drop from
23/38to22/37 - symbol-labeled module edges
- the comprehensive symbol import graph
- a JSON snapshot for
inspect
This is the full learning loop on the sample repo:
- no-config run for discovery and rough structure
- config-backed run for architectural interpretation
inspecton the saved snapshot when you want one concrete path or edge
If you want the path rendered back onto a graph instead of only as JSON:
importscope inspect \
path demo_shop demo_shop.persistence._sql \
--highlight \
--graph-format bothThat writes a highlighted module path graph alongside the normal outputs.
Use the same workflow, but change the scope:
- small package repo: analyze the whole package first, as done here
- larger multi-package repo: pass
--packageto isolate one package - noisy repo with one specific question: keep the package, then exclude unrelated directories in config
- dense repo with too many internals in one box: keep the same scope, then refine
module_group_depth
The real-repo examples show each of those patterns on different repositories.
Architecture report excerpt:
# Import graph report
- Modules parsed: `22`
- Internal import edges: `37`
- Lazy local edges: `2`
- Strongly connected components >1: `1`
- Private import edges: `3`
- Cross-area private import edges: `0`
- Boundary/helper cleanup candidate edges: `1`
## Findings summary
- `boundary_helper`: `1`
- `forbidden_import`: `1`Package-level dependency graph:
Module dependency graph with symbols:
Comprehensive symbol import graph:
Inspect one path:
importscope inspect \
--snapshot .cache/importscope/full/analysis_snapshot.json \
path demo_shop.api.routes demo_shop.infra.emailExpected output:
{
"mode": "path",
"paths": [
[
"demo_shop.api.routes",
"demo_shop.services.orders",
"demo_shop.services.notifications",
"demo_shop.infra.email"
]
]
}Inspect the private import edge:
importscope inspect \
--snapshot .cache/importscope/full/analysis_snapshot.json \
edge demo_shop.api.routes demo_shop.persistence._sqlExpected output:
{
"matches": [
{
"imported": ["quote_identifier"],
"lazy_kind": "eager",
"line": 10,
"source": "demo_shop.api.routes",
"target": "demo_shop.persistence._sql"
}
],
"mode": "edge"
}