The blessed package to manage your versions by SCM tags
—
setuptools-scm provides seamless integration with setuptools, modern build systems, and packaging tools through entry points, hooks, and configuration. This enables automatic version inference and SCM-managed file inclusion in Python packages.
from pathlib import Path
from typing import Any, Sequence
from os import PathLike
# Type Aliases (from setuptools_scm)
PathT = PathLike[str] | strFunctions for finding and listing SCM-managed files for inclusion in source distributions.
def find_files(path: PathT = "") -> list[str]:
"""
Find SCM-managed files for setuptools integration.
Parameters:
- path: Root path to search for files (default: current directory)
Returns:
List of file paths managed by SCM
"""Functions for writing version information to files during build or development.
def dump_version(
root: PathT,
version: str,
write_to: PathT,
template: str | None = None,
scm_version: ScmVersion | None = None
) -> None:
"""
Write version information to a file (soft deprecated).
Parameters:
- root: Repository root directory
- version: Version string to write
- write_to: Path to write version file to
- template: Template for version file content (uses default if None)
- scm_version: ScmVersion object for additional metadata
"""
def write_version_to_path(
target: Path,
template: str | None = None,
version: str,
scm_version: ScmVersion | None = None
) -> None:
"""
Write version to specific path with template.
Parameters:
- target: Target file path
- template: Template for file content
- version: Version string
- scm_version: ScmVersion object for metadata
"""Specialized file finders for different SCM systems, accessible via entry points.
# Git file finders
def git_find_files(path: str) -> list[str]:
"""Find files managed by Git"""
def git_archive_find_files(path: str) -> list[str]:
"""Find files from Git archive metadata"""
# Mercurial file finders
def hg_find_files(path: str) -> list[str]:
"""Find files managed by Mercurial"""
def hg_archive_find_files(path: str) -> list[str]:
"""Find files from Mercurial archive metadata"""Integration points for setuptools to automatically infer versions and find files.
def version_keyword(dist, keyword, value):
"""Handle use_scm_version keyword in setup.py (legacy)"""
def infer_version(dist):
"""Automatically infer version for setuptools during build"""Helper functions for parsing and processing integration data.
def data_from_mime(path: str, content: str | None = None) -> dict[str, str]:
"""
Return mapping from MIME/pseudo-MIME content.
Parameters:
- path: File path
- content: File content (reads from path if None)
Returns:
Dictionary mapping parsed from MIME-like content
"""setuptools-scm uses Python's entry point system to provide extensible plugin architecture:
# Entry point group: "setuptools_scm.parse_scm"
".git" = "setuptools_scm.git:parse"
".hg" = "setuptools_scm.hg:parse"
# Entry point group: "setuptools_scm.parse_scm_fallback"
".git_archival.txt" = "setuptools_scm.git:parse_archival"
".hg_archival.txt" = "setuptools_scm.hg:parse_archival"
"PKG-INFO" = "setuptools_scm.fallbacks:parse_pkginfo"
"pyproject.toml" = "setuptools_scm.fallbacks:fallback_version"
"setup.py" = "setuptools_scm.fallbacks:fallback_version"# Entry point group: "setuptools_scm.files_command"
".git" = "setuptools_scm._file_finders.git:git_find_files"
".hg" = "setuptools_scm._file_finders.hg:hg_find_files"
# Entry point group: "setuptools_scm.files_command_fallback"
".git_archival.txt" = "setuptools_scm._file_finders.git:git_archive_find_files"
".hg_archival.txt" = "setuptools_scm._file_finders.hg:hg_archive_find_files"# Entry point group: "distutils.setup_keywords"
"use_scm_version" = "setuptools_scm._integration.setuptools:version_keyword"
# Entry point group: "setuptools.file_finders"
"setuptools_scm" = "setuptools_scm._file_finders:find_files"
# Entry point group: "setuptools.finalize_distribution_options"
"setuptools_scm" = "setuptools_scm._integration.setuptools:infer_version"Modern Python packaging with PEP 518/621 support:
[build-system]
requires = ["setuptools>=64", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta"
[project]
name = "mypackage"
dynamic = ["version"]
dependencies = ["requests", "click"]
[tool.setuptools_scm]
version_file = "src/mypackage/_version.py"
version_file_template = '''
# File generated by setuptools-scm
__version__ = "{version}"
__version_tuple__ = {version_tuple}
'''# Configuration with version file
from setuptools_scm import get_version
version = get_version(
write_to="src/mypackage/_version.py",
write_to_template='''
__version__ = "{version}"
__version_tuple__ = {version_tuple}
'''
)
# The generated file will contain:
# __version__ = "1.2.3.dev4+g1234567.d20231201"
# __version_tuple__ = ('1', '2', '3', 'dev4', 'g1234567', 'd20231201')# setup.py (legacy approach)
from setuptools import setup
setup(
name="mypackage",
use_scm_version=True,
setup_requires=["setuptools-scm"],
# ... other setup parameters
)
# With custom configuration
setup(
name="mypackage",
use_scm_version={
"version_scheme": "python-simplified-semver",
"local_scheme": "no-local-version",
"write_to": "mypackage/_version.py"
},
setup_requires=["setuptools-scm"],
)# Using build (PEP 517)
python -m build
# Using pip
pip install -e .
# Using setuptools directly
python setup.py sdist bdist_wheelAll these tools automatically pick up setuptools-scm configuration and use it for version determination.
from setuptools_scm._file_finders import find_files
# Find all SCM-managed files
files = find_files(".")
print("SCM-managed files:", files)
# This is used internally by setuptools to include files in sdistYou can extend setuptools-scm with custom entry points:
# In your package's pyproject.toml
[project.entry-points."setuptools_scm.version_scheme"]
"my-custom-scheme" = "mypackage.scm:my_version_scheme"
[project.entry-points."setuptools_scm.local_scheme"]
"my-local-scheme" = "mypackage.scm:my_local_scheme"
[project.entry-points."setuptools_scm.parse_scm"]
".myvcm" = "mypackage.scm:parse_my_vcm"# mypackage/scm.py
def my_version_scheme(version):
"""Custom version scheme"""
return f"custom-{version.tag}"
def my_local_scheme(version):
"""Custom local scheme"""
return f"+build{version.distance}" if version.distance else ""
def parse_my_vcm(root, config):
"""Custom VCS parser"""
# Implementation for custom version control system
passname: Build and Release
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Required for setuptools-scm
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install dependencies
run: |
pip install build setuptools-scm
- name: Get version
run: |
VERSION=$(python -m setuptools_scm)
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Building version: $VERSION"
- name: Build package
run: python -m buildbuild:
stage: build
script:
- pip install build setuptools-scm
- VERSION=$(python -m setuptools_scm --strip-dev)
- echo "Building version $VERSION"
- python -m build
artifacts:
paths:
- dist/FROM python:3.11-slim
# Install git for setuptools-scm
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
COPY . /app
WORKDIR /app
# setuptools-scm will automatically determine version
RUN pip install -e .
# Version information available at runtime
RUN python -c "import mypackage; print(mypackage.__version__)"# In your package's __init__.py
from importlib.metadata import version, PackageNotFoundError
try:
__version__ = version("mypackage")
except PackageNotFoundError:
# Development installation
from setuptools_scm import get_version
__version__ = get_version(root="..")Or with generated version file:
# In your package's __init__.py
try:
from ._version import __version__
except ImportError:
# Fallback for development
from setuptools_scm import get_version
__version__ = get_version(root="..")# Default template for .py files
TEMPLATE_PY = '''
# file generated by setuptools-scm
# don't change, don't track in version control
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
__version__ = version = {version!r}
__version_tuple__ = version_tuple = {version_tuple!r}
'''# Default template for .txt files
TEMPLATE_TXT = "{version}"# Custom template example
custom_template = '''
"""Version information for {dist_name}"""
VERSION = "{version}"
BUILD_DATE = "{build_date}"
COMMIT = "{node}"
DIRTY = {dirty}
'''
version = get_version(
write_to="mypackage/_version.py",
write_to_template=custom_template
)Install with Tessl CLI
npx tessl i tessl/pypi-setuptools-scm