# SBOM Generation Guide for Python - UV, Poetry, Pipenv

> Learn how to generate Software Bill of Materials for Python projects. Complete guide with uv.lock, poetry.lock, and Pipfile.lock examples.

- **Updated:** 2026-08-31

## Source vs Build SBOMs

Before we dive into the actual guide, let's start by talking about SBOM generation. Generally speaking, an SBOM is either built from source (also known as 'pre-build' in CycloneDX terminology) or built after installation. When we talk about source SBOMs, we normally refer to generating an SBOM from a lockfile, and for built SBOMs, we look at what has been installed by the package manager. These might seem interchangeable, and the output might be identical. However, this really boils down to the quality of the lockfile, so let's dive into this with some examples.

## Lockfile Example

Modern Python package managers like [UV](https://docs.astral.sh/uv/) and [Poetry](https://python-poetry.org/) automatically generate complete lockfiles with all transitive dependencies and cryptographic hashes. This makes them ideal for SBOM generation.

### Why Migrate Away from requirements.txt?

If you're still using `requirements.txt` with `pip`, consider migrating to UV or Poetry. The traditional `requirements.txt` approach has several limitations that affect SBOM quality:

- **Missing transitive dependencies**: A `requirements.txt` often only lists direct dependencies. Flask might be listed, but its dependencies (Werkzeug, Jinja2, etc.) are missing unless you manually run `pip freeze`.
- **No hashes by default**: Adding cryptographic hashes requires extra tooling like pip-tools, and even then, SBOM tools have inconsistent support for this format.
- **Version ranges cause ambiguity**: Expressions like `flask>=3.0.0` make it impossible to generate accurate source/pre-build SBOMs since the exact version is unknown.

UV and Poetry solve all of these issues automatically.

For a Flask-based Python server, your `pyproject.toml` would declare:

```toml
[project]
dependencies = ["flask>=3.0.3"]
```

Running `uv lock` generates a `uv.lock` file that captures Flask and all its dependencies with exact versions and hashes – ready for accurate SBOM generation.

Similarly, with Poetry, running `poetry lock` generates a `poetry.lock` file with the same completeness.

The key point is that the SBOM is directly correlated to the quality of the lockfile. UV and Poetry solve this by automatically:

- Capturing all transitive dependencies
- Including cryptographic hashes
- Using precise version pinning

### Hashes Are Included Automatically

UV and Poetry automatically add cryptographic hashes to all packages in their lockfiles. This is required for NTIA Minimum Elements compliance – no extra tooling needed.

## Generating an SBOM

SBOM generation is the first step in the [SBOM lifecycle](https://sbomify.com/features/generate-collaborate-analyze/). After generation, you typically need to enrich your SBOM with package metadata and augment it with your organization's details.

![Lifecycle](https://sbomify.com/assets/images/d2/lifecycle.svg)

### Using sbomify GitHub Action (Recommended)

The [sbomify action](https://sbomify.com/sbomify-action/) is a swiss army knife for SBOMs that automatically selects the best generation tool for your ecosystem, enriches the output with package metadata, and optionally augments it with your business information – all in one step. It runs on [any CI platform](https://sbomify.com/sbomify-action/runtimes/), not just GitHub, and the source is [on GitHub](https://github.com/sbomify/sbomify-action/).

For Python, sbomify uses **cyclonedx-py** under the hood as it provides the most accurate results for Python lockfiles, including proper hash support.

**Standalone (no account needed):**

```yaml
- uses: sbomify/sbomify-action@master
  env:
    LOCK_FILE: uv.lock
    OUTPUT_FILE: sbom.cdx.json
    COMPONENT_NAME: my-python-app
    COMPONENT_VERSION: ${{ github.ref_name }}
    ENRICH: true
    UPLOAD: false
```

Using `github.ref_name` automatically captures your git tag (e.g., `v1.2.3`) as the SBOM version. For rolling releases without tags, use `github.sha` instead. See our [SBOM versioning guide](https://sbomify.com/guides/how-to-version-sboms/) for best practices.

**With sbomify platform (adds augmentation and upload):**

```yaml
- uses: sbomify/sbomify-action@master
  env:
    TOKEN: ${{ secrets.SBOMIFY_TOKEN }}
    COMPONENT_ID: my-component-id
    LOCK_FILE: uv.lock
    OUTPUT_FILE: sbom.cdx.json
    AUGMENT: true
    ENRICH: true
```

The action supports all Python lockfiles: `uv.lock`, `poetry.lock`, `Pipfile.lock`, and `pyproject.toml`.

**Still on requirements.txt?** If you haven't migrated to UV or Poetry yet, sbomify-action can still help. When given a `requirements.txt`, the action uses [pipdeptree](https://github.com/tox-dev/pipdeptree) to discover transitive dependencies that aren't listed in your requirements file and automatically adds them to the SBOM. This means you get a more complete SBOM even without a proper lockfile, though we still recommend migrating to UV or Poetry for the best results.

### Alternative Tools

If you prefer to run SBOM generation tools manually:

**cyclonedx-py (Python-native, recommended for manual use):**

```bash
pip install cyclonedx-bom
cyclonedx-py environment --output-format json -o sbom.cdx.json
```

This is the same tool sbomify uses under the hood. It reads from your installed environment and properly handles hashes.

**cdxgen:**

```bash
cdxgen -o sbom.cdx.json
```

When using these tools directly, you'll need to handle enrichment and augmentation separately. See our [Resources page](https://sbomify.com/resources/#python) for more Python SBOM tools.

### GitLab CI

```yaml
generate-sbom:
  image: ghcr.io/sbomify/sbomify-action
  variables:
    LOCK_FILE: uv.lock
    OUTPUT_FILE: sbom.cdx.json
    UPLOAD: "false"
    ENRICH: "true"
  script:
    - sbomify-action
```

## PEP 770: Shipping SBOMs Inside Python Packages

[PEP 770](https://peps.python.org/pep-0770/) standardizes including SBOMs directly inside Python wheels via the `.dist-info/sboms/` directory. If you publish Python packages to PyPI, this means your users get the SBOM automatically when they `pip install` your package. Build backends like hatchling (>= 1.28.0) already support it. Read our [full write-up on PEP 770](https://sbomify.com/2026/03/05/pep-770-sboms-in-python-packages/) for implementation details and real-world examples.

## Further Reading

Related blog posts:

- [PEP 770: SBOMs Are Now a First-Class Citizen in Python Packages](https://sbomify.com/2026/03/05/pep-770-sboms-in-python-packages/) - How PEP 770 works and how we adopted it in two projects with minimal effort
- [How to Generate SBOMs for Python Packages with pipdeptree and cyclonedx-py](https://sbomify.com/2024/07/30/generate-sboms-for-python-packages-with-pipdeptree-and-cyclonedx-py/) - Tutorial on generating CycloneDX SBOMs including transitive dependencies and best practices for pinning with hashes

## Further Resources

For more SBOM tools and resources, see our [SBOM Resources](https://sbomify.com/resources/) page, which includes additional Python-specific tools like CycloneDX Python, sbom4python, and SPDX Python libraries.

---

From sbomify: https://sbomify.com/guides/python/
