A task runner for Python projects that enables task definition and execution through pyproject.toml configuration.
Template variable system with recursive variable support for DRY task configuration and flexible command composition.
Represents a single variable with its value and recursion configuration.
class Variable:
def __init__(self, name: str, value: str, recursive: bool):
"""
Create a Variable instance.
Args:
name: Variable name identifier
value: Variable value (may contain template references)
recursive: Whether variable supports recursive substitution
"""Access to variable metadata and configuration.
@property
def name(self) -> str:
"""Variable name identifier."""
@property
def value(self) -> str:
"""Variable value string."""
@property
def recursive(self) -> bool:
"""Whether variable supports recursive substitution."""from taskipy.variable import Variable
# Simple variable
var = Variable('src_path', 'src/mypackage', False)
print(f"Variable: {var.name} = {var.value}")
# Recursive variable
recursive_var = Variable('full_path', '{base_path}/mypackage', True)
print(f"Recursive: {recursive_var.recursive}")Variables are typically configured in pyproject.toml rather than created directly:
[tool.taskipy.variables]
src_dir = "src"
test_dir = "tests"
package_name = "mypackage"Direct value assignment:
[tool.taskipy.variables]
src_path = "src/mypackage"
test_path = "tests"
python_version = "3.9"Variables that reference other variables:
[tool.taskipy.variables]
base_dir = "src"
package_dir = { var = "{base_dir}/mypackage", recursive = true }
test_path = { var = "{package_dir}/tests", recursive = true }Use variables in task commands with Python string formatting:
[tool.taskipy.variables]
src_path = "src/mypackage"
test_path = "tests"
[tool.taskipy.tasks]
lint = { cmd = "pylint {src_path}", use_vars = true }
test = { cmd = "pytest {test_path}", use_vars = true }Enable variables for all tasks:
[tool.taskipy.settings]
use_vars = true
[tool.taskipy.variables]
src_path = "src/mypackage"
test_path = "tests"
[tool.taskipy.tasks]
lint = "pylint {src_path}"
test = "pytest {test_path}"Control variable usage per task:
[tool.taskipy.variables]
src_path = "src/mypackage"
[tool.taskipy.tasks]
lint = { cmd = "pylint {src_path}", use_vars = true }
simple_task = "echo 'No variables here'" # use_vars defaults to falseVariables that reference other variables:
[tool.taskipy.variables]
base_dir = "src"
package_dir = { var = "{base_dir}/mypackage", recursive = true }
[tool.taskipy.tasks]
lint = { cmd = "pylint {package_dir}", use_vars = true }
# Resolves to: pylint src/mypackageComplex variable dependency chains:
[tool.taskipy.variables]
root = "."
src_dir = { var = "{root}/src", recursive = true }
package_dir = { var = "{src_dir}/mypackage", recursive = true }
module_dir = { var = "{package_dir}/core", recursive = true }
[tool.taskipy.tasks]
lint = { cmd = "pylint {module_dir}", use_vars = true }
# Resolves to: pylint ./src/mypackage/coreTaskipy detects and prevents circular variable dependencies:
[tool.taskipy.variables]
var_a = { var = "{var_b}/path", recursive = true }
var_b = { var = "{var_a}/other", recursive = true }
# This configuration will raise CircularVariableError[tool.taskipy.variables]
env = "development"
db_host = { var = "db-{env}.example.com", recursive = true }
api_url = { var = "https://api-{env}.example.com", recursive = true }
[tool.taskipy.tasks]
test_integration = { cmd = "pytest --db-host {db_host}", use_vars = true }[tool.taskipy.variables]
project_root = "."
src_dir = { var = "{project_root}/src", recursive = true }
tests_dir = { var = "{project_root}/tests", recursive = true }
docs_dir = { var = "{project_root}/docs", recursive = true }
build_dir = { var = "{project_root}/build", recursive = true }
[tool.taskipy.tasks]
lint = { cmd = "pylint {src_dir} {tests_dir}", use_vars = true }
test = { cmd = "pytest {tests_dir}", use_vars = true }
docs = { cmd = "sphinx-build {docs_dir} {build_dir}/docs", use_vars = true }[tool.taskipy.variables]
python_version = "3.9"
package_name = "mypackage"
test_pattern = "test_*.py"
coverage_threshold = "90"
[tool.taskipy.tasks]
test = {
cmd = "python{python_version} -m pytest {test_pattern} --cov={package_name} --cov-fail-under={coverage_threshold}",
use_vars = true
}Variables use Python's str.format() method, supporting all formatting options:
[tool.taskipy.variables]
version = "1.2.3"
width = "80"
[tool.taskipy.tasks]
version_info = { cmd = "echo 'Version: {version:>10}'", use_vars = true }
banner = { cmd = "echo '{version:=^{width}}'", use_vars = true }[tool.taskipy.variables]
debug = "true"
log_level = "DEBUG"
[tool.taskipy.tasks]
run_app = {
cmd = "python app.py{' --debug' if debug == 'true' else ''} --log-level {log_level}",
use_vars = true
}[tool.taskipy.tasks]
broken_task = { cmd = "echo {undefined_var}", use_vars = true }
# Raises MalformedTaskError: undefined_var variable expected[tool.taskipy.variables]
invalid_var = 123 # Must be string or dict with 'var' key
# Raises InvalidVariableError[tool.taskipy.variables]
a = { var = "{b}", recursive = true }
b = { var = "{a}", recursive = true }
# Raises CircularVariableError[tool.taskipy.variables]
# Use descriptive names
src_directory = "src"
test_directory = "tests"
python_executable = "python3.9"
# Use consistent naming patterns
api_host_dev = "api-dev.example.com"
api_host_prod = "api-prod.example.com"[tool.taskipy.variables]
# Group related variables
# Paths
src_path = "src"
test_path = "tests"
docs_path = "docs"
# Configuration
python_version = "3.9"
coverage_threshold = "85"
# Derived paths
package_path = { var = "{src_path}/mypackage", recursive = true }# Good: Simple, clear variable usage
[tool.taskipy.variables]
test_dir = "tests"
[tool.taskipy.tasks]
test = { cmd = "pytest {test_dir}", use_vars = true }
# Avoid: Overly complex recursive chains
[tool.taskipy.variables]
a = { var = "{b}/{c}", recursive = true }
b = { var = "{d}/{e}", recursive = true }
c = { var = "{f}", recursive = true }
# This becomes hard to debug and maintainInstall with Tessl CLI
npx tessl i tessl/pypi-taskipy