Your first SBOM¶
In this tutorial you build a small package with an SBOM inside its wheel, look at what the SBOM says,
and check it the way a CI job would. It takes about ten minutes. You need Python 3.11 or later, git, and uv;
any other build frontend, such as python -m build, works the same.
Create the project¶
Make a directory with a package, a license and a pyproject.toml. The project uses hatchling, the backend
python-sbom has the most to say about; Supported backends lists the others.
$ mkdir greet && cd greet
$ mkdir -p src/greet && touch src/greet/__init__.py
$ printf 'Copyright (c) 2026 You\n\nMIT terms\n' > LICENSE
[build-system]
build-backend = "hatchling.build"
requires = [ "hatchling>=1.28", "python-sbom[hatch]>=0.3" ]
[project]
name = "greet"
version = "1.0"
description = "says hello"
license = "MIT"
license-files = [ "LICENSE" ]
requires-python = ">=3.11"
classifiers = [
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3.15",
]
dependencies = [ "packaging>=24" ]
urls.Source = "https://github.com/you/greet"
[tool.hatch]
build.hooks.python-sbom = {}
[tool.python-sbom]
supplier = { name = "You", url = [ "https://github.com/you" ] }
The empty [tool.hatch.build.hooks.python-sbom] table turns the hook on. [tool.python-sbom] holds python-sbom’s
own options; here it names you as the supplier. docs/examples/hatchling holds the finished project.
Commit it, with the build output ignored, so the SBOM can name the exact source revision. A checkout with uncommitted or untracked files has no commit that reproduces it, and then the SBOM names none:
$ printf 'dist/\n*.cdx.json\n' > .gitignore
$ git init --quiet && git add . && git commit --quiet --message first
Build the wheel¶
$ uv build
Successfully built dist/greet-1.0.tar.gz
Successfully built dist/greet-1.0-py3-none-any.whl
List the wheel’s .dist-info and you find the SBOM next to the metadata:
$ unzip -Z1 dist/greet-1.0-py3-none-any.whl | grep dist-info
greet-1.0.dist-info/METADATA
greet-1.0.dist-info/WHEEL
greet-1.0.dist-info/licenses/LICENSE
greet-1.0.dist-info/sboms/greet.cdx.json
greet-1.0.dist-info/RECORD
The sdist holds a .python-sbom-revision file with your commit. uv built the wheel from that sdist, not from
your checkout, and the file is how the wheel still knows which commit it came from.
Read the SBOM¶
Print a few parts of the document:
$ unzip -p dist/greet-1.0-py3-none-any.whl greet-1.0.dist-info/sboms/greet.cdx.json > greet.cdx.json
$ python - <<'EOF'
import json
import pprint
sbom = json.load(open('greet.cdx.json'))
root = sbom['metadata']['component']
print(root['name'], root['version'], root['licenses'])
print([component['name'] for component in sbom['components']])
print([reference['url'] for reference in root['externalReferences'] if reference['type'] == 'vcs'])
pprint.pp(sorted(tool['name'] for tool in sbom['metadata']['tools']['components']), compact=True, width=100)
EOF
It prints:
greet 1.0 [{'expression': 'MIT', 'acknowledgement': 'declared'}]
['packaging']
['https://github.com/you/greet', 'https://github.com/you/greet/tree/<your commit>']
['cpython', 'hatchling', 'installer', 'packageurl-python', 'packaging', 'pathspec', 'pluggy',
'python-sbom', 'tomlkit', 'trove-classifiers', 'zipremove']
That is four things:
the project itself, with its license;
packaging, the runtime dependency the project declares, without a version, since the installer picks it later;a link to your repository at the exact commit you built;
the build tools: the interpreter, hatchling, python-sbom and every other package of the build environment.
What the SBOM records says what each part is for.
Check it¶
Install the command line with its validate extra and check the wheel:
$ uvx --from 'python-sbom[validate]' python-sbom validate dist/ --allow-license MIT
SBOM in greet-1.0-py3-none-any.whl is valid
That is the check a CI job runs before publishing; Check SBOMs in CI shows how to wire it in.
Next steps¶
Your project uses another build backend? Set it up for a build backend has a guide with a complete project for each one python-sbom is tested with.
Your project bundles wheels or knows more than its metadata says: Add what your project knows.
Every option python-sbom reads: Configuration.