Purpose¶
Real projects have distinct layers — domain, infrastructure, API — each with its own naming conventions. Instead of applying every rule globally, you can scope each rule set to the layer where it belongs, reducing false positives and making the intent of each rule explicit.
Configuration¶
rules:
- name: attribute-matches-type
type: variable
filter: { target: attribute }
naming: { source: type_annotation, transform: snake_case }
- name: bool-method-prefix
type: function
filter: { return_type: bool }
naming: { prefix: [is_, has_, should_] }
- name: domain-module-naming
type: module
naming: { source: class_name, transform: snake_case }
- name: constant-upper-case
type: variable
filter: { target: constant }
naming: { case: UPPER_CASE }
- name: exception-naming
type: class
filter: { base_class: Exception }
naming: { regex: "^[A-Z][a-zA-Z]+(NotFound|Invalid|Denied|Conflict|Failed)Error$" }
apply:
- name: domain-layer
rules:
- attribute-matches-type
- bool-method-prefix
- domain-module-naming
- constant-upper-case
modules: contexts.*.domain
- name: global-exceptions
rules: [exception-naming]
modules: "**"
The domain-layer apply block targets every contexts/<context>/domain package, while global-exceptions runs the exception naming rule across the entire codebase.
Violation Example¶
# contexts/billing/domain/service.py
max_retry = 3 # constant not in UPPER_CASE
class BillingService:
def validate(self) -> bool: # bool method missing prefix
return self._status == "active"
# contexts/billing/domain/exceptions.py
class BillingError(Exception): # exception missing semantic suffix
pass
Passing Example¶
# contexts/billing/domain/service.py
MAX_RETRY = 3
class BillingService:
def is_valid(self) -> bool:
return self._status == "active"
# contexts/billing/domain/exceptions.py
class BillingNotFoundError(Exception):
pass
Output¶
$ pnl check
contexts/billing/domain/service.py:3
[constant-upper-case] max_retry (expected case: UPPER_CASE)
contexts/billing/domain/service.py:6
[bool-method-prefix] validate (expected prefix: is_ | has_ | should_)
contexts/billing/domain/exceptions.py:3
[exception-naming] BillingError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$)
Found 3 violation(s).