Versioning It with your Version In Git - automatic package versioning based on VCS tags
—
Integration with build systems including setuptools command classes, onbuild hooks, and file providers for inserting version information into build artifacts. This system allows versioningit to modify files during the build process.
Custom setuptools command classes that run the onbuild step during sdist and wheel building.
def get_cmdclasses(
bases: Optional[dict[str, type[Command]]] = None
) -> dict[str, type[Command]]:
"""
Return a dict of custom setuptools Command classes that run the onbuild
step when building an sdist or wheel.
Parameters:
- bases: Optional dict of alternative base classes for customization
Returns:
dict[str, type[Command]]: Dict containing "sdist" and "build_py" command classes
"""Run the onbuild step for inserting version information into build artifacts.
def run_onbuild(
*,
build_dir: str | Path,
is_source: bool,
template_fields: dict[str, Any],
project_dir: str | Path = os.curdir,
config: Optional[dict] = None,
) -> None:
"""
Run the onbuild step for the given setuptools project.
Parameters:
- build_dir: Directory containing the in-progress build
- is_source: True for sdist (preserves source paths), False for wheel (uses install paths)
- template_fields: Dict of fields for template substitution
- project_dir: Path to project root (default: current directory)
- config: Configuration dict or None to read from configuration file
Raises:
- NoConfigFileError: if config is None and no configuration file exists
- NoConfigSectionError: if configuration file has no versioningit section
- ConfigError: if configuration values are invalid
- MethodError: if a method returns wrong type
"""
def get_template_fields_from_distribution(
dist: Distribution,
) -> Optional[dict[str, Any]]:
"""
Extract template fields stashed on setuptools Distribution by versioningit's
setuptools hook, for passing to the onbuild step.
Parameters:
- dist: setuptools Distribution object
Returns:
Optional[dict[str, Any]]: Template fields or None if building from sdist
"""Abstract interfaces for accessing and modifying files during the build process.
class OnbuildFileProvider(ABC):
"""
Abstract base class for accessing files that are about to be included
in an sdist or wheel currently being built.
"""
@abstractmethod
def get_file(
self, source_path: str | PurePath, install_path: str | PurePath, is_source: bool
) -> OnbuildFile:
"""
Get an object for reading & writing a file in the project being built.
Parameters:
- source_path: Path relative to project root
- install_path: Path when installed (for wheels)
- is_source: True for sdist, False for wheel
Returns:
OnbuildFile: File access object
"""
class OnbuildFile(ABC):
"""
Abstract base class for opening a file in a project currently being built.
"""
@abstractmethod
def open(
self,
mode: str = "r",
encoding: str | None = None,
errors: str | None = None,
newline: str | None = None,
) -> IO:
"""
Open the associated file. Mode must be "r", "w", "a", "rb", "br",
"wb", "bw", "ab", or "ba".
"""Implementation for setuptools builds that operates on temporary build directories.
@dataclass
class SetuptoolsFileProvider(OnbuildFileProvider):
"""
OnbuildFileProvider implementation for setuptools builds.
Operates directly on the temporary directory containing build files.
"""
build_dir: Path
"""The setuptools-managed temporary directory containing build files."""
modified: set[PurePath]
"""Set of file paths that have been opened for writing or appending."""
def get_file(
self, source_path: str | PurePath, install_path: str | PurePath, is_source: bool
) -> SetuptoolsOnbuildFile: ...
@dataclass
class SetuptoolsOnbuildFile(OnbuildFile):
"""File access implementation for setuptools builds."""
provider: SetuptoolsFileProvider
source_path: PurePath
install_path: PurePath
is_source: boolImplementation for Hatch builds that uses temporary directories and forced inclusion.
@dataclass
class HatchFileProvider(OnbuildFileProvider):
"""
OnbuildFileProvider implementation for Hatch builds.
Creates modified files in temporary directory for forced inclusion.
"""
src_dir: Path
"""The root of the project directory."""
tmp_dir: Path
"""Temporary directory for creating modified files."""
modified: set[PurePath]
"""Set of file paths created under the temporary directory."""
def get_file(
self, source_path: str | PurePath, install_path: str | PurePath, is_source: bool
) -> HatchOnbuildFile: ...
def get_force_include(self) -> dict[str, str]:
"""
Get mapping of temporary files to their target paths for forced inclusion.
"""
@dataclass
class HatchOnbuildFile(OnbuildFile):
"""File access implementation for Hatch builds."""
provider: HatchFileProvider
source_path: PurePath
install_path: PurePath
is_source: bool# setup.py
from setuptools import setup
from versioningit import get_cmdclasses
setup(
name="mypackage",
cmdclass=get_cmdclasses(),
# ... other setup parameters
)# pyproject.toml
[build-system]
requires = ["setuptools", "versioningit"]
build-backend = "setuptools.build_meta"
[project]
dynamic = ["version"]
[tool.setuptools.dynamic]
version = {attr = "versioningit.get_version"}
[tool.versioningit]
# ... configuration
[tool.versioningit.onbuild]
method = "replace-version"
source-file = "src/mypackage/__init__.py"
build-file = "mypackage/__init__.py"from setuptools import setup
from setuptools.command.build_py import build_py
from versioningit import get_cmdclasses
class CustomBuildPy(build_py):
def run(self):
# Custom build logic
super().run()
# Use custom base classes
cmdclasses = get_cmdclasses({"build_py": CustomBuildPy})
setup(
name="mypackage",
cmdclass=cmdclasses,
)from pathlib import Path
from versioningit import run_onbuild, get_template_fields_from_distribution
def custom_build_command(self):
# Get template fields from distribution
template_fields = get_template_fields_from_distribution(self.distribution)
if template_fields is not None:
# Run onbuild step
run_onbuild(
build_dir=self.build_lib,
is_source=False, # Building wheel
template_fields=template_fields,
project_dir=Path().resolve()
)from versioningit.onbuild import OnbuildFileProvider, OnbuildFile
from pathlib import Path
class CustomFileProvider(OnbuildFileProvider):
def __init__(self, build_dir: Path):
self.build_dir = build_dir
def get_file(self, source_path, install_path, is_source):
return CustomOnbuildFile(
self, source_path, install_path, is_source
)
class CustomOnbuildFile(OnbuildFile):
def __init__(self, provider, source_path, install_path, is_source):
self.provider = provider
self.source_path = source_path
self.install_path = install_path
self.is_source = is_source
def open(self, mode="r", encoding=None, errors=None, newline=None):
path = self.source_path if self.is_source else self.install_path
full_path = self.provider.build_dir / path
return full_path.open(mode=mode, encoding=encoding, errors=errors, newline=newline)def custom_onbuild_method(
*,
file_provider: OnbuildFileProvider,
is_source: bool,
template_fields: dict[str, Any],
params: dict[str, Any],
) -> None:
"""Custom onbuild method example."""
source_file = params["source-file"]
build_file = params["build-file"]
# Get file handle
file = file_provider.get_file(source_file, build_file, is_source)
# Read current content
with file.open("r", encoding="utf-8") as fp:
content = fp.read()
# Modify content with template fields
new_content = content.format(**template_fields)
# Write back
with file.open("w", encoding="utf-8") as fp:
fp.write(new_content)from versioningit import Versioningit
vgit = Versioningit.from_project_dir()
report = vgit.run()
# Template fields available for onbuild
fields = report.template_fields
# Common substitutions in onbuild
version_line = '__version__ = "{version}"'.format(**fields)
build_info = """
__version__ = "{version}"
__version_tuple__ = {version_tuple}
__build_date__ = "{build_date}"
__git_revision__ = "{rev}"
""".format(**fields)from versioningit import get_cmdclasses, run_onbuild
from versioningit.errors import ConfigError, MethodError
try:
cmdclasses = get_cmdclasses()
except Exception as e:
print(f"Failed to get command classes: {e}")
cmdclasses = {}
# In build command
try:
run_onbuild(
build_dir=build_dir,
is_source=False,
template_fields=template_fields
)
except ConfigError as e:
print(f"Configuration error in onbuild: {e}")
except MethodError as e:
print(f"Method error in onbuild: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-versioningit