Usage
At build time
Setuptools Version Requirements
setuptools-scm requires setuptools 61 or later (minimum), but recommends >=80 for best compatibility.
Support for setuptools <80 is deprecated and will be removed in a future release.
The examples below use setuptools>=80 as the recommended version.
There are three ways to enable setuptools-scm at build time:
Simplified Activation (new)
For basic usage without custom configuration, use the simple extra:
[build-system]
requires = ["setuptools>=80", "setuptools-scm[simple]>=9.2"]
build-backend = "setuptools.build_meta"
[project]
# version = "0.0.1" # Remove any existing version parameter.
dynamic = ["version"]
# No [tool.setuptools_scm] section needed for basic usage!
This streamlined approach automatically enables version inference when both conditions are met:
setuptools-scm[simple]is listed inbuild-system.requiresversionis included inproject.dynamic
When to use simplified activation
Use simplified activation when you:
- Want basic SCM version inference with default settings
- Don't need custom version schemes or file writing
- Prefer minimal configuration
Upgrade to explicit configuration if you need customization.
Explicit Configuration (full control)
Add a tool.setuptools_scm section for custom configuration:
[build-system]
requires = ["setuptools>=80", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta"
[project]
# version = "0.0.1" # Remove any existing version parameter.
dynamic = ["version"]
[tool.setuptools_scm]
# Configure custom options here (version schemes, file writing, etc.)
version_file = "src/mypackage/_version.py"
# Example: Git-specific configuration
[tool.setuptools_scm.scm.git]
pre_parse = "fail_on_missing_submodules" # Fail if submodules are not initialized
describe_command = "git describe --dirty --tags --long --exclude *js*" # Custom Git describe command
Projects must support PEP 518 (pip and
pep517). Tools that still invoke setup.py
must ensure build requirements are installed.
Using setup.py to pass code
setup.py should only be used when you need to pass Python callables
(custom version schemes, local schemes, etc.) that cannot be expressed in TOML.
All non-code configuration belongs in pyproject.toml — use the
simplified or explicit
approaches above for that.
setup.py is not for configuration
Prefer configuring string-based options like
use_scm_version={"local_scheme": "no-local-version"} in
[tool.setuptools_scm] in pyproject.toml.
While you can pass these options via setup.py (they are forwarded
as keyword overrides and string scheme names work the same way as in
[tool.setuptools_scm]), it is recommended to keep non-code settings
in pyproject.toml and reserve setup.py for Python callables
that cannot be represented in TOML.
To pass a custom version scheme or local scheme as a callable,
combine a pyproject.toml (for build requirements and non-code config)
with a setup.py (for the callable):
[build-system]
requires = ["setuptools>=80", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
dynamic = ["version"]
[tool.setuptools_scm]
# Non-code configuration stays here
version_file = "src/my_package/_version.py"
from setuptools import setup
from setuptools_scm import ScmVersion
def my_version_scheme(version: ScmVersion) -> str:
from setuptools_scm.version import guess_next_version
return version.format_next_version(guess_next_version, "{guessed}b{distance}")
setup(use_scm_version={"version_scheme": my_version_scheme})
The use_scm_version dict in setup.py overrides matching keys from
[tool.setuptools_scm] — use it only for the callable entries, and keep
everything else in pyproject.toml.
See Customizing for more examples of custom version schemes.
How use_scm_version works
The use_scm_version keyword is a setuptools entry point registered
by setuptools-scm. For it to work, setuptools-scm must be installed
in the build environment before setup() runs.
With modern tooling (python -m build, pip installing from source),
the build frontend reads build-system.requires from pyproject.toml
and installs setuptools-scm automatically. This is the only
supported approach — always declare your build dependencies in
pyproject.toml.
Legacy simplified activation (removed in 9.2.0)
Previous versions had a "simplified" activation where listing
plain setuptools-scm in build-system.requires together with
project.dynamic = ["version"] would auto-enable version inference
without any [tool.setuptools_scm] section or use_scm_version keyword.
This was removed because it caused ambiguous activation — projects using setuptools-scm only for its file finder would unexpectedly trigger version inference.
The [simple] extra replaces this with explicit opt-in. If you previously
relied on the old behavior, either:
- Add the
[simple]extra:setuptools-scm[simple]>=9.2inbuild-system.requires. - Add an explicit
[tool.setuptools_scm]section topyproject.toml.
Version files
Version files can be created with the version_file directive.
...
[tool.setuptools_scm]
version_file = "pkg/_version.py"
Where pkg is the name of your package.
Unless the small overhead of introspecting the version at runtime via
importlib.metadata is a concern or you need a version file in an
alternative format such as plain-text (see version_file_template)
you most likely do not need to write a separate version file; see
the runtime discussion below for more details.
As cli tool
If you need to confirm which version string is being generated or debug the configuration, you can install setuptools-scm directly in your working environment and run:
$ python -m setuptools_scm # example from running local after changes
7.1.1.dev149+g5197d0f.d20230727
and to list all files tracked by the SCM:
$ python -m setuptools_scm ls # output trimmed for brevity
./LICENSE
...
./src/setuptools_scm/__init__.py
./src/...
...
Committed files only
Currently only committed files are listed; this might change in the future.
sdists/archives don't provide file lists
Currently there is no built-in mechanism to safely transfer file lists to sdists or obtain them from archives. Coordination for setuptools and hatch is ongoing.
To explore other options, try
$ python -m setuptools_scm --help
At runtime
Recommended Approach
Use standard Python metadata (importlib.metadata) for runtime version access. This is the standard, recommended approach that works with any packaging system.
Python Metadata
The standard method to retrieve the version number at runtime is via
PEP-0566 metadata using
importlib.metadata from the standard library (added in Python 3.8)
or the
importlib_metadata
backport for earlier versions:
from importlib.metadata import version, PackageNotFoundError
try:
__version__ = version("package-name")
except PackageNotFoundError:
# package is not installed
pass
Via your version file
If you have opted to create a Python version file via the standard
template, you can import that file, where you will have a version
string and a version_tuple tuple with elements corresponding to
the version tags.
import package_name._version as v
print(v.version)
print(v.version_tuple)
Via setuptools_scm (strongly discouraged)
Discouraged API
Direct use of setuptools_scm.get_version() at runtime is strongly discouraged. Use importlib.metadata instead.
While the most simple looking way to use setuptools_scm at
runtime is:
from setuptools_scm import get_version
version = get_version()
it is strongly discouraged to call directly into setuptools_scm over
the standard Python importlib.metadata.
In order to use setuptools_scm from code that is one directory deeper
than the project's root, you can use:
from setuptools_scm import get_version
version = get_version(root='..', relative_to=__file__)
For legacy configurations or when working with extracted archives (like PyPI tarballs),
you may need to specify a fallback_root parameter. This is particularly useful
for legacy Sphinx configurations that use get_version() instead of getting the
version from the installed package:
from setuptools_scm import get_version
# For legacy Sphinx conf.py that needs to work both in development and from archives
version = get_version(root='..', fallback_root='..', relative_to=__file__)
The fallback_root parameter specifies the directory to use when the SCM metadata
is not available (e.g., in extracted tarballs), while root is used when SCM
metadata is present.
Usage from Sphinx
The recommended approach for Sphinx configurations is to use the installed package metadata:
from importlib.metadata import version as get_version
release: str = get_version("package-name")
# for example take major/minor
version: str = ".".join(release.split('.')[:2])
The underlying reason is that services like Read the Docs sometimes change the working directory for good reasons and using the installed metadata prevents using needless volatile data there.
Legacy Sphinx configurations
If you have a legacy Sphinx configuration that still uses setuptools_scm.get_version()
directly (instead of importlib.metadata), you may need to use the fallback_root
parameter to ensure it works both in development and when building from archives:
from setuptools_scm import get_version
# Legacy approach - use fallback_root for archive compatibility
release = get_version(root='..', fallback_root='..', relative_to=__file__)
version = ".".join(release.split('.')[:2])
However, it's strongly recommended to migrate to the importlib.metadata approach above.
With Docker/Podman
In some situations, Docker may not copy the .git into the container when
building images. Because of this, builds with version inference may fail.
The following snippet exposes the external .git directory without copying.
This allows the version to be inferred properly form inside the container
without copying the entire .git folder into the container image.
RUN --mount=source=.git,target=.git,type=bind \
pip install --no-cache-dir -e .
However, this build step introduces a dependency to the state of your local
.git folder the build cache and triggers the long-running pip install process on every build.
To optimize build caching, one can use an environment variable to pretend a pseudo
version that is used to cache the results of the pip install process:
FROM python
COPY pyproject.toml
ARG PSEUDO_VERSION=1 # strongly recommended to update based on Git describe
RUN SETUPTOOLS_SCM_PRETEND_VERSION_FOR_MY_PACKAGE=${PSEUDO_VERSION} pip install -e .[test]
RUN --mount=source=.git,target=.git,type=bind pip install -e .
Note that running this Dockerfile requires docker with BuildKit enabled docs.
To avoid BuildKit and mounting of the .git folder altogether, one can also pass the desired
version as a build argument.
Note that SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${DIST_NAME}
is preferred over SETUPTOOLS_SCM_PRETEND_VERSION.
Default versioning scheme
In the standard configuration setuptools-scm takes a look at three things:
- latest tag (with a version number)
- the distance to this tag (e.g. number of revisions since latest tag)
- workdir state (e.g. uncommitted changes since latest tag)
and uses roughly the following logic to render the version:
| distance | state | format |
|---|---|---|
| no | unchanged | {tag} |
| yes | unchanged | {next_version}.dev{distance}+{scm letter}{revision hash} |
| no | changed | {tag}+dYYYYMMDD |
| yes | changed | {next_version}.dev{distance}+{scm letter}{revision hash}.dYYYYMMDD |
where {next_version} is the next version number after the latest tag
The next version is calculated by adding 1 to the last numeric component of
the tag.
For Git projects, the version relies on git describe,
so you will see an additional g prepended to the {revision hash}.
Version Tag Formats
setuptools-scm automatically detects version information from SCM tags. The default tag regex supports a wide variety of tag formats, with the "v" prefix being recommended for clarity and consistency.
Recommended Tag Format
Use the "v" prefix for version tags:
git tag v1.0.0 # Recommended
git tag v2.1.3
git tag v1.0.0-alpha1
git tag v1.0.0-rc1
Supported Tag Formats
setuptools-scm's default tag regex supports:
- Version prefix:
vorV(optional, but recommended) - Project prefix: Optional project name followed by dashes (e.g.,
myproject-v1.0.0) - Version number: Standard semantic versioning patterns
- Pre-release suffixes: Alpha, beta, RC versions
- Build metadata: Anything after
+is ignored
Examples of valid tags:
# Recommended formats (with v prefix)
v1.0.0
v2.1.3
v1.0.0-alpha1
v1.0.0-beta2
v1.0.0-rc1
v1.2.3-dev
V1.0.0 # Capital V also works
# Project-prefixed formats
myproject-v1.0.0
my-lib-v2.1.0
# Without v prefix (supported but not recommended)
1.0.0
2.1.3
1.0.0-alpha1
# With build metadata (metadata after + is ignored)
v1.0.0+build.123
v1.0.0+20240115
Why Use the "v" Prefix?
- Clarity: Makes it immediately obvious that the tag represents a version
- Convention: Widely adopted standard across the software industry
- Git compatibility: Works well with Git's tag sorting and filtering
- Tool compatibility: Many other tools expect version tags to have a "v" prefix
Custom Tag Patterns
If you need different tag patterns, you can customize the tag regex:
[tool.setuptools_scm]
tag_regex = "^release-(?P<version>[0-9]+\\.[0-9]+\\.[0-9]+)$"
Node ID Prefixes
setuptools-scm automatically prepends identifying characters to node IDs (commit/revision hashes) to distinguish between different SCM systems:
- Git repositories: Node IDs are prefixed with
g(e.g.,g1a2b3c4d5) - Mercurial repositories: Node IDs are prefixed with
h(e.g.,h1a2b3c4d5)
This prefixing serves several purposes:
- SCM identification: Makes it clear which version control system was used
- Consistency: Ensures predictable node ID format across different SCM backends
- Debugging: Helps identify the source SCM when troubleshooting version issues
The prefixes are automatically added by setuptools-scm and should be included when manually
specifying node IDs in environment variables like SETUPTOOLS_SCM_PRETEND_METADATA.
Examples:
# Git node ID
1.0.0.dev5+g1a2b3c4d5
# Mercurial node ID
1.0.0.dev5+h1a2b3c4d5
Note
According to PEP 440, if a version includes a local component, the package cannot be published to public package indexes like PyPI or TestPyPI. The disallowed version segments may be seen in auto-publishing workflows or when a configuration mistake is made.
However, some package indexes such as devpi or other alternatives allow local versions. Local version identifiers must comply with PEP 440.
Semantic Versioning (SemVer)
Due to the default behavior it's necessary to always include a
patch version (the 3 in 1.2.3), or else the automatic guessing
will increment the wrong part of the SemVer (e.g. tag 2.0 results in
2.1.devX instead of 2.0.1.devX). So please make sure to tag
accordingly.
Builtin mechanisms for obtaining version numbers
- the SCM itself (Git/Mercurial)
.hg_archivalfiles (Mercurial archives).git_archival.txtfiles (Git archives, see subsection below)PKG-INFO
Git archives
Git archives are supported, but require specific setup and understanding of how they work with package building.
Overview
When you create a .git_archival.txt file in your repository, it enables setuptools-scm to extract version information from Git archives (e.g., GitHub's source downloads). However, this file contains template placeholders that must be expanded by git archive - they won't work when building directly from your working directory.
Setting up git archival support
You can generate a .git_archival.txt file using the setuptools-scm CLI:
# Generate a stable archival file (recommended for releases)
$ python -m setuptools_scm create-archival-file --stable
# Generate a full archival file with all metadata (use with caution)
$ python -m setuptools_scm create-archival-file --full
Alternatively, you can create the file manually:
Stable version (recommended):
node: $Format:%H$
node-date: $Format:%cI$
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
Full version (with branch information - can cause instability):
# WARNING: Including ref-names can make archive checksums unstable
# after commits are added post-release. Use only if describe-name is insufficient.
node: $Format:%H$
node-date: $Format:%cI$
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
ref-names: $Format:%D$
Feel free to alter the match field in describe-name to match your project's
tagging style.
Note
If your Git host provider does not properly expand describe-name, you may
need to include ref-names: $Format:%D$. But beware, this can often
lead to the Git archive's checksum changing after a commit is added
post-release. See this issue for more details.
.git_archival.txt export-subst
Finally, commit both files:
$ git add .git_archival.txt .gitattributes && git commit -m "add git archive support"
Understanding the warnings
If you see warnings like these when building your package:
UserWarning: git archive did not support describe output
UserWarning: unprocessed git archival found (no export subst applied)
This typically happens when:
- Building from working directory: You're running
python -m builddirectly in your repository - Sdist extraction: A build tool extracts your sdist to build wheels, but the extracted directory isn't a Git repository
Recommended build workflows
For development builds:
Exclude .git_archival.txt from your package to avoid warnings:
# Exclude archival file from development builds
exclude .git_archival.txt
For release builds from archives: Build from an actual Git archive to ensure proper template expansion:
# Create archive from a specific tag/commit
$ git archive --output=../source_archive.tar v1.2.3
$ cd ..
$ tar -xf source_archive.tar
$ cd extracted_directory/
$ python -m build .
For automated releases: Many CI systems and package repositories (like GitHub Actions) automatically handle this correctly when building from Git archives.
Integration with package managers
MANIFEST.in exclusions:
# Exclude development files from packages
exclude .git_archival.txt
exclude .gitattributes
# Archive configuration
.git_archival.txt export-subst
.gitignore export-ignore
Troubleshooting
Problem: "unprocessed git archival found" warnings
- ✅ Solution: Add
exclude .git_archival.txttoMANIFEST.infor development builds - ✅ Alternative: Build from actual Git archives for releases
Problem: "git archive did not support describe output" warnings
- ℹ️ Information: This is expected when
.git_archival.txtcontains unexpanded templates - ✅ Solution: Same as above - exclude file or build from Git archives
Problem: Version detection fails in git archives
- ✅ Check: Is
.gitattributesconfigured withexport-subst? - ✅ Check: Are you building from a properly created Git archive?
- ✅ Check: Does your Git hosting provider support archive template expansion?
Branch Names and Archive Stability
Including ref-names: $Format:%D$ in your .git_archival.txt can make archive checksums change when new commits are added to branches referenced in the archive. This primarily affects GitHub's automatic source archives. Use the stable format (without ref-names) unless you specifically need branch information and understand the stability implications.
Version Files
If you are creating a _version.py file, it should not be kept in version control. Add it to .gitignore:
# Generated version file
src/mypackage/_version.py
File finders hook makes most of MANIFEST.in unnecessary
Automatic File Inclusion
setuptools-scm automatically provides a setuptools file finder by default. This means that when you install setuptools-scm, it will automatically include all SCM-tracked files in your source distributions (sdist) without requiring a MANIFEST.in file.
This automatic behavior can be surprising if you're not expecting it. The file finder is active as soon as setuptools-scm is installed in your build environment.
setuptools-scm implements a file_finders entry point
which returns all files tracked by your SCM.
This eliminates the need for a manually constructed MANIFEST.in in most cases where this
would be required when not using setuptools-scm.
How it works
- Automatic Discovery: When building source distributions (
python -m build --sdist), setuptools automatically calls thesetuptools-scmfile finder - SCM Integration: The file finder queries your SCM (Git/Mercurial) for all tracked files
- Inclusion: All tracked files are automatically included in the sdist
Controlling file inclusion
To exclude unwanted files:
-
Use
MANIFEST.into exclude specific files/patterns:MANIFEST.inexclude development.txt recursive-exclude tests *.pyc -
Configure Git archive (for Git repositories):
.gitattributes# Add to .gitattributes tests/ export-ignore *.md export-ignore -
Use
.hgignoreor Mercurial archive configuration (for Mercurial repositories)
Troubleshooting
Problem: Unwanted files in my package
- ✅ Solution: Add exclusions to
MANIFEST.in - ✅ Alternative: Use Git/Mercurial archive configuration
Problem: Missing files in package
- ✅ Check: Are the files tracked in your SCM?
- ✅ Solution:
git addmissing files or override withMANIFEST.in
Problem: File finder not working
- ✅ Check: Is setuptools-scm installed in your build environment?
- ✅ Check: Are you in a valid SCM repository?
Problem: Error about dubious ownership
- CVE-2022-24765 identified security issues with git trusting repositories owned by another user. If the file-finder detects that git is unable to list files because it is operating in a git directory owned by another user, it will raise an error. Since this was not the default behaviour of setuptools_scm previously, you can override this by setting SETUPTOOLS_SCM_IGNORE_DUBIOUS_OWNER, but be warned git will not be listing files correctly with this flag set.
Timestamps for Local Development Versions
Improved Timestamp Behavior
When your working directory has uncommitted changes (dirty), setuptools-scm now uses the actual modification time of changed files instead of the current time for local version schemes like node-and-date.
Before: Dirty working directories always used current time (now)
Now: Uses the latest modification time of changed files, falling back to current time only if no changed files are found
This provides more stable and meaningful timestamps that reflect when you actually made changes to your code.
How it works:
- Clean repository: Uses commit timestamp from SCM
- Dirty repository: Uses latest modification time of changed files
- Fallback: Uses current time if no modification times can be determined
Benefits:
- More stable builds during development
- Timestamps reflect actual change times
- Better for reproducible development workflows