0
# Error Handling
1
2
Comprehensive exception hierarchy for handling various error conditions with specific exit codes and descriptive error messages.
3
4
## Capabilities
5
6
### Base Exception Class
7
8
Root exception class for all taskipy-specific errors.
9
10
```python { .api }
11
class TaskipyError(Exception):
12
"""Base exception class for all taskipy errors."""
13
exit_code = 1
14
```
15
16
### Configuration Errors
17
18
Errors related to pyproject.toml file handling and parsing.
19
20
```python { .api }
21
class MissingPyProjectFileError(TaskipyError):
22
"""Raised when no pyproject.toml file is found in directory or parent directories."""
23
24
class MalformedPyProjectError(TaskipyError):
25
"""Raised when pyproject.toml file is malformed and cannot be parsed."""
26
def __init__(self, reason: Optional[str] = None):
27
"""
28
Initialize with optional reason for malformation.
29
30
Args:
31
reason: Specific reason for TOML parsing failure
32
"""
33
34
class MissingTaskipyTasksSectionError(TaskipyError):
35
"""Raised when [tool.taskipy.tasks] section is missing from pyproject.toml."""
36
exit_code = 127
37
38
class MissingTaskipySettingsSectionError(TaskipyError):
39
"""Raised when [tool.taskipy.settings] section is expected but missing."""
40
exit_code = 127
41
42
class EmptyTasksSectionError(TaskipyError):
43
"""Raised when tasks section exists but contains no tasks."""
44
exit_code = 127
45
```
46
47
### Task Execution Errors
48
49
Errors that occur during task discovery and execution.
50
51
```python { .api }
52
class TaskNotFoundError(TaskipyError):
53
"""Raised when a requested task cannot be found."""
54
exit_code = 127
55
56
def __init__(self, task_name: str, suggestion: Optional[str] = None):
57
"""
58
Initialize with task name and optional suggestion.
59
60
Args:
61
task_name: Name of the task that was not found
62
suggestion: Suggested similar task name (from difflib)
63
"""
64
65
class MalformedTaskError(TaskipyError):
66
"""Raised when a task definition is invalid or malformed."""
67
68
def __init__(self, task_name: str, reason: str):
69
"""
70
Initialize with task name and specific reason.
71
72
Args:
73
task_name: Name of the malformed task
74
reason: Specific reason for the malformation
75
"""
76
```
77
78
### Variable and Settings Errors
79
80
Errors related to variable substitution and settings configuration.
81
82
```python { .api }
83
class CircularVariableError(TaskipyError):
84
"""Raised when circular dependencies are detected in recursive variables."""
85
exit_code = 127
86
87
class InvalidVariableError(TaskipyError):
88
"""Raised when a variable definition is invalid."""
89
exit_code = 127
90
91
def __init__(self, variable: str, reason: str):
92
"""
93
Initialize with variable name and reason.
94
95
Args:
96
variable: Name of the invalid variable
97
reason: Specific reason for invalidity
98
"""
99
100
class InvalidRunnerTypeError(TaskipyError):
101
"""Raised when runner setting is not a string."""
102
```
103
104
### Usage Errors
105
106
Errors related to command-line usage and argument parsing.
107
108
```python { .api }
109
class InvalidUsageError(TaskipyError):
110
"""Raised when command-line arguments are invalid."""
111
exit_code = 127
112
113
def __init__(self, parser: ArgumentParser):
114
"""
115
Initialize with argument parser for usage message.
116
117
Args:
118
parser: ArgumentParser instance for generating usage help
119
"""
120
```
121
122
## Usage Examples
123
124
### Basic Error Handling
125
126
```python
127
from taskipy.cli import run
128
from taskipy.exceptions import TaskipyError, TaskNotFoundError
129
130
try:
131
exit_code = run(['nonexistent_task'])
132
except TaskNotFoundError as e:
133
print(f"Task not found: {e}")
134
if e.suggestion:
135
print(f"Did you mean: {e.suggestion}")
136
exit_code = e.exit_code
137
except TaskipyError as e:
138
print(f"Taskipy error: {e}")
139
exit_code = e.exit_code
140
```
141
142
### Configuration Error Handling
143
144
```python
145
from taskipy.pyproject import PyProject
146
from taskipy.exceptions import (
147
MissingPyProjectFileError,
148
MalformedPyProjectError,
149
MissingTaskipyTasksSectionError
150
)
151
from pathlib import Path
152
153
try:
154
project = PyProject(Path('/path/to/project'))
155
tasks = project.tasks
156
except MissingPyProjectFileError:
157
print("Error: No pyproject.toml file found")
158
print("Make sure you're in a directory with a pyproject.toml file")
159
except MalformedPyProjectError as e:
160
print(f"Error: Invalid pyproject.toml syntax")
161
if e.reason:
162
print(f"Reason: {e.reason}")
163
except MissingTaskipyTasksSectionError:
164
print("Error: No [tool.taskipy.tasks] section found")
165
print("Add tasks to your pyproject.toml file")
166
```
167
168
### Variable Error Handling
169
170
```python
171
from taskipy.task_runner import TaskRunner
172
from taskipy.exceptions import CircularVariableError, InvalidVariableError, MalformedTaskError
173
from pathlib import Path
174
175
try:
176
runner = TaskRunner(Path('.'))
177
exit_code = runner.run('my_task', [])
178
except CircularVariableError:
179
print("Error: Circular dependency detected in variables")
180
print("Check your [tool.taskipy.variables] for circular references")
181
except InvalidVariableError as e:
182
print(f"Error: Invalid variable '{e.variable}'")
183
print(f"Reason: {e.reason}")
184
except MalformedTaskError as e:
185
print(f"Error: Task '{e.task}' is malformed")
186
print(f"Reason: {e.reason}")
187
```
188
189
### Complete Error Handling Pattern
190
191
```python
192
from taskipy.cli import run
193
from taskipy.exceptions import *
194
import sys
195
196
def safe_task_runner(task_name, args=None):
197
"""Safely run a task with comprehensive error handling."""
198
if args is None:
199
args = []
200
201
try:
202
return run([task_name] + args)
203
204
except TaskNotFoundError as e:
205
print(f"β Task '{e.task}' not found", file=sys.stderr)
206
if e.suggestion:
207
print(f"π‘ Did you mean '{e.suggestion}'?", file=sys.stderr)
208
return e.exit_code
209
210
except MissingPyProjectFileError:
211
print("β No pyproject.toml file found", file=sys.stderr)
212
print("π‘ Make sure you're in a Python project directory", file=sys.stderr)
213
return 1
214
215
except MalformedPyProjectError as e:
216
print("β Invalid pyproject.toml syntax", file=sys.stderr)
217
if e.reason:
218
print(f"π {e.reason}", file=sys.stderr)
219
return 1
220
221
except MissingTaskipyTasksSectionError:
222
print("β No tasks defined", file=sys.stderr)
223
print("π‘ Add [tool.taskipy.tasks] section to pyproject.toml", file=sys.stderr)
224
return 127
225
226
except CircularVariableError:
227
print("β Circular variable dependency detected", file=sys.stderr)
228
print("π‘ Check [tool.taskipy.variables] for circular references", file=sys.stderr)
229
return 127
230
231
except InvalidVariableError as e:
232
print(f"β Invalid variable '{e.variable}': {e.reason}", file=sys.stderr)
233
return 127
234
235
except MalformedTaskError as e:
236
print(f"β Task '{e.task}' is malformed: {e.reason}", file=sys.stderr)
237
return 1
238
239
except InvalidRunnerTypeError:
240
print("β Runner setting must be a string", file=sys.stderr)
241
print("π‘ Check [tool.taskipy.settings.runner] in pyproject.toml", file=sys.stderr)
242
return 1
243
244
except TaskipyError as e:
245
print(f"β Taskipy error: {e}", file=sys.stderr)
246
return e.exit_code
247
248
except Exception as e:
249
print(f"β Unexpected error: {e}", file=sys.stderr)
250
return 1
251
252
# Usage
253
if __name__ == "__main__":
254
exit_code = safe_task_runner('test', ['--verbose'])
255
sys.exit(exit_code)
256
```
257
258
## Exit Codes
259
260
Taskipy uses specific exit codes to indicate different types of failures:
261
262
- **0**: Success
263
- **1**: General error (TaskipyError, configuration issues)
264
- **127**: Command not found or invalid usage (TaskNotFoundError, InvalidUsageError, missing sections)
265
266
### Exit Code Usage
267
268
```python
269
import subprocess
270
import sys
271
272
# Running taskipy as subprocess
273
result = subprocess.run(['task', 'test'], capture_output=True)
274
275
if result.returncode == 0:
276
print("β Task completed successfully")
277
elif result.returncode == 127:
278
print("β Task not found or invalid usage")
279
print("Check task name and available tasks with 'task --list'")
280
else:
281
print(f"β Task failed with exit code {result.returncode}")
282
print("Check task output for details")
283
```
284
285
## Error Messages
286
287
### Task Not Found
288
289
```
290
could not find task "tets", did you mean "test"?
291
```
292
293
### Missing Configuration
294
295
```
296
no pyproject.toml file found in this directory or parent directories
297
```
298
299
### Malformed Configuration
300
301
```
302
pyproject.toml file is malformed and could not be read. reason: Invalid TOML syntax
303
```
304
305
### Variable Errors
306
307
```
308
variable src_path is invalid. reason: expected variable to contain a string or be a table with a key "var"
309
```
310
311
```
312
cannot resolve variables, found variables that depend on each other.
313
```
314
315
### Task Errors
316
317
```
318
the task "test" in the pyproject.toml file is malformed. reason: tasks must be strings, or dicts that contain { cmd, cwd, help, use_vars }
319
```
320
321
## Debugging Tips
322
323
### Enable Verbose Error Reporting
324
325
```python
326
import logging
327
logging.basicConfig(level=logging.DEBUG)
328
329
# Now taskipy operations will show more detailed information
330
```
331
332
### Common Error Scenarios
333
334
1. **Typo in Task Name**: Use `task --list` to see available tasks
335
2. **Missing pyproject.toml**: Ensure you're in the correct directory
336
3. **TOML Syntax Error**: Validate TOML syntax using online validators
337
4. **Circular Variables**: Draw variable dependency graph to identify cycles
338
5. **Missing Variable**: Check variable names match exactly (case-sensitive)
339
340
### Error Prevention
341
342
```toml
343
# Use descriptive task names to avoid typos
344
[tool.taskipy.tasks]
345
run_tests = "pytest" # Clear and descriptive
346
test = "pytest" # Short but clear
347
348
# Validate variable references
349
[tool.taskipy.variables]
350
src_dir = "src"
351
# Make sure variables exist before referencing them
352
package_dir = { var = "{src_dir}/mypackage", recursive = true }
353
```