# setuptools-scm: Python versions derived from your tags

> setuptools-scm extracts a package version from Git or Mercurial metadata so the number in your distribution is never typed by hand, and the repository now also ships vcs-versioning, the same versioning logic as a library that does not depend on setuptools. It is plumbing, and plumbing has sharp edges.

**pypa/setuptools-scm** — the blessed package to manage your versions by scm tags

- Repository: https://github.com/pypa/setuptools-scm
- Website: https://setuptools-scm.readthedocs.io/en/latest/
- Stars: 955 · Forks: 250
- Language: Python
- License: not declared
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/pypa-setuptools-scm

## The version number stops being something you type

The repository description calls setuptools-scm the blessed package to manage your versions by scm tags. The readme is more specific about what the main package does: it extracts Python package versions from Git or Mercurial metadata and provides setuptools integration.

The problem it removes is mundane but persistent. A version string that lives in `pyproject.toml` is a promise that somebody has to keep. The version in the file drifts from the tag, the tag drifts from the release, and the first person to notice is usually a user who installed a broken build and reported it as a bug.

Deriving the version from the repository removes the manual step from the loop. The number in the artefact is a function of the repository state at build time, so a build from a tagged commit produces the tagged version, and a build from a commit after that tag produces something that reflects the difference rather than pretending to be the release.

The scope is wider than the classic setuptools plugin. The readme says the package provides setuptools integration, and the repository also contains a second project, vcs-versioning, described as the core VCS versioning functionality extracted as a standalone library that can be used independently of setuptools.

That split is the most important thing in the repository for anyone deciding what to depend on. If you are publishing a setuptools package, setuptools-scm is the entry point. If you want the versioning logic in another build system, or inside another tool that needs to know what version a checkout represents, vcs-versioning is the piece to look at, and it does not drag setuptools in with it.

## A monorepo with two release lines

The top level README calls this the monorepo for the setuptools-scm ecosystem, containing two main projects, each with its own directory, its own README and its own documentation link.

The release history shows the consequence. The recent releases include vcs-versioning 2.6.0 on 2026-10-06, vcs-versioning 2.5.0 on 2026-09-23, and setuptools-scm 10.3.4 on 2026-09-23, the same minute for the latter two. So the two packages are versioned independently and released independently, and a single afternoon can see a release of each.

The workspace configuration at the top of pyproject.toml is worth a look for anyone trying to reproduce the development setup. The workspace project itself is named `vcs-versioning.workspace` with the description set to workspace configuration for this monorepo and the version `0.1+private`, and it depends on both `vcs-versioning` and `setuptools-scm`. It also declares a console script, `create-release-proposal`, pointing at `vcs_versioning_workspace.create_release_proposal:main`.

The workspace requires Python 3.8 or newer. The dependency groups are organised so that a developer group aggregates docs, typing, release and coverage, and the comment on the coverage group explains that coverage is measured across both packages in one pytest run from the root.

There are also two project files that describe the release machinery: `RELEASE_SYSTEM.md` at the top level and a `CLAUDE.md`. The presence of a `create-release-proposal` script and a towncrier dependency in the release group suggests that release notes are assembled from fragments rather than written by hand.

## Running the tests with uv and pytest

The workspace uses uv for dependency management, and the readme gives the exact commands.

```bash
# Install all packages with all dependency groups (tests, docs, etc.)
uv sync --all-packages --all-groups
```

The whole test suite runs in parallel with twelve workers.

```bash
# Run all tests
uv run pytest -n12
```

And each package can be tested on its own, which is the practical benefit of the split.

```bash
# Run tests for setuptools-scm only
uv run pytest setuptools-scm/testing_scm -n12

# Run tests for vcs-versioning only
uv run pytest vcs-versioning/testing_vcs -n12
```

The pytest configuration in pyproject.toml is strict in a way that suggests a project with many edge cases. `minversion` is 8.2, `xfail_strict` is on so an unexpectedly passing xfail is a failure, and the addopts include `--strict-config`, `--strict-markers` and a plugin, `vcs_versioning.test_api`, loaded explicitly. The `testpaths` list both packages' test directories, so a bare pytest run from the root covers everything.

The markers are informative about what the tests do. `issue(id)` references a GitHub issue, `no_init` skips initialising the VCS repository in the fixture, and `skip_commit` skips committing in the helpers. Those last two exist because the code under test is about repositories, so the fixtures need to create and manipulate real ones.

Documentation is shared across the projects and served locally with mkdocs.

## Repository metadata in the archive, and where the version comes from

Two files in the repository layout hint at how the version travels beyond a working checkout. There is a `.git_archival.txt` at the top level, which is the archive substitution mechanism that lets a version be recorded in an exported source archive, and a `.gitattributes` file that configures how git treats archive exports.

The reason this matters is the one failure mode of any tag based scheme. A version derived from repository metadata requires the metadata. When a project is built from a checkout, the tags are there and the derivation works. When it is built from a source distribution that someone downloaded and unpacked, there is no `.git` directory, so the tool has to fall back to whatever was recorded at packaging time.

That fallback is exactly what the archival file provides, and it is why the packaging configuration of the project is not optional decoration. If your release pipeline downloads an sdist and rebuilds it, the version it reports depends on that record having been written correctly.

The repository also carries `TESTING.md` and `CONTRIBUTING.md` at the top level, plus a `.pre-commit-config.yaml`, so the contribution and test workflow is documented separately from the readme, and the documentation site is built with mkdocs from `mkdocs.yml` with mkdocstrings and the material theme, plus a docs dependency group that is conditional on Python 3.10 or newer.

## Where setuptools-scm is the wrong tool

The first case is building without repository metadata. If your CI builds from an unpacked source archive, or from a copy that has been stripped of `.git`, the tool has nothing to derive a version from. The archival substitution file exists to cover this, but it only works if it was written during the original packaging. A build pipeline that reconstructs a package from a tarball without that record is in the unsupported case, and the symptom is a version like `0.0` or an error at build time rather than a sensible number.

The second case is a version scheme that does not come from tags. Some projects number releases by calendar or by an internal scheme that is not a git tag at all. If your release process does not create a tag, or creates tags that are not the version, deriving the version from them gives you something other than what you intended.

The third case is a project that wants no build time magic. A static version in `pyproject.toml` is boring, but it is legible, reproducible without any tooling, and obvious to anyone reading the file. setuptools-scm trades that legibility for correctness across many releases, which is the right trade for a package with dozens of contributors and a busy release cadence, and the wrong one for a small internal package built a handful of times.

The fourth is simply that the split adds a decision. With two packages in the repository and separate release lines, a change has to go to the right one. That is manageable, but it is an extra thing to know before you file a patch.

## How a derived version differs from a maintained one

The alternative to a tag derived version is the version you maintain by hand. It is worth being clear about what each approach actually gives you, because the choice is often made by habit rather than by argument.

A hand maintained version is a fact about the source tree. It says what this code claims to be, and it is correct whether or not a repository exists. It is wrong in exactly the way that matters most: it disagrees with reality at the moment someone tags a release, cuts a build from the wrong commit, or patches a release branch. Nothing in the build fails when that happens, so the error surfaces downstream, as a broken dependency resolution or a user installing two different builds with the same name.

A derived version is a fact about the repository state. It is always consistent with the commit it was built from, and it changes when the commit changes. What it gives up is the ability to declare a version that the repository does not support, which for most projects is not a capability anyone misses.

The cost of derivation is that the build now depends on more than the source tree. Git or Mercurial must be available, the tag must exist, and the archival fallback must be in place for distribution builds. The cost of maintenance is human attention spread across every release, which is exactly the kind of cost that is invisible until the day it is not paid.

The split into `vcs-versioning` and `setuptools-scm` reflects the same reasoning at a smaller scale. If you only need the versioning logic, you should not have to install setuptools to get it, and that is what the standalone library is for.

## Conclusion

setuptools-scm suits a Python project that tags releases in Git and wants the published version to match the tag without a human editing a version string, and it is the wrong tool for a project that builds from an exported source tree with no repository metadata, or that needs a version scheme unrelated to its tags. Before adopting it, check that your release process tags before building, since that ordering is what makes the derived number correct, and read RELEASE_SYSTEM.md and the towncrier configuration, because the repository now ships two packages with separate release lines, vcs-versioning 2.6.0 and setuptools-scm 10.3.4, and you need to know which one a change belongs to.

## FAQ

### What does setuptools-scm do?

It extracts Python package versions from Git or Mercurial metadata and provides setuptools integration, so the version of a published package comes from the repository tags rather than from a version string someone has to edit.

### What is vcs-versioning?

It is the core VCS versioning functionality extracted from setuptools-scm as a standalone library that can be used independently of setuptools. The two live in the same monorepo and are released on separate version lines, for example vcs-versioning 2.6.0 and setuptools-scm 10.3.4.

### How do I set up the development environment for setuptools-scm?

The workspace uses uv. Run `uv sync --all-packages --all-groups` to install everything, then `uv run pytest -n12` for the whole suite, or target one package with `uv run pytest setuptools-scm/testing_scm -n12` and `uv run pytest vcs-versioning/testing_vcs -n12`.

### What licence applies to setuptools-scm and vcs-versioning?

The readme states that both projects are distributed under the terms of the MIT licence.

### What happens when a build has no Git metadata?

A version derived from tags needs the repository, so the repository carries a `.git_archival.txt` file and a `.gitattributes` configuration for archive exports. Builds from an unpacked archive depend on that record having been written during the original packaging.

## Sources

- [Issues](https://github.com/pypa/setuptools-scm/issues)
- [Project website](https://setuptools-scm.readthedocs.io/en/latest/)
- [pypa/setuptools-scm on GitHub](https://github.com/pypa/setuptools-scm)
- [README](https://github.com/pypa/setuptools-scm/blob/main/README.md)
- [Releases](https://github.com/pypa/setuptools-scm/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pypa-setuptools-scm
