Plugin for Poetry to enable dynamic versioning based on VCS tags
—
File modification and restoration functionality for applying dynamic versions to project files. Handles version substitution in source code, pyproject.toml updates, and automatic cleanup with comprehensive backup and restoration capabilities.
Core function that detects project configuration, extracts version information, and applies it to project files with automatic state management and cleanup preparation.
def _get_and_apply_version(
pyproject_path: Optional[Path] = None,
retain: bool = False,
force: bool = False,
io: bool = True
) -> Optional[str]:
"""
Main function to get version and apply it to project files.
Parameters:
- pyproject_path: Path to pyproject.toml (auto-detected if None)
- retain: If True, don't disable plugin in pyproject.toml during application
- force: Apply even if plugin is disabled in configuration
- io: If True, actually modify files (False for in-memory only)
Returns:
Optional[str]: Project name if successful, None if no project detected
Raises:
- RuntimeError: Unable to find pyproject.toml or determine version
"""Apply version information to pyproject.toml and configured source files, handling both Classic Poetry and PEP 621 project formats with comprehensive file creation and substitution.
def _apply_version(
name: str,
version: str,
instance: Version,
config: _Config,
pyproject_path: Path,
mode: _Mode,
retain: bool = False
) -> None:
"""
Apply version to pyproject.toml and configured files.
Parameters:
- name: Project name for state tracking
- version: Formatted version string to apply
- instance: Dunamai Version object for template rendering
- config: Configuration containing file and substitution settings
- pyproject_path: Path to project's pyproject.toml
- mode: Project mode (Classic or PEP 621)
- retain: Don't disable plugin after application if True
"""Restore all modified files to their original state, removing version changes and re-enabling plugin configuration to maintain repository cleanliness.
def _revert_version(retain: bool = False) -> None:
"""
Revert all version changes back to original state.
Parameters:
- retain: Don't re-enable plugin configuration if True
"""Apply version substitution to multiple files using configurable patterns and folder structures with support for different substitution modes and file creation.
def _substitute_version(name: str, version: str, folders: Sequence[_FolderConfig]) -> None:
"""
Substitute version in specified files using configured patterns.
Parameters:
- name: Project name for state tracking
- version: Version string to substitute into files
- folders: Folder configurations with file patterns and substitution rules
"""Core text substitution functionality supporting string and tuple modes for different version placeholder formats commonly used in Python projects.
def _substitute_version_in_text(version: str, content: str, patterns: Sequence[_SubPattern]) -> str:
"""
Perform text substitution with version using configured patterns.
Parameters:
- version: Version string to substitute
- content: Original text content
- patterns: Substitution patterns with mode specifications
Returns:
str: Modified content with version substitutions applied
"""class _FolderConfig:
def __init__(self, path: Path, files: Sequence[str], patterns: Sequence[_SubPattern]):
"""
Configuration for folder-specific substitution settings.
Parameters:
- path: Base path for file pattern matching
- files: File glob patterns to process
- patterns: Substitution patterns to apply
"""
@staticmethod
def from_config(config: _Config, root: Path) -> Sequence["_FolderConfig"]:
"""
Create folder configurations from main configuration and project root.
Parameters:
- config: Main configuration object
- root: Project root path for relative path resolution
Returns:
Sequence[_FolderConfig]: List of folder configurations
"""class _SubPattern:
def __init__(self, value: str, mode: str):
"""
Individual substitution pattern with mode specification.
Parameters:
- value: Regular expression pattern with two capture groups
- mode: Substitution mode ("str" or "tuple")
"""
@staticmethod
def from_config(config: Sequence[Union[str, Mapping]]) -> Sequence["_SubPattern"]:
"""
Create substitution patterns from configuration.
Parameters:
- config: Pattern configuration (strings or mappings with value/mode)
Returns:
Sequence[_SubPattern]: List of substitution pattern objects
"""class _ProjectState:
def __init__(
self,
path: Path,
original_version: Optional[str],
version: str,
mode: _Mode,
dynamic_array: Optional[tomlkit.items.Array],
substitutions: Optional[MutableMapping[Path, str]] = None,
) -> None:
"""
Track state for a single project during version application.
Parameters:
- path: Path to project's pyproject.toml
- original_version: Original version before modification
- version: Applied dynamic version
- mode: Project configuration mode (Classic or PEP 621)
- dynamic_array: PEP 621 dynamic array for restoration
- substitutions: Map of modified files to original content
"""class _State:
def __init__(self) -> None:
"""Global plugin state management."""
patched_core_poetry_create: bool # Whether Poetry core has been patched
cli_mode: bool # Whether running in CLI mode
projects: MutableMapping[str, _ProjectState] # Active project statesThe substitution system supports different modes for version placeholder replacement:
# Pattern captures quotes around version string
pattern = r"(^__version__\s*(?::.*?)?=\s*['\"])[^'\"]*(['\"])"
# Substitutes: __version__ = "1.2.3"# Pattern captures parentheses around version tuple
pattern = r"(^__version_tuple__\s*(?::.*?)?=\s*\()[^)]*(\))"
# Substitutes: __version_tuple__ = (1, 2, 3)
# With pre-release: __version_tuple__ = (1, 2, 3, "dev0", "abc123")from poetry_dynamic_versioning import _get_and_apply_version
from pathlib import Path
# Apply version to current project
project_name = _get_and_apply_version()
if project_name:
print(f"Applied version to project: {project_name}")
# Apply with specific path and options
project_name = _get_and_apply_version(
pyproject_path=Path("./my-project/pyproject.toml"),
retain=True, # Don't disable plugin
force=True, # Apply even if disabled
io=True # Actually modify files
)from poetry_dynamic_versioning import (
_apply_version, _get_version, _get_config, _Mode
)
from pathlib import Path
# Get configuration and version
config = _get_config_from_path()
version_str, version_obj = _get_version(config)
# Apply to specific project
pyproject_path = Path("./pyproject.toml")
_apply_version(
name="my-project",
version=version_str,
instance=version_obj,
config=config,
pyproject_path=pyproject_path,
mode=_Mode.Classic,
retain=False
)from poetry_dynamic_versioning import _substitute_version_in_text, _SubPattern
# Define custom patterns
patterns = [
_SubPattern(r'(VERSION = ")[^"]*(")', "str"),
_SubPattern(r'(version_info = \()[^)]*(\))', "tuple")
]
# Apply substitution to content
original_content = '''
VERSION = "0.0.0"
version_info = (0, 0, 0)
'''
new_content = _substitute_version_in_text("1.2.3", original_content, patterns)
print(new_content)
# VERSION = "1.2.3"
# version_info = (1, 2, 3)from poetry_dynamic_versioning import _revert_version, _state
# Check current project states
for name, state in _state.projects.items():
print(f"Project: {name}, Modified files: {len(state.substitutions)}")
# Revert all changes
_revert_version(retain=False) # Re-enable plugin after revert
print("All version changes reverted")from poetry_dynamic_versioning import _FolderConfig, _SubPattern
from pathlib import Path
# Create custom folder configuration
patterns = [_SubPattern(r'(__version__ = ")[^"]*(")', "str")]
folder_config = _FolderConfig(
path=Path("./src"),
files=["**/*.py"],
patterns=patterns
)
# Apply substitution to folder
from poetry_dynamic_versioning import _substitute_version
_substitute_version("my-project", "1.2.3", [folder_config])Configuration in pyproject.toml can create files with initial content:
[tool.poetry-dynamic-versioning.files."src/mypackage/_version.py"]
initial-content = """
# This file is automatically generated by poetry-dynamic-versioning
__version__ = "0.0.0"
__version_tuple__ = (0, 0, 0)
"""
persistent-substitution = trueThe system will create the file with initial content, then apply version substitution patterns to update the placeholders.
Install with Tessl CLI
npx tessl i tessl/pypi-poetry-dynamic-versioning