Skip to content

Configuration

Configuration lives in a [tool.curfew] table in pyproject.toml (uv-native). An optional standalone curfew.toml is deep-merged on top of it (top-level keys, no [tool.curfew] prefix).

[tool.curfew]
source_roots = ["src"]      # applies to the workspace root package
default_deny = false        # true => every first-party module must have a rule
known_first_party = []      # escape hatch for code generated outside source roots
ignore_modules = []         # import names never flagged as unresolved

[tool.curfew.modules."rwa_calc.core"]
depends_on = []                    # leaf — may import nothing first-party
interface = ["api"]                # only rwa_calc.core.api is public
interface_enforced = true          # defaults to true when an interface is declared

[tool.curfew.modules."rwa_calc.io"]
depends_on    = ["rwa_calc.core", "rwa_calc.types"]
deprecated_on = ["rwa_calc.legacy"]   # allowed but reported as a warning (burndown)

[tool.curfew.external]
ignore = ["setuptools"]            # never flag as undeclared
ignore_unused = []                 # never flag as unused

[tool.curfew.check]
boundaries = true
externals = true

Module patterns

A pattern matches a module by prefix, guarded at a dot boundary so rwa_calc.io never matches rwa_calc.iolib:

Pattern Matches
pkg.core pkg.core and everything under it (subtree)
pkg.core.* same as above (explicit subtree sugar)
pkg.core! only pkg.core exactly

When several rules match, the longest prefix wins (an exact rule beats a subtree rule on a tie).

Boundary semantics

  • A module that has a rule is default-deny: it may import only its depends_on targets plus its own subtree.
  • A module with no rule is unrestricted — so you can adopt curfew incrementally. Set default_deny = true to require a rule on every first-party module (ungoverned modules may then only import within their own package).
  • A public interface lists the names a package exposes. Importing a strict descendant that isn't in the interface — or a from pkg import name whose name isn't listed — is a bypass (error).
  • A deprecated dependency is allowed but reported as a warning, so you can burn it down over time.

A note on curfew dogfooding itself

curfew's own pyproject.toml carries a [tool.curfew] block that documents and enforces its layering: model and errors are leaves; the engine layers build up; cli is the orchestrator. curfew check runs against curfew in CI.