CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-versioningit

Versioning It with your Version In Git - automatic package versioning based on VCS tags

Pending
Overview
Eval results
Files

method-system.mddocs/

Method System

Framework for loading and executing custom methods via entry points, module imports, or direct callables. This system enables complete customization of versioningit's pipeline steps while maintaining a consistent interface.

Capabilities

Method Specifications

Abstract specifications for different ways to reference methods in configuration.

class MethodSpec(ABC):
    """
    Abstract base class for method specifications parsed from versioningit
    configurations.
    """
    
    @abstractmethod
    def load(self, project_dir: str | Path) -> Callable:
        """
        Load & return the callable specified by the MethodSpec.
        
        Parameters:
        - project_dir: Project directory for loading project-local methods
        
        Returns:
        Callable: The loaded method function
        """

Entry Point Methods

Load methods from Python packaging entry points registered by packages.

@dataclass
class EntryPointSpec(MethodSpec):
    """
    A parsed method specification identifying a Python packaging entry point.
    """
    
    group: str
    """The name of the group in which to look up the entry point."""
    
    name: str  
    """The name of the entry point."""
    
    def load(self, _project_dir: str | Path) -> Callable:
        """
        Loads & returns the entry point.

        Returns:
        Callable: The loaded entry point function
        
        Raises:
        - ConfigError: if no such entry point exists
        - MethodError: if the loaded entry point is not callable
        """

Custom Module Methods

Load methods from local Python modules within the project.

@dataclass
class CustomMethodSpec(MethodSpec):
    """
    A parsed method specification identifying a callable in a local Python
    module.
    """
    
    module: str
    """The dotted name of the module containing the callable."""
    
    value: str
    """The name of the callable object within the module."""
    
    module_dir: Optional[str]
    """The directory in which the module is located; defaults to project_dir."""
    
    def load(self, project_dir: str | Path) -> Callable:
        """
        Loads the module and returns the callable.

        Parameters:
        - project_dir: Project directory added to sys.path for import
        
        Returns:
        Callable: The loaded method function
        
        Raises:
        - MethodError: if the object is not actually callable
        """

Direct Callable Methods

Wrap already-loaded callable objects.

@dataclass
class CallableSpec(MethodSpec):
    """
    A parsed method specification identifying a callable by the callable itself.
    """
    
    func: Callable
    """The callable."""
    
    def load(self, _project_dir: str | Path) -> Callable:
        """Return the callable."""

Method Execution

Loaded method with user-supplied parameters for pipeline execution.

@dataclass
class VersioningitMethod:
    """
    A loaded versioningit method and the user-supplied parameters to pass to it.
    """
    
    method: Callable
    """The loaded method."""
    
    params: dict[str, Any]
    """User-supplied parameters obtained from the original configuration."""
    
    def __call__(self, **kwargs: Any) -> Any:
        """
        Invokes the method with the given keyword arguments and the
        user-supplied parameters.
        
        Parameters:
        - **kwargs: Step-specific arguments (e.g., tag, version, description)
        
        Returns:
        Any: Return value from the method (type depends on pipeline step)
        """

Entry Point Groups

Versioningit uses these entry point groups for built-in method discovery:

# Entry point groups used by versioningit
ENTRY_POINT_GROUPS = {
    "versioningit.vcs": "VCS querying methods",
    "versioningit.tag2version": "Tag to version conversion methods", 
    "versioningit.next_version": "Next version calculation methods",
    "versioningit.format": "Version formatting methods",
    "versioningit.template_fields": "Template field generation methods",
    "versioningit.write": "File writing methods",
    "versioningit.onbuild": "Build-time file modification methods"
}

Usage Examples

Entry Point Method Configuration

# Using built-in entry points
config = {
    "vcs": {"method": "git"},
    "tag2version": {"method": "basic"},
    "next-version": {"method": "minor"},
    "format": {"method": "basic"},
    "template-fields": {"method": "basic"},
    "write": {"method": "basic"},
    "onbuild": {"method": "replace-version"}
}

Custom Module Method Configuration

# Custom method in project module
config = {
    "tag2version": {
        "method": "mypackage.versioning:custom_tag2version",
        "strip-prefix": "release-"
    }
}

# Method in separate directory
config = {
    "format": {
        "method": "scripts.version_tools:custom_format",
        "module-dir": "build_scripts"
    }
}

Implementing Custom Methods

# Custom VCS method
def custom_vcs_method(*, project_dir: Path, params: dict[str, Any]) -> VCSDescription:
    """Custom VCS implementation."""
    # Your custom logic here
    return VCSDescription(
        tag="v1.0.0",
        state="exact",
        branch="main", 
        fields={"custom_field": "value"}
    )

# Custom tag2version method  
def custom_tag2version(*, tag: str, params: dict[str, Any]) -> str:
    """Custom tag to version conversion."""
    prefix = params.get("strip-prefix", "")
    if tag.startswith(prefix):
        tag = tag[len(prefix):]
    return tag.lstrip("v")

# Custom format method
def custom_format(
    *,
    description: VCSDescription,
    base_version: str, 
    next_version: str,
    params: dict[str, Any]
) -> str:
    """Custom version formatting."""
    if description.state == "exact":
        return base_version
    elif description.state == "distance":
        distance = description.fields.get("distance", 0)
        return f"{next_version}.dev{distance}"
    else:
        return f"{next_version}.dev0+dirty"

Creating Entry Point Methods

# setup.py or pyproject.toml
[project.entry-points."versioningit.tag2version"]
mymethod = "mypackage.versioning:my_tag2version"

[project.entry-points."versioningit.format"] 
myformat = "mypackage.versioning:my_format"

Direct Callable Configuration

from versioningit import Versioningit
from versioningit.methods import CallableSpec, VersioningitMethod

def my_custom_method(*, tag: str, params: dict[str, Any]) -> str:
    return tag.replace("release-", "")

# Create method spec
callable_spec = CallableSpec(func=my_custom_method)
method = VersioningitMethod(
    method=callable_spec.load("."),
    params={}
)

# Use in configuration dict format  
config = {
    "vcs": {"method": "git"},
    "tag2version": my_custom_method,  # Direct callable
    "format": {"method": "basic"}
}

Method Loading and Execution

from versioningit.methods import EntryPointSpec, CustomMethodSpec

# Load entry point method
entry_spec = EntryPointSpec(group="versioningit.tag2version", name="basic")
basic_tag2version = entry_spec.load(".")

# Load custom method
custom_spec = CustomMethodSpec(
    module="mypackage.versioning",
    value="custom_tag2version",
    module_dir=None
)
custom_tag2version = custom_spec.load("/path/to/project")

# Execute methods
result1 = basic_tag2version(tag="v1.2.3", params={"rmprefix": "v"})
result2 = custom_tag2version(tag="release-1.2.3", params={"strip-prefix": "release-"})

Error Handling in Method System

from versioningit.methods import EntryPointSpec
from versioningit.errors import ConfigError, MethodError

try:
    spec = EntryPointSpec(group="versioningit.tag2version", name="nonexistent")
    method = spec.load(".")
except ConfigError as e:
    print(f"Entry point not found: {e}")

try:
    spec = CustomMethodSpec(
        module="nonexistent_module",
        value="some_function", 
        module_dir=None
    )
    method = spec.load(".")
except (ImportError, AttributeError) as e:
    print(f"Failed to load custom method: {e}")
except MethodError as e:
    print(f"Loaded object is not callable: {e}")

Advanced Method System Usage

from versioningit.core import Versioningit
from versioningit.config import Config
from versioningit.methods import CallableSpec, VersioningitMethod

# Custom pipeline with mix of methods
def custom_vcs(*, project_dir: Path, params: dict) -> VCSDescription:
    # Custom VCS logic
    pass

def custom_formatter(*, description: VCSDescription, base_version: str, 
                    next_version: str, params: dict) -> str:
    # Custom formatting logic  
    pass

# Build configuration programmatically
config_dict = {
    "vcs": custom_vcs,  # Direct callable
    "tag2version": {"method": "basic"},  # Entry point
    "next-version": {"method": "mypackage.custom:next_version"},  # Custom module
    "format": custom_formatter,  # Direct callable
    "template-fields": {"method": "basic"},
}

# Create Versioningit instance
vgit = Versioningit.from_project_dir(config=config_dict)
version = vgit.get_version()

Method Parameter Validation

def validated_method(*, tag: str, params: dict[str, Any]) -> str:
    """Example method with parameter validation."""
    
    # Validate required parameters
    if "required_param" not in params:
        raise ConfigError("required_param is mandatory")
    
    # Validate parameter types
    if not isinstance(params.get("numeric_param", 0), int):
        raise ConfigError("numeric_param must be an integer")
    
    # Validate parameter values
    valid_options = ["option1", "option2", "option3"]
    if params.get("choice_param") not in valid_options:
        raise ConfigError(f"choice_param must be one of {valid_options}")
    
    # Method implementation
    return tag.lstrip("v")

Install with Tessl CLI

npx tessl i tessl/pypi-versioningit

docs

build-integration.md

builtin-methods.md

core-operations.md

data-models.md

exceptions.md

index.md

method-system.md

versioningit-class.md

tile.json