tasktree 0.0.21__py3-none-any.whl → 0.0.22__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tasktree/state.py CHANGED
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import json
6
6
  from dataclasses import dataclass, field
7
7
  from pathlib import Path
8
- from typing import Any
8
+ from typing import Any, Set
9
9
 
10
10
 
11
11
  @dataclass
@@ -43,7 +43,7 @@ class TaskState:
43
43
  class StateManager:
44
44
  """
45
45
  Manages the .tasktree-state file.
46
- @athena: 44713c70e04e
46
+ @athena: 3dd3447bb53b
47
47
  """
48
48
 
49
49
  STATE_FILE = ".tasktree-state"
@@ -64,17 +64,16 @@ class StateManager:
64
64
  def load(self) -> None:
65
65
  """
66
66
  Load state from file if it exists.
67
- @athena: 11748af0886c
67
+ @athena: e0cf9097c590
68
68
  """
69
69
  if self.state_path.exists():
70
70
  try:
71
71
  with open(self.state_path, "r") as f:
72
72
  data = json.load(f)
73
73
  self._state = {
74
- key: TaskState.from_dict(value)
75
- for key, value in data.items()
74
+ key: TaskState.from_dict(value) for key, value in data.items()
76
75
  }
77
- except (json.JSONDecodeError, KeyError) as e:
76
+ except (json.JSONDecodeError, KeyError):
78
77
  # If state file is corrupted, start fresh
79
78
  self._state = {}
80
79
  self._loaded = True
@@ -116,13 +115,13 @@ class StateManager:
116
115
  self.load()
117
116
  self._state[cache_key] = state
118
117
 
119
- def prune(self, valid_task_hashes: set[str]) -> None:
118
+ def prune(self, valid_task_hashes: Set[str]) -> None:
120
119
  """
121
120
  Remove state entries for tasks that no longer exist.
122
121
 
123
122
  Args:
124
123
  valid_task_hashes: Set of valid task hashes from current recipe
125
- @athena: ce21bb523d49
124
+ @athena: 2717c6c244d3
126
125
  """
127
126
  if not self._loaded:
128
127
  self.load()
tasktree/substitution.py CHANGED
@@ -7,30 +7,30 @@ and {{ env.NAME }} placeholders with their corresponding values.
7
7
  """
8
8
 
9
9
  import re
10
- from random import choice
11
10
  from typing import Any
12
11
 
13
-
14
12
  # Pattern matches: {{ prefix.name }} with optional whitespace
15
13
  # Groups: (1) prefix (var|arg|env|tt), (2) name (identifier)
16
14
  PLACEHOLDER_PATTERN = re.compile(
17
- r'\{\{\s*(var|arg|env|tt)\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}'
15
+ r"\{\{\s*(var|arg|env|tt)\.([a-zA-Z_][a-zA-Z0-9_]*)\s*}}"
18
16
  )
19
17
 
20
18
  # Pattern matches: {{ dep.task_name.outputs.output_name }} with optional whitespace
21
19
  # Groups: (1) task_name (can include dots for namespacing), (2) output_name (identifier)
22
20
  DEP_OUTPUT_PATTERN = re.compile(
23
- r'\{\{\s*dep\.([a-zA-Z_][a-zA-Z0-9_.-]*)\.outputs\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}'
21
+ r"\{\{\s*dep\.([a-zA-Z_][a-zA-Z0-9_.-]*)\.outputs\.([a-zA-Z_][a-zA-Z0-9_]*)\s*}}"
24
22
  )
25
23
 
26
24
  # Pattern matches: {{ self.(inputs|outputs).name }} or {{ self.(inputs|outputs).0 }} with optional whitespace
27
25
  # Groups: (1) field (inputs|outputs), (2) name (identifier) or index (numeric)
28
26
  SELF_REFERENCE_PATTERN = re.compile(
29
- r'\{\{\s*self\.(inputs|outputs)\.([a-zA-Z_][a-zA-Z0-9_]*|[0-9]+)\s*\}\}'
27
+ r"\{\{\s*self\.(inputs|outputs)\.([a-zA-Z_][a-zA-Z0-9_]*|[0-9]+)\s*}}"
30
28
  )
31
29
 
32
30
 
33
- def substitute_variables(text: str | dict[str, Any], variables: dict[str, str]) -> str | dict[str, Any]:
31
+ def substitute_variables(
32
+ text: str | dict[str, Any], variables: dict[str, str]
33
+ ) -> str | dict[str, Any]:
34
34
  """
35
35
  Substitute {{ var.name }} placeholders with variable values.
36
36
 
@@ -52,13 +52,18 @@ def substitute_variables(text: str | dict[str, Any], variables: dict[str, str])
52
52
 
53
53
  for arg_name in text.keys():
54
54
  # Pull out and substitute the individual fields of an argument one at a time
55
- for field in [ "default", "min", "max" ]:
55
+ for field in ["default", "min", "max"]:
56
56
  if field in text[arg_name]:
57
- text[arg_name][field] = substitute_variables(text[arg_name][field], variables)
57
+ text[arg_name][field] = substitute_variables(
58
+ text[arg_name][field], variables
59
+ )
58
60
 
59
61
  # choices is a list of things
60
62
  if "choices" in text[arg_name]:
61
- text[arg_name]["choices"] = [substitute_variables(c, variables) for c in text[arg_name]["choices"]]
63
+ text[arg_name]["choices"] = [
64
+ substitute_variables(c, variables)
65
+ for c in text[arg_name]["choices"]
66
+ ]
62
67
 
63
68
  return text
64
69
  else:
@@ -88,7 +93,9 @@ def substitute_variables(text: str | dict[str, Any], variables: dict[str, str])
88
93
  return PLACEHOLDER_PATTERN.sub(replace_match, text)
89
94
 
90
95
 
91
- def substitute_arguments(text: str, args: dict[str, Any], exported_args: set[str] | None = None) -> str:
96
+ def substitute_arguments(
97
+ text: str, args: dict[str, Any], exported_args: set[str] | None = None
98
+ ) -> str:
92
99
  """
93
100
  Substitute {{ arg.name }} placeholders with argument values.
94
101
 
@@ -172,9 +179,7 @@ def substitute_environment(text: str) -> str:
172
179
 
173
180
  value = os.environ.get(name)
174
181
  if value is None:
175
- raise ValueError(
176
- f"Environment variable '{name}' is not set"
177
- )
182
+ raise ValueError(f"Environment variable '{name}' is not set")
178
183
 
179
184
  return value
180
185
 
@@ -203,6 +208,7 @@ def substitute_builtin_variables(text: str, builtin_vars: dict[str, str]) -> str
203
208
  'Root: /home/user/project'
204
209
  @athena: 716250e3a71f
205
210
  """
211
+
206
212
  def replace_match(match: re.Match) -> str:
207
213
  prefix = match.group(1)
208
214
  name = match.group(2)
@@ -226,7 +232,7 @@ def substitute_dependency_args(
226
232
  template_value: str,
227
233
  parent_task_name: str,
228
234
  parent_args: dict[str, Any],
229
- exported_args: set[str] | None = None
235
+ exported_args: set[str] | None = None,
230
236
  ) -> str:
231
237
  """
232
238
  Substitute {{ arg.* }} templates in dependency argument values.
@@ -357,6 +363,7 @@ def substitute_dependency_outputs(
357
363
  'Deploy dist/app.js'
358
364
  @athena: 1e537c8d579c
359
365
  """
366
+
360
367
  def replacer(match: re.Match) -> str:
361
368
  dep_task_name = match.group(1)
362
369
  output_name = match.group(2)
@@ -383,7 +390,11 @@ def substitute_dependency_outputs(
383
390
  # Look up the named output
384
391
  if output_name not in dep_task._output_map:
385
392
  available = list(dep_task._output_map.keys())
386
- available_msg = ", ".join(available) if available else "(none - all outputs are anonymous)"
393
+ available_msg = (
394
+ ", ".join(available)
395
+ if available
396
+ else "(none - all outputs are anonymous)"
397
+ )
387
398
  raise ValueError(
388
399
  f"Task '{current_task_name}' references output '{output_name}' "
389
400
  f"from task '{dep_task_name}', but '{dep_task_name}' has no output named '{output_name}'.\n"
@@ -447,6 +458,7 @@ def substitute_self_references(
447
458
  'cp *.txt out/result.txt'
448
459
  @athena: 9d997ff08eef
449
460
  """
461
+
450
462
  def replacer(match: re.Match) -> str:
451
463
  field = match.group(1) # "inputs" or "outputs"
452
464
  identifier = match.group(2) # name or numeric index
tasktree/types.py CHANGED
@@ -3,7 +3,6 @@
3
3
  import re
4
4
  from datetime import datetime
5
5
  from ipaddress import IPv4Address, IPv6Address, ip_address
6
- from pathlib import Path
7
6
  from typing import Any, Optional
8
7
 
9
8
  import click
@@ -22,7 +21,9 @@ class HostnameType(click.ParamType):
22
21
  r"^(?=.{1,253}$)(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})*\.?$"
23
22
  )
24
23
 
25
- def convert(self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> str:
24
+ def convert(
25
+ self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
26
+ ) -> str:
26
27
  """
27
28
  @athena: 8d921e52bcf2
28
29
  """
@@ -41,11 +42,11 @@ class EmailType(click.ParamType):
41
42
  name = "email"
42
43
 
43
44
  # Basic email validation (RFC 5322 simplified)
44
- EMAIL_PATTERN = re.compile(
45
- r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
46
- )
45
+ EMAIL_PATTERN = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
47
46
 
48
- def convert(self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> str:
47
+ def convert(
48
+ self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
49
+ ) -> str:
49
50
  """
50
51
  @athena: 25046aeb6e6f
51
52
  """
@@ -63,7 +64,9 @@ class IPType(click.ParamType):
63
64
 
64
65
  name = "ip"
65
66
 
66
- def convert(self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> str:
67
+ def convert(
68
+ self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
69
+ ) -> str:
67
70
  """
68
71
  @athena: d57618e5ad89
69
72
  """
@@ -82,7 +85,9 @@ class IPv4Type(click.ParamType):
82
85
 
83
86
  name = "ipv4"
84
87
 
85
- def convert(self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> str:
88
+ def convert(
89
+ self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
90
+ ) -> str:
86
91
  """
87
92
  @athena: 7ed2d17d1f1a
88
93
  """
@@ -101,7 +106,9 @@ class IPv6Type(click.ParamType):
101
106
 
102
107
  name = "ipv6"
103
108
 
104
- def convert(self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> str:
109
+ def convert(
110
+ self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
111
+ ) -> str:
105
112
  """
106
113
  @athena: 4b101e4d54cf
107
114
  """
@@ -120,7 +127,9 @@ class DateTimeType(click.ParamType):
120
127
 
121
128
  name = "datetime"
122
129
 
123
- def convert(self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]) -> str:
130
+ def convert(
131
+ self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
132
+ ) -> str:
124
133
  """
125
134
  @athena: 13fa66adfe94
126
135
  """
@@ -130,7 +139,11 @@ class DateTimeType(click.ParamType):
130
139
  return value
131
140
  except ValueError:
132
141
  pass
133
- self.fail(f"{value!r} is not a valid datetime (expected YYYY-MM-DDTHH:MM:SS format)", param, ctx)
142
+ self.fail(
143
+ f"{value!r} is not a valid datetime (expected YYYY-MM-DDTHH:MM:SS format)",
144
+ param,
145
+ ctx,
146
+ )
134
147
 
135
148
 
136
149
  # Type registry for dynamic parameter creation
@@ -149,7 +162,11 @@ TYPE_MAPPING = {
149
162
  }
150
163
 
151
164
 
152
- def get_click_type(type_name: str, min_val: int | float | None = None, max_val: int | float | None = None) -> click.ParamType:
165
+ def get_click_type(
166
+ type_name: str,
167
+ min_val: int | float | None = None,
168
+ max_val: int | float | None = None,
169
+ ) -> click.ParamType:
153
170
  """
154
171
  Get Click parameter type by name with optional range constraints.
155
172
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tasktree
3
- Version: 0.0.21
3
+ Version: 0.0.22
4
4
  Summary: A task automation tool with incremental execution
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: click>=8.1.0
@@ -10,7 +10,9 @@ Requires-Dist: pyyaml>=6.0
10
10
  Requires-Dist: rich>=13.0.0
11
11
  Requires-Dist: typer>=0.9.0
12
12
  Provides-Extra: dev
13
+ Requires-Dist: black>=26.1.0; extra == 'dev'
13
14
  Requires-Dist: pytest>=9.0.2; extra == 'dev'
15
+ Requires-Dist: ruff>=0.14.14; extra == 'dev'
14
16
  Description-Content-Type: text/markdown
15
17
 
16
18
  # Task Tree (tt)
@@ -255,20 +257,19 @@ tasks:
255
257
  cmd: go build -o dist/binary # Command to execute
256
258
  ```
257
259
 
260
+ **Task name constraints:**
261
+ - Task names cannot contain dots (`.`) - they are reserved for namespacing imported tasks
262
+ - Example: `build.release` is invalid as a task name, but valid as a reference to task `release` in namespace `build`
263
+
258
264
  ### Commands
259
265
 
260
- **Single-line commands** are executed directly via the configured shell:
266
+ All commands are executed by writing them to temporary script files. This provides consistent behavior and better shell syntax support:
261
267
 
262
268
  ```yaml
263
269
  tasks:
264
270
  build:
265
271
  cmd: cargo build --release
266
- ```
267
-
268
- **Multi-line commands** are written to temporary script files for proper execution:
269
272
 
270
- ```yaml
271
- tasks:
272
273
  deploy:
273
274
  cmd: |
274
275
  mkdir -p dist
@@ -276,7 +277,7 @@ tasks:
276
277
  rsync -av dist/ server:/opt/app/
277
278
  ```
278
279
 
279
- Multi-line commands preserve shell syntax (line continuations, heredocs, etc.) and support shebangs on Unix/macOS.
280
+ Commands preserve shell syntax (line continuations, heredocs, etc.) and support shebangs on Unix/macOS.
280
281
 
281
282
  Or use folded blocks for long single-line commands:
282
283
 
@@ -292,7 +293,7 @@ tasks:
292
293
 
293
294
  ### Execution Environments
294
295
 
295
- Configure custom shell environments for task execution:
296
+ Configure custom shell environments for task execution. Use the `preamble` field to add initialization code to all commands:
296
297
 
297
298
  ```yaml
298
299
  environments:
@@ -300,17 +301,14 @@ environments:
300
301
 
301
302
  bash-strict:
302
303
  shell: bash
303
- args: ['-c'] # For single-line: bash -c "command"
304
- preamble: | # For multi-line: prepended to script
304
+ preamble: | # Prepended to all commands
305
305
  set -euo pipefail
306
306
 
307
307
  python:
308
308
  shell: python
309
- args: ['-c']
310
309
 
311
310
  powershell:
312
311
  shell: powershell
313
- args: ['-ExecutionPolicy', 'Bypass', '-Command']
314
312
  preamble: |
315
313
  $ErrorActionPreference = 'Stop'
316
314
 
@@ -339,8 +337,8 @@ tasks:
339
337
  4. Platform default (bash on Unix, cmd on Windows)
340
338
 
341
339
  **Platform defaults** when no environments are configured:
342
- - **Unix/macOS**: bash with `-c` args
343
- - **Windows**: cmd with `/c` args
340
+ - **Unix/macOS**: bash
341
+ - **Windows**: cmd
344
342
 
345
343
  ### Docker Environments
346
344
 
@@ -0,0 +1,14 @@
1
+ tasktree/__init__.py,sha256=ZfI6vy-TxinN7eK0_XjfHUFq83pFV1XiHFrai_SHNqw,1251
2
+ tasktree/cli.py,sha256=588ngMB27T6FqUp5LIJ_x_UNoPRlMv39b1_U0a88tfE,23577
3
+ tasktree/docker.py,sha256=DAvdr6kZqkhx8J9fhsz26Z41hFPreANC60vdunvPX00,15050
4
+ tasktree/executor.py,sha256=UF2H-qCgkJ5E2QoYt3To3a-ypwTIYJT9oyWRcBw2CzQ,46800
5
+ tasktree/graph.py,sha256=Ya4ATQidj0rlHxvvbWF0Xx2J18MTpz2XPAHTcJXFcBg,23637
6
+ tasktree/hasher.py,sha256=fvs7vNBrHdAA6pX-ZLhNnWLtd5qdxjAVFJC8SDF-DhQ,6730
7
+ tasktree/parser.py,sha256=OUuME_ZqITAMQEL90w-yJFLKoEquF_IIa-By8MifMvI,101074
8
+ tasktree/state.py,sha256=R4ZXgfs_rwrOH9nEb4mOjuHa7gAVqi7QPVCi0CouNQA,3961
9
+ tasktree/substitution.py,sha256=hwox1m9r8p5b0Fcln12C63GexJHc5ZMUoqOrpqQLuPk,18663
10
+ tasktree/types.py,sha256=nCiPrxg3r3ZX4CoZ6L5FrkJO9mgz_lFsZ-98X8tYgns,5015
11
+ tasktree-0.0.22.dist-info/METADATA,sha256=fuvZWDbtlwfbQNOP_TMCDRSDGKdZYUTeROo3eqVcJ6o,54519
12
+ tasktree-0.0.22.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
13
+ tasktree-0.0.22.dist-info/entry_points.txt,sha256=lQINlvRYnimvteBbnhH84A9clTg8NnpEjCWqWkqg8KE,40
14
+ tasktree-0.0.22.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- tasktree/__init__.py,sha256=ZfI6vy-TxinN7eK0_XjfHUFq83pFV1XiHFrai_SHNqw,1251
2
- tasktree/cli.py,sha256=pyXMUENz1ldl8z9SPXBZ1zhOHzmvIRUGu2y5VwRUdgo,22960
3
- tasktree/docker.py,sha256=EeIUqptvDqSXbjWKXwWGhA4hWvsBjOk0Z-3cS3cqEOM,14857
4
- tasktree/executor.py,sha256=x0K-TIVRmiKuUlur1UTOpB2xOJOE_Cv3-3MlE7xUNEk,47271
5
- tasktree/graph.py,sha256=BjpLaa7EE2vmOcUlqhvp1KK7yT5VbXHQCbxljOhF84k,23603
6
- tasktree/hasher.py,sha256=UM-2dqRE-rspQUbhA-noGHc80-Hu-QaxCTPRb_UzPto,6661
7
- tasktree/parser.py,sha256=sXgotDdDQiNSmo7FaESFQ4j1OytaheyplJSdFrFXTYk,99286
8
- tasktree/state.py,sha256=0fxKbMNYbo-dCpCTTnKTmJhGRy72G5kLnh99FScMLGU,3985
9
- tasktree/substitution.py,sha256=2ubFn6jIX5qmtzyWSCxTut-_Cn0zew8KnwxMqDIPq1o,18545
10
- tasktree/types.py,sha256=2uHdUncfyfw4jNWbQTBUirew-frmTjuk1ZYNd15cyQQ,4908
11
- tasktree-0.0.21.dist-info/METADATA,sha256=nyjDhb1hdQMEiLx0t_qqonqPoyFZVMCrIg7xFIKxMs8,54353
12
- tasktree-0.0.21.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
13
- tasktree-0.0.21.dist-info/entry_points.txt,sha256=lQINlvRYnimvteBbnhH84A9clTg8NnpEjCWqWkqg8KE,40
14
- tasktree-0.0.21.dist-info/RECORD,,