The include and exclude keys control which directories and .py files psl walks. They are top-level config keys and are applied before any rule matching. Each rule then scopes itself with its own within (or source/mirror) pattern; include and exclude narrow the pool of paths those patterns are allowed to match.
include:
- src
exclude:
- "**/__pycache__/**"
rules:
- name: context-layers
type: directory-exists
description: Each context has domain, application, and adapters layers
within: src/contexts/{ctx}
require: [domain, application, adapters]
Both keys accept a list of project-root-relative glob patterns, using the same segment tokens as within (*, **, {name}). They filter every pathmatch-based rule at once: the directory rules (directory-exists, allowed-children) and the file rule (symbol-count).
mirror is the exception to include. Its two trees are named explicitly by source and mirror, so include never applies to it — otherwise include: [src] would erase the tests tree and disable the rule. exclude still applies to both sides of a mirror.
Scenarios¶
No include or exclude¶
When neither key is present, psl walks the entire project root: every directory is a candidate for the directory rules, and every .py file is a candidate for symbol-count.
rules:
- name: one-class-per-domain-module
type: symbol-count
description: Domain modules define a single class
within: src/contexts/{ctx}/domain/**
symbol: class
max: 1
ignore_files: [__init__.py]
Every directory and .py file in the project is a candidate. Each rule's own within/source/mirror pattern then decides which of those paths it actually checks — here, only .py files under src/contexts/{ctx}/domain/.
Only include¶
When only include is specified, only paths under a listed root are candidates. Everything else is invisible to every tree-walking rule.
include:
- src
rules:
- name: domain-components
type: allowed-children
description: The domain layer holds only recognized component directories
within: src/contexts/{ctx}/domain
kind: dir
group_level: optional
allow: [entities, value_objects, events, services, errors]
ignore: [__init__.py]
Only directories and files under src/ are scanned. Anything in tests/, scripts/, or another top-level tree is never checked, even if a rule's within would otherwise match it. An include entry is treated both as a root prefix and as a glob, so src and src/** both include everything under src/.
Use include when your project has several top-level trees and you only want the source one linted.
Only exclude¶
When only exclude is specified, psl scans the whole project root except the paths matching an excluded pattern. Exclusion is segment-aware, and the pattern shape decides its reach:
exclude:
- __pycache__
- tests/**
- "**/migrations/**"
rules:
- name: context-layers
type: directory-exists
description: Each context has domain, application, and adapters layers
within: src/contexts/{ctx}
require: [domain, application, adapters]
Each pattern above behaves differently:
| Pattern | Reach |
|---|---|
__pycache__ |
A bare name — no separator, no wildcard — matches that segment anywhere in the tree and drops the directory and its subtree wherever it appears. |
tests/** |
A rooted pattern stays rooted at the project root. It drops everything under the top-level tests/, but does not leak onto a nested src/contexts/orders/tests. |
**/migrations/** |
Targets the contents of any migrations/ directory, and — because a trailing /** (or /*) is stripped to also test the container — drops the migrations/ directory itself too. |
This is useful when you want broad coverage but need to skip caches, generated code, fixtures, or a whole tooling tree.
Both include and exclude¶
When both keys are present, include is applied first and exclude is applied to the result.
include:
- src
exclude:
- "**/__pycache__/**"
rules:
- name: one-class-per-domain-module
type: symbol-count
description: Domain modules define a single class
within: src/contexts/{ctx}/domain/**
symbol: class
max: 1
ignore_files: [__init__.py]
Step-by-step:
- Start with every directory and
.pyfile under the project root. - Keep only paths under
src/(applyinclude). - Drop any
__pycache__/directory and its contents (applyexclude).
The result is everything under src/ except cache directories — for example src/contexts/orders/domain/entities/order.py is scanned, while src/contexts/orders/domain/__pycache__/order.pyc never is.
Summary¶
include |
exclude |
Paths scanned |
|---|---|---|
| Not set | Not set | All directories and .py files under the project root |
| Set | Not set | Only paths under an include root |
| Not set | Set | All paths except those matching an exclude pattern |
| Set | Set | Paths under include, then filtered by exclude |
Exclude pattern shapes:
| Shape | Example | Effect |
|---|---|---|
| Bare name | __pycache__ |
Drops that segment and its subtree anywhere in the tree |
| Rooted glob | tests/** |
Stays rooted at the project root; does not match a nested same-named directory |
| Wrapped glob | **/x/** |
Drops the contents of any x/ and the x/ directory itself |
include applies to directory-exists, allowed-children, and symbol-count; exclude applies to those and to both trees of mirror. mirror ignores include because it names its source and mirror roots explicitly.