Skip to content

Configuration reference

Every [tool.pitloom] setting, its default, and how to reach it from each surface (CLI flag, GitHub Action input, Python API parameter). This is the exhaustive reference; Command line and GitHub Action have narrative walkthroughs with worked examples for the settings people reach for most (creator and creation metadata).

A CLI flag or API parameter of None (its default, when omitted) always defers to [tool.pitloom] where one applies to the target -- see Where settings come from below, since not every target has one. Passing an explicit value overrides it for that run only. A GitHub Action input of "" (empty, its default) defers the same way.

Where settings come from

Precedence, on every surface: a per-run flag/parameter wins over --config FILE (pitloom_config=), which wins over the target's own [tool.pitloom] (where one applies), which wins over the hardcoded default. --config/pitloom_config= replaces the target's own config outright rather than merging with it -- a key the given file doesn't set reverts to the default, not to the target's own value. The replaced config is not parsed at all, so an invalid [tool.pitloom] in the target does not fail a run given --config.

Only a project directory, an sdist archive, an embed-wheel --project-dir, and the Hatchling build hook read a target's own [tool.pitloom]. A wheel, an installed environment, a model file, a Hugging Face model, enrich without --project-dir, and embed-wheel without --project-dir never read one -- not the current directory's, and not one beside the target -- either could belong to an unrelated project; --config/pitloom_config= is the only config they can get.

An sdist archive's own config is read as its unpacked directory's is: the [tool.pitloom] of the pyproject.toml at the archive's root, else the [tool:pitloom] of its root setup.cfg. An invalid one fails the run, as a directory's does; so does one that cannot be read (not valid TOML, not UTF-8, or over 1 MiB -- a limit for archive members only). The error names the archive and member, e.g. config file dist/demo-1.0.0.tar.gz:pyproject.toml: ...; --config replaces it without reading it. Some keys cannot apply to an archive and are ignored without a warning:

  • id-registry (it could only name a file inside the archive) and [tool.pitloom.fragment] (fragments merge only into a project directory's SBOM, even from --config);
  • use-lockfile, enrich, extract-file-header, scan-model-usage and [tool.pitloom.content-type] -- the settings whose flags warn for an sdist (see Options with no effect).
Surface Target's own config --config / pitloom_config= Current directory Flags
project/generate (project dir) -- generate_project_sbom() Read Replaces it Never read Override
project/generate (sdist archive) -- generate_project_sbom() Read (root pyproject.toml, else setup.cfg; not id-registry/fragments) Replaces it Never read Override
wheel/generate (.whl) -- generate_wheel_sbom() Never read Only config source Never read Override
env/generate env -- generate_env_sbom() Never read Only config source Never read Override
model (local file)/generate -- generate_model_sbom() Never read Only config source Never read Override
model (Hugging Face) -- generate_model_sbom() Never read Only config source Never read Override
enrich (no --project-dir) -- enrich_model() Never read Only config source Never read Override
enrich --project-dir D -- enrich_model(project_target=D) Read, for document identity (use-lockfile) and the registry (id-registry) only; for an sdist D neither applies, but an invalid config still fails the run Replaces D's own Never read Override
embed-wheel --project-dir D -- embed_wheel_sbom(project_dir=D) Read Replaces it Never read Override
embed-wheel (no --project-dir) -- embed_wheel_sbom() Never read Only config source Never read Override
embed-wheel --sbom -- embed_wheel_sbom(sbom_path=...) Not read Not read (--config warns, no effect) Never read Embedding flags only (--sbom-basename, -o, --verify, ...); every SBOM-generation flag warns, since the file is embedded as is
Hatchling build hook Read (always, at build time) No equivalent -- the hook has no per-run override surface Never read None -- no per-run surface
GitHub Action Project and embed-wheel modes read it (--project-dir under the hood); model mode never config input maps to --config Never read Every other input maps to a flag

A relative path inside a --config file (id-registry, a fragment's path) resolves against that file's own directory, not the current directory or a symlink's target -- as a project's own pyproject.toml does. loom fragment list reads only the project's own config. A key a target cannot use is ignored without a warning, since one config often serves several commands: fragments merge only into a project directory's SBOM (project, generate <dir>, embed-wheel --project-dir), and an SBOM embedded in a wheel is always compact. The matching flags do warn -- see Options with no effect. A relative --id-registry on the command line resolves against the current directory, on every command -- unlike a target's own id-registry, which is project-relative.

See Options with no effect for which flags a target given for the wrong kind warns about instead of silently doing nothing.

[tool.pitloom]

A key Pitloom does not know -- in [tool.pitloom], one of its sub-tables or an entry of creator, creation-tool, content-type.override or fragment.files -- is ignored with one WARNING: naming the file, the table and the key, and either the table that does know it (it belongs in [tool.pitloom.provenance]) or the closest known key (ofline: did you mean 'offline'?). It is not an error, and warns once per file per run, on every source: pyproject.toml, setup.cfg, an sdist's own config (named archive:member), --config and the Hatchling hook. It is given before the values are checked, so a misspelt required key shows its hint ahead of the error. A setup.cfg [DEFAULT] key is not checked: a [tool:pitloom...] section inherits it only when the key is one that section reads, and [tool:pitloom:content-type:override] never does. Not checked: a misspelt [tool] table ([tool.pitlom]), setup.cfg sub-section ([tool:pitloom:provenence]) and the hook's own [tool.hatch.build.hooks.pitloom] options.

Key Type Default CLI flag Action input API param Meaning
pretty bool false --pretty / --no-pretty pretty pretty Indent the JSON output with 2 spaces (whitespace only; key order stays RFC 8785). Not used for an SBOM embedded in a wheel (embed-wheel, wheel --embed, including its -o copy), which is always compact; the flags warn there.
describe-relationship bool false --describe-relationship / --no-describe-relationship -- describe_relationship Include human-readable text on SPDX relationships. Not used for an SBOM embedded in a wheel, as for pretty.
sbom-basename string (derived from project name/version) -- -- sbom_basename Base filename for the generated SBOM; .spdx3.json is added. The extension is optional: one trailing .spdx3.json (any case) is dropped with a WARNING:, so x.spdx3.json gives x.spdx3.json.
offline bool false --offline offline offline Skip the PyPI JSON API fallback used to fill dependency metadata gaps. Network attempted, best-effort, by default -- any failure (including no network) silently falls back to local-only data.
use-lockfile bool true --use-lockfile / --no-use-lockfile use-lockfile use_lockfile Resolve exact versions from a lock/pin file cascade (pylock.toml/uv.lock/poetry.lock/pdm.lock/Pipfile.lock/pinned requirements.txt) -- see Dependency sources and precedence. On by default, unlike every other bool above; false falls back to direct dependencies and environment introspection only. CLI flag only on project/generate (dependency resolution) and enrich (--project-dir document identity matching); no effect on model/wheel/embed-wheel/env.
extract-file-header bool true --extract-file-header / --no-extract-file-header extract-file-header extract_file_header Scan each source file's leading comment header for SPDX-File* tags. Independent of content-type detection below -- a binary file with no text header still gets a contentType when that's on.
scan-model-usage bool false (unset) --scan-model-usage / --no-scan-model-usage scan-model-usage scan_model_usage Record which Python files reference each discovered AI model file (hasDataFile); off by default because it reads every Python file. Where it applies, and the INFO: given when it is never set: --scan-model-usage; the flags warn on other targets. Its caps: AI model scan limits.
max-model-extract-bytes positive integer 536870912 (512 MiB) -- -- -- Largest AI model file copied out of a wheel to be read; one wheel's models together may use four times this. What happens over either: Size and count caps. Zero, negative or not an integer is an error, not "unlimited". Wheel targets only (wheel, wheel --embed, embed-wheel without --project-dir), from --config/pitloom_config= (a library PitloomConfig value is checked on a wheel target and named as pitloom_config in the error); a project directory is read in place and ignores it, but a bad value in a project's own [tool.pitloom] is still an error, as the table is validated whenever it is read.
enrich bool false --enrich / --no-enrich enrich enrich Run local README/model-card enrichment for discovered AI models.
id-registry string null: no registry; nothing is searched --id-registry -- id_registry Path to the Loom ID registry file, relative to the project directory (or to the --config file's own directory, when set there). Used only when declared here, via --id-registry, or via a --config file -- never auto-discovered. loom.Run is the one exception: it never reads any [tool.pitloom] config at all, so it only ever takes its own id_registry= kwarg, resolved relative to the current directory. A declared file that's missing, unreadable or invalid raises ValueError (CLI: ERROR: and exit 1; hook: fails the build).
update-id-registry bool true -- from the target's own [tool.pitloom] where one applies (see Where settings come from), else from --config/pitloom_config=, else the default --update-id-registry / --no-update-id-registry -- update_id_registry After generating, harvest newly-minted ids back into a declared registry and save it -- never creates one. Effective on project/wheel/env/generate; given for model/enrich/embed-wheel/wheel --embed it warns WARNING: Options: ... has no effect and is dropped. No effect when no registry is declared -- see Loom ID registry.

Invalid values / fallback behaviour: every boolean above raises ValueError at config-read time if set to a non-boolean (e.g. the TOML string "true" instead of the bare value true) -- no silent coercion. sbom-basename/id-registry raise ValueError if set to a non-string, and sbom-basename also if it is a path rather than a file name (a /, \, : or NUL, or ./..), or is only .spdx3.json -- the same rules as embed-wheel --sbom-basename. extract-file-header off never errors and never blocks content-type detection -- see below.

In setup.cfg's [tool:pitloom], where every value is text, a boolean is true/false, yes/no or 1/0 (any case) and an integer is base 10; any other value, or an empty one, raises ValueError as above.

Moved keys: the pre-rename ids-file and update-registry keys raise ValueError if present -- e.g. [tool.pitloom] 'ids-file' has moved to 'id-registry'. Update your config. -- rather than being read under their old names.

[tool.pitloom.content-type]

Content-type detection (magika/filename-extension) is independent of extract-file-header above -- both are opt-in, gated separately, because they have different cost profiles and apply to different kinds of files (a header only exists in text source; a content type applies to every file, text or binary).

Key Type Default CLI flag Action input API param Meaning
enabled bool false --content-type / --no-content-type content-type content_type Detect each file's real IANA media type. Off by default -- magika inference is a real per-file cost (~5ms/file).
method "auto" | "magika" | "extension" "auto" --content-type-method content-type-method content_type_method Which detector resolves a value: "auto" tries magika, falling back to a filename-extension guess when magika isn't installed or its result is inconclusive; "magika" behaves identically per-file but raises immediately if the package isn't installed at all; "extension" skips magika entirely.

Invalid values / fallback behaviour: enabled non-boolean raises ValueError at config-read time. method not one of the three listed values raises ValueError at config-read time. method = "magika" with the magika package not installed raises RuntimeError at generation time, before any file is scanned -- you asked for magika specifically, so this fails loudly rather than silently degrading every file's contentType the way "auto" would. "auto"/"extension" never raise for a missing/inconclusive detector; they just resolve to None for that file. Overrides below only ever apply while enabled is true -- with it false, no file gets a contentType at all, configured overrides or not.

[[tool.pitloom.content-type.override]]

A deterministic, config-asserted contentType for files matching a glob pattern -- pre-empts detection for that file entirely (no magika/extension guess runs). Config-only: no CLI flag, Action input, or API parameter, since a glob-to-MIME-type mapping doesn't fit a scalar flag; a caller who wants this programmatically constructs their own PitloomConfig.

Key Type Meaning
pattern string A shell-glob (fnmatch.fnmatchcase, case-sensitive on every platform) matched against the file's distribution_path. * matches / too, so vendor/* matches everything under vendor/.
content-type string The MIME/IANA media type to assign on a match, e.g. "font/woff2".
[tool.pitloom.content-type]
enabled = true
method = "auto"

[[tool.pitloom.content-type.override]]
pattern = "*.woff2"
content-type = "font/woff2"

[[tool.pitloom.content-type.override]]
pattern = "vendor/*"
content-type = "application/octet-stream"

Invalid values / fallback behaviour: override present but not an array of tables, an entry not a table, a missing/empty pattern, or a content-type not shaped like type/subtype -- each raises ValueError at config-read time with a message naming the exact problem. First-match-wins in declaration order; a file matching no pattern falls through to normal detection.

[tool.pitloom.fragment]

Key Type Default CLI flag Action input API param Meaning
files array of strings and/or tables [] -- -- -- Pre-generated SPDX 3 JSON-LD fragment files merged into a project directory's SBOM (not a wheel, sdist, environment or model file SBOM). Each entry is either a plain path string (shorthand -- every other field below defaults) or an inline table with path plus any of the fields below. See Merge fragments, loom fragment list.

Kept as its own table (rather than folded into a flat [tool.pitloom] key) since it's expected to grow more fragment-related settings.

files table-entry fields (all optional besides path):

Key Type Default Meaning
path string (required) Path to the fragment file, relative to the directory of the file that sets it (the project directory for its own pyproject.toml).
role string null Free-form, unvalidated label for what part this fragment plays in a pipeline (e.g. input_dataset, output_dataset, ai_model, software_package, source, training_script, data_cleaning_script, post_processing_script, guardrail_safety_function) -- not enforced, and not read by the merge itself yet; informational only, shown by loom fragment list.
description string null Human-readable description of what the fragment covers.
required boolean false If true, a missing or unreadable fragment fails the build (FragmentMergeError) instead of the default warn-and-skip.
sha256 string null Expected SHA-256 hex digest of the fragment file. Currently checked for display only by loom fragment list -- not yet enforced before merge (planned: loom fragment sign).
link-to-main string null Reserved for a future SPDX relationship type between the fragment's root element and the project's main package. Stored but not yet acted on.
[tool.pitloom.fragment]
files = [
    "fragments/legacy.spdx3.json",
    { path = "fragments/model.spdx3.json", role = "ai_model", required = true, sha256 = "a3f1..." },
]

[tool.pitloom.creation]

Key Type Default CLI flag Meaning
creation-datetime string (ISO 8601) (current time) --creation-datetime Overrides the SBOM's recorded creation timestamp.
creation-comment string null --creation-comment Free-text comment on CreationInfo.
no-creation-tool bool false --no-creation-tool Omit the default "Pitloom" creation-tool entry.

creation-datetime resolution order: an explicit pin here (or --creation-datetime) always wins when set -- it is a deliberate, per-SBOM value and so takes priority over the ambient, workspace-wide SOURCE_DATE_EPOCH environment variable (reproducible-builds.org). When neither is set, the current UTC time is used. The same priority order applies to the Hatchling build hook's builtTime field. SOURCE_DATE_EPOCH is a useful default for CI environments that already export it for reproducibility without needing a per-project creation-datetime pin, but an explicit pin always overrides it.

Embedding into a wheel (loom embed-wheel, loom wheel --embed): a .whl is a ZIP archive, and the ZIP format's own per-entry timestamp field can only represent dates from 1980-01-01 onward -- a binary format limitation, unrelated to Unix time (what SOURCE_DATE_EPOCH counts from, starting 1970-01-01) or to the SBOM's own created field (plain JSON, no such limit). A SOURCE_DATE_EPOCH set below 1980 (e.g. 0, a value some build systems use deliberately as a fixed placeholder) is floored to 1980-01-01 for the wheel's embedded ZIP entry only -- the SBOM's own created field keeps the true value, so the two can legitimately diverge. When this happens, Pitloom prints an INFO: line rather than silently rewriting the SBOM's stated creation date to match the ZIP format's limitation. To avoid the divergence entirely, set SOURCE_DATE_EPOCH to 315532800 (1980-01-01) or later.

[[tool.pitloom.creator]] / [[tool.pitloom.creation-tool]]

Array-of-tables, one entry per creator/tool. See Creator and creation metadata for worked examples and Creation metadata for what these fields record in the generated SBOM.

Table Key Type Meaning
[[tool.pitloom.creator]] name string (required) Creator's name.
email string Creator's email.
type "person" | "organization" | "software-agent" | "agent" Defaults to "person".
[[tool.pitloom.creation-tool]] name string (required) Tool name recorded as having produced the SBOM.

Invalid values / fallback behaviour: a missing/empty name on either table, or a non-string type/email, raises ValueError at config-read time. --creator-name/--creation-tool on the CLI replace the whole configured list for that run rather than merging with it.

[tool.pitloom.provenance]

Config-only, except max-source-metadata-bytes (see below) -- see Metadata provenance for what each setting changes in the generated SBOM's Annotations.

Key Type Default CLI flag Action input API param Meaning
format "annotation" | "comment" | "both" "both" -- -- -- How metadata provenance is recorded: SPDX Core Annotation elements, legacy Element.comment strings, or both.
schema string "pitloom/1" -- -- -- Which statement schema encodes provenance Annotations.
detail "minimal" | "full" "minimal" -- -- -- "minimal" emits a field-source Annotation only when the source adds signal the native value can't convey; "full" emits the per-field source map for every field.
preserve-source-metadata "auto" | "always" | "never" "auto" -- -- -- Whether to embed an artifact's verbatim original metadata blob. "auto" does so only when the artifact isn't shipped with the distribution (and so can't be re-extracted later).
max-source-metadata-bytes 0 or an integer >= 8 0 --max-source-metadata-bytes max-source-metadata-bytes max_source_metadata_bytes Byte budget for the serialised artifact-metadata Annotation.statement. 0 means unlimited (today's behaviour). When exceeded, the largest metadata entries are dropped first and the result is marked truncated/truncatedKeys/truncatedKeyCount/maxMetadataBytes -- see Metadata provenance. Unlike its siblings above, this one has a CLI flag, an Action input and an API parameter (generate(), generate_*_sbom(); ConfigOverrides for embed_wheel_sbom()): a byte cap is an operational knob someone may want to override per-run without editing pyproject.toml.

Invalid values / fallback behaviour: a non-string value, or a format/detail/preserve-source-metadata outside its listed set, raises ValueError at config-read time. An unknown schema id is not caught here (core doesn't import the assembly layer's encoder registry) -- it's caught with a clear error the first time an SBOM is actually generated. max-source-metadata-bytes: only 0 (unlimited) or at least 8 bytes -- the smallest JSON object it could ever hold -- is valid. A non-integer, a bool, a negative value or 1 to 7 is an error, never "unlimited": at config-read time, on --max-source-metadata-bytes (exit 2) and on the library's max_source_metadata_bytes=.

See also

  • Command line -- flag-by-flag usage with worked examples.
  • GitHub Action -- input reference for CI.
  • Python API -- calling Pitloom from Python code.
  • Dependency sources and precedence -- how resolved lock files feed into Source SBOM dependencies.
  • Hatchling build hook -- inherits the project's [tool.pitloom] automatically, no separate hook-level config surface (only [tool.hatch.build.hooks.pitloom] enabled controls whether the hook itself runs).