A task runner for Python projects that enables task definition and execution through pyproject.toml configuration.
Comprehensive exception hierarchy for handling various error conditions with specific exit codes and descriptive error messages.
Root exception class for all taskipy-specific errors.
class TaskipyError(Exception):
"""Base exception class for all taskipy errors."""
exit_code = 1Errors related to pyproject.toml file handling and parsing.
class MissingPyProjectFileError(TaskipyError):
"""Raised when no pyproject.toml file is found in directory or parent directories."""
class MalformedPyProjectError(TaskipyError):
"""Raised when pyproject.toml file is malformed and cannot be parsed."""
def __init__(self, reason: Optional[str] = None):
"""
Initialize with optional reason for malformation.
Args:
reason: Specific reason for TOML parsing failure
"""
class MissingTaskipyTasksSectionError(TaskipyError):
"""Raised when [tool.taskipy.tasks] section is missing from pyproject.toml."""
exit_code = 127
class MissingTaskipySettingsSectionError(TaskipyError):
"""Raised when [tool.taskipy.settings] section is expected but missing."""
exit_code = 127
class EmptyTasksSectionError(TaskipyError):
"""Raised when tasks section exists but contains no tasks."""
exit_code = 127Errors that occur during task discovery and execution.
class TaskNotFoundError(TaskipyError):
"""Raised when a requested task cannot be found."""
exit_code = 127
def __init__(self, task_name: str, suggestion: Optional[str] = None):
"""
Initialize with task name and optional suggestion.
Args:
task_name: Name of the task that was not found
suggestion: Suggested similar task name (from difflib)
"""
class MalformedTaskError(TaskipyError):
"""Raised when a task definition is invalid or malformed."""
def __init__(self, task_name: str, reason: str):
"""
Initialize with task name and specific reason.
Args:
task_name: Name of the malformed task
reason: Specific reason for the malformation
"""Errors related to variable substitution and settings configuration.
class CircularVariableError(TaskipyError):
"""Raised when circular dependencies are detected in recursive variables."""
exit_code = 127
class InvalidVariableError(TaskipyError):
"""Raised when a variable definition is invalid."""
exit_code = 127
def __init__(self, variable: str, reason: str):
"""
Initialize with variable name and reason.
Args:
variable: Name of the invalid variable
reason: Specific reason for invalidity
"""
class InvalidRunnerTypeError(TaskipyError):
"""Raised when runner setting is not a string."""Errors related to command-line usage and argument parsing.
class InvalidUsageError(TaskipyError):
"""Raised when command-line arguments are invalid."""
exit_code = 127
def __init__(self, parser: ArgumentParser):
"""
Initialize with argument parser for usage message.
Args:
parser: ArgumentParser instance for generating usage help
"""from taskipy.cli import run
from taskipy.exceptions import TaskipyError, TaskNotFoundError
try:
exit_code = run(['nonexistent_task'])
except TaskNotFoundError as e:
print(f"Task not found: {e}")
if e.suggestion:
print(f"Did you mean: {e.suggestion}")
exit_code = e.exit_code
except TaskipyError as e:
print(f"Taskipy error: {e}")
exit_code = e.exit_codefrom taskipy.pyproject import PyProject
from taskipy.exceptions import (
MissingPyProjectFileError,
MalformedPyProjectError,
MissingTaskipyTasksSectionError
)
from pathlib import Path
try:
project = PyProject(Path('/path/to/project'))
tasks = project.tasks
except MissingPyProjectFileError:
print("Error: No pyproject.toml file found")
print("Make sure you're in a directory with a pyproject.toml file")
except MalformedPyProjectError as e:
print(f"Error: Invalid pyproject.toml syntax")
if e.reason:
print(f"Reason: {e.reason}")
except MissingTaskipyTasksSectionError:
print("Error: No [tool.taskipy.tasks] section found")
print("Add tasks to your pyproject.toml file")from taskipy.task_runner import TaskRunner
from taskipy.exceptions import CircularVariableError, InvalidVariableError, MalformedTaskError
from pathlib import Path
try:
runner = TaskRunner(Path('.'))
exit_code = runner.run('my_task', [])
except CircularVariableError:
print("Error: Circular dependency detected in variables")
print("Check your [tool.taskipy.variables] for circular references")
except InvalidVariableError as e:
print(f"Error: Invalid variable '{e.variable}'")
print(f"Reason: {e.reason}")
except MalformedTaskError as e:
print(f"Error: Task '{e.task}' is malformed")
print(f"Reason: {e.reason}")from taskipy.cli import run
from taskipy.exceptions import *
import sys
def safe_task_runner(task_name, args=None):
"""Safely run a task with comprehensive error handling."""
if args is None:
args = []
try:
return run([task_name] + args)
except TaskNotFoundError as e:
print(f"❌ Task '{e.task}' not found", file=sys.stderr)
if e.suggestion:
print(f"💡 Did you mean '{e.suggestion}'?", file=sys.stderr)
return e.exit_code
except MissingPyProjectFileError:
print("❌ No pyproject.toml file found", file=sys.stderr)
print("💡 Make sure you're in a Python project directory", file=sys.stderr)
return 1
except MalformedPyProjectError as e:
print("❌ Invalid pyproject.toml syntax", file=sys.stderr)
if e.reason:
print(f"📝 {e.reason}", file=sys.stderr)
return 1
except MissingTaskipyTasksSectionError:
print("❌ No tasks defined", file=sys.stderr)
print("💡 Add [tool.taskipy.tasks] section to pyproject.toml", file=sys.stderr)
return 127
except CircularVariableError:
print("❌ Circular variable dependency detected", file=sys.stderr)
print("💡 Check [tool.taskipy.variables] for circular references", file=sys.stderr)
return 127
except InvalidVariableError as e:
print(f"❌ Invalid variable '{e.variable}': {e.reason}", file=sys.stderr)
return 127
except MalformedTaskError as e:
print(f"❌ Task '{e.task}' is malformed: {e.reason}", file=sys.stderr)
return 1
except InvalidRunnerTypeError:
print("❌ Runner setting must be a string", file=sys.stderr)
print("💡 Check [tool.taskipy.settings.runner] in pyproject.toml", file=sys.stderr)
return 1
except TaskipyError as e:
print(f"❌ Taskipy error: {e}", file=sys.stderr)
return e.exit_code
except Exception as e:
print(f"❌ Unexpected error: {e}", file=sys.stderr)
return 1
# Usage
if __name__ == "__main__":
exit_code = safe_task_runner('test', ['--verbose'])
sys.exit(exit_code)Taskipy uses specific exit codes to indicate different types of failures:
import subprocess
import sys
# Running taskipy as subprocess
result = subprocess.run(['task', 'test'], capture_output=True)
if result.returncode == 0:
print("✅ Task completed successfully")
elif result.returncode == 127:
print("❌ Task not found or invalid usage")
print("Check task name and available tasks with 'task --list'")
else:
print(f"❌ Task failed with exit code {result.returncode}")
print("Check task output for details")could not find task "tets", did you mean "test"?no pyproject.toml file found in this directory or parent directoriespyproject.toml file is malformed and could not be read. reason: Invalid TOML syntaxvariable src_path is invalid. reason: expected variable to contain a string or be a table with a key "var"cannot resolve variables, found variables that depend on each other.the task "test" in the pyproject.toml file is malformed. reason: tasks must be strings, or dicts that contain { cmd, cwd, help, use_vars }import logging
logging.basicConfig(level=logging.DEBUG)
# Now taskipy operations will show more detailed informationtask --list to see available tasks# Use descriptive task names to avoid typos
[tool.taskipy.tasks]
run_tests = "pytest" # Clear and descriptive
test = "pytest" # Short but clear
# Validate variable references
[tool.taskipy.variables]
src_dir = "src"
# Make sure variables exist before referencing them
package_dir = { var = "{src_dir}/mypackage", recursive = true }Install with Tessl CLI
npx tessl i tessl/pypi-taskipy