cook-build 0.7.1__py3-none-any.whl → 0.7.3__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.
- cook/__main__.py +4 -6
- cook/actions.py +13 -9
- cook/contexts.py +1 -1
- cook/controller.py +22 -26
- cook/manager.py +2 -2
- cook/util.py +1 -0
- {cook_build-0.7.1.dist-info → cook_build-0.7.3.dist-info}/METADATA +1 -1
- cook_build-0.7.3.dist-info/RECORD +14 -0
- {cook_build-0.7.1.dist-info → cook_build-0.7.3.dist-info}/WHEEL +1 -1
- cook_build-0.7.1.dist-info/RECORD +0 -14
- {cook_build-0.7.1.dist-info → cook_build-0.7.3.dist-info}/entry_points.txt +0 -0
- {cook_build-0.7.1.dist-info → cook_build-0.7.3.dist-info}/licenses/LICENSE +0 -0
- {cook_build-0.7.1.dist-info → cook_build-0.7.3.dist-info}/top_level.txt +0 -0
cook/__main__.py
CHANGED
|
@@ -342,12 +342,10 @@ def __main__(cli_args: list[str] | None = None) -> None:
|
|
|
342
342
|
]
|
|
343
343
|
)
|
|
344
344
|
if args.module:
|
|
345
|
-
#
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
finally:
|
|
350
|
-
sys.path.pop()
|
|
345
|
+
# Add the current working directory to the path so local modules can be
|
|
346
|
+
# imported.
|
|
347
|
+
sys.path.append(os.getcwd())
|
|
348
|
+
importlib.import_module(args.module)
|
|
351
349
|
elif args.recipe.is_file():
|
|
352
350
|
# Parse the recipe.
|
|
353
351
|
spec = importlib.util.spec_from_file_location("recipe", args.recipe)
|
cook/actions.py
CHANGED
|
@@ -36,6 +36,7 @@ executor with a deprecation warning.
|
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
38
|
import asyncio
|
|
39
|
+
import functools
|
|
39
40
|
import hashlib
|
|
40
41
|
import logging
|
|
41
42
|
import os
|
|
@@ -94,7 +95,9 @@ class FunctionAction(Action):
|
|
|
94
95
|
else:
|
|
95
96
|
# Run sync function in executor
|
|
96
97
|
loop = asyncio.get_running_loop()
|
|
97
|
-
await loop.run_in_executor(
|
|
98
|
+
await loop.run_in_executor(
|
|
99
|
+
None, functools.partial(self.func, task, *self.args, **self.kwargs)
|
|
100
|
+
)
|
|
98
101
|
|
|
99
102
|
|
|
100
103
|
class SubprocessAction(Action):
|
|
@@ -119,21 +122,22 @@ class SubprocessAction(Action):
|
|
|
119
122
|
True
|
|
120
123
|
"""
|
|
121
124
|
|
|
122
|
-
def __init__(self,
|
|
125
|
+
def __init__(self, args: str | list[str], **kwargs) -> None:
|
|
123
126
|
# Validate shell argument early
|
|
124
|
-
if kwargs.get("shell", False) and
|
|
127
|
+
if kwargs.get("shell", False) and not isinstance(args, str):
|
|
125
128
|
raise ValueError("shell=True requires string args")
|
|
126
129
|
self.args = args
|
|
127
130
|
self.kwargs = kwargs
|
|
128
131
|
|
|
129
132
|
async def execute(self, task: "Task") -> None:
|
|
130
133
|
# Get the command arguments
|
|
131
|
-
|
|
134
|
+
args = self.args
|
|
132
135
|
shell = self.kwargs.get("shell", False)
|
|
133
136
|
other_kwargs = {k: v for k, v in self.kwargs.items() if k != "shell"}
|
|
134
137
|
|
|
135
138
|
# Create the subprocess
|
|
136
139
|
if shell:
|
|
140
|
+
assert isinstance(args, str)
|
|
137
141
|
process = await asyncio.create_subprocess_shell(args, **other_kwargs)
|
|
138
142
|
else:
|
|
139
143
|
# Exec mode: args can be a string (single command) or list
|
|
@@ -164,7 +168,7 @@ class SubprocessAction(Action):
|
|
|
164
168
|
@property
|
|
165
169
|
def hexdigest(self) -> str:
|
|
166
170
|
hasher = hashlib.sha1()
|
|
167
|
-
|
|
171
|
+
args = self.args
|
|
168
172
|
if isinstance(args, str):
|
|
169
173
|
hasher.update(args.encode())
|
|
170
174
|
else:
|
|
@@ -173,7 +177,7 @@ class SubprocessAction(Action):
|
|
|
173
177
|
return hasher.hexdigest()
|
|
174
178
|
|
|
175
179
|
def __repr__(self) -> str:
|
|
176
|
-
args
|
|
180
|
+
args = self.args
|
|
177
181
|
if not isinstance(args, str):
|
|
178
182
|
args = " ".join(map(shlex.quote, args))
|
|
179
183
|
return f"{self.__class__.__name__}({repr(args)})"
|
|
@@ -196,13 +200,13 @@ class CompositeAction(Action):
|
|
|
196
200
|
|
|
197
201
|
@property
|
|
198
202
|
def hexdigest(self) -> str | None:
|
|
199
|
-
|
|
203
|
+
hasher = hashlib.sha1()
|
|
200
204
|
for action in self.actions:
|
|
201
205
|
hexdigest = action.hexdigest
|
|
202
206
|
if hexdigest is None:
|
|
203
207
|
return None
|
|
204
|
-
|
|
205
|
-
return
|
|
208
|
+
hasher.update(bytearray.fromhex(hexdigest))
|
|
209
|
+
return hasher.hexdigest()
|
|
206
210
|
|
|
207
211
|
|
|
208
212
|
class ModuleAction(SubprocessAction):
|
cook/contexts.py
CHANGED
|
@@ -152,7 +152,7 @@ class create_target_directories(Context):
|
|
|
152
152
|
create = self.manager.create_task(
|
|
153
153
|
name,
|
|
154
154
|
action=actions.FunctionAction(
|
|
155
|
-
lambda _
|
|
155
|
+
lambda _, p=target.parent: p.mkdir(parents=True, exist_ok=True)
|
|
156
156
|
),
|
|
157
157
|
)
|
|
158
158
|
task.task_dependencies.append(create)
|
cook/controller.py
CHANGED
|
@@ -39,11 +39,6 @@ QUERIES = {
|
|
|
39
39
|
"last_digested" TIMESTAMP NOT NULL
|
|
40
40
|
);
|
|
41
41
|
""",
|
|
42
|
-
"select_task": """
|
|
43
|
-
SELECT "digest"
|
|
44
|
-
FROM "files"
|
|
45
|
-
WHERE "name" = :name
|
|
46
|
-
""",
|
|
47
42
|
"upsert_task_completed": """
|
|
48
43
|
INSERT INTO "tasks" ("name", "digest", "last_completed")
|
|
49
44
|
VALUES (:name, :digest, :last_completed)
|
|
@@ -80,7 +75,6 @@ class Controller:
|
|
|
80
75
|
def __init__(self, dependencies: nx.DiGraph, connection: Connection) -> None:
|
|
81
76
|
self.dependencies = dependencies
|
|
82
77
|
self.connection = connection
|
|
83
|
-
self._digest_cache: dict[Path, tuple[float, bytes]] = {}
|
|
84
78
|
|
|
85
79
|
def resolve_stale_tasks(self, tasks: list["Task"] | None = None) -> set["Task"]:
|
|
86
80
|
self.is_stale(tasks or list(self.dependencies))
|
|
@@ -88,7 +82,7 @@ class Controller:
|
|
|
88
82
|
node for node, data in self.dependencies.nodes(True) if data.get("is_stale")
|
|
89
83
|
}
|
|
90
84
|
|
|
91
|
-
def _evaluate_task_hexdigest(self, task: "Task") -> str
|
|
85
|
+
def _evaluate_task_hexdigest(self, task: "Task") -> str:
|
|
92
86
|
"""
|
|
93
87
|
Evaluate the digest of a task by combining the digest of all its dependencies.
|
|
94
88
|
"""
|
|
@@ -102,8 +96,7 @@ class Controller:
|
|
|
102
96
|
)
|
|
103
97
|
dependency = Path(dependency).resolve()
|
|
104
98
|
if not dependency.is_file():
|
|
105
|
-
|
|
106
|
-
return None
|
|
99
|
+
raise FileNotFoundError(f"dependency {dependency} of {task} is missing")
|
|
107
100
|
dependencies.append(dependency)
|
|
108
101
|
|
|
109
102
|
hasher = hashlib.sha1()
|
|
@@ -198,9 +191,11 @@ class Controller:
|
|
|
198
191
|
return True
|
|
199
192
|
|
|
200
193
|
# If one of the dependencies is missing, the task is stale.
|
|
201
|
-
|
|
202
|
-
|
|
194
|
+
try:
|
|
195
|
+
current_digest = self._evaluate_task_hexdigest(task)
|
|
196
|
+
except FileNotFoundError:
|
|
203
197
|
LOGGER.debug("%s is stale because one of its dependencies is missing", task)
|
|
198
|
+
return True
|
|
204
199
|
|
|
205
200
|
# If the digest has changed, the task is stale.
|
|
206
201
|
(cached_digest,) = cached_digest
|
|
@@ -290,8 +285,10 @@ class Controller:
|
|
|
290
285
|
dep_futures = [task_futures[dep] for dep in dep_tasks]
|
|
291
286
|
await asyncio.gather(*dep_futures)
|
|
292
287
|
|
|
293
|
-
|
|
294
|
-
|
|
288
|
+
digest: str | None = None
|
|
289
|
+
if not dry_run:
|
|
290
|
+
digest = self._evaluate_task_hexdigest(task)
|
|
291
|
+
start: datetime | None = None
|
|
295
292
|
|
|
296
293
|
try:
|
|
297
294
|
# Log what we're doing
|
|
@@ -307,22 +304,19 @@ class Controller:
|
|
|
307
304
|
" action: %s",
|
|
308
305
|
task.action,
|
|
309
306
|
)
|
|
310
|
-
else:
|
|
311
|
-
LOGGER.log(
|
|
312
|
-
logging.DEBUG if task.name.startswith("_") else logging.INFO,
|
|
313
|
-
"executing %s ...",
|
|
314
|
-
task,
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
# Update DB for start
|
|
318
|
-
if not dry_run:
|
|
319
|
-
params = {"name": task.name, "last_started": start}
|
|
320
|
-
self.connection.execute(QUERIES["upsert_task_started"], params)
|
|
321
|
-
self.connection.commit()
|
|
322
307
|
|
|
323
308
|
# Execute the task
|
|
324
309
|
if not dry_run:
|
|
325
310
|
async with semaphore:
|
|
311
|
+
start = datetime.now()
|
|
312
|
+
LOGGER.log(
|
|
313
|
+
logging.DEBUG if task.name.startswith("_") else logging.INFO,
|
|
314
|
+
"executing %s ...",
|
|
315
|
+
task,
|
|
316
|
+
)
|
|
317
|
+
params = {"name": task.name, "last_started": start}
|
|
318
|
+
self.connection.execute(QUERIES["upsert_task_started"], params)
|
|
319
|
+
self.connection.commit()
|
|
326
320
|
await task.execute()
|
|
327
321
|
|
|
328
322
|
# Check that all targets were created
|
|
@@ -335,6 +329,7 @@ class Controller:
|
|
|
335
329
|
|
|
336
330
|
# Update DB for completion
|
|
337
331
|
if not dry_run:
|
|
332
|
+
assert digest is not None
|
|
338
333
|
params = {
|
|
339
334
|
"name": task.name,
|
|
340
335
|
"digest": digest,
|
|
@@ -344,6 +339,7 @@ class Controller:
|
|
|
344
339
|
self.connection.commit()
|
|
345
340
|
|
|
346
341
|
# Log completion
|
|
342
|
+
assert start is not None
|
|
347
343
|
delta = util.format_timedelta(datetime.now() - start)
|
|
348
344
|
LOGGER.log(
|
|
349
345
|
logging.DEBUG if task.name.startswith("_") else logging.INFO,
|
|
@@ -362,7 +358,7 @@ class Controller:
|
|
|
362
358
|
self.connection.execute(QUERIES["upsert_task_failed"], params)
|
|
363
359
|
self.connection.commit()
|
|
364
360
|
|
|
365
|
-
delta = util.format_timedelta(datetime.now() - start)
|
|
361
|
+
delta = util.format_timedelta(datetime.now() - start) if start else "?"
|
|
366
362
|
LOGGER.exception("failed to execute %s after %s", task, delta)
|
|
367
363
|
raise util.FailedTaskError(ex, task=task) from ex
|
|
368
364
|
|
cook/manager.py
CHANGED
|
@@ -63,7 +63,7 @@ class Manager:
|
|
|
63
63
|
raise ValueError(f"{context} did not return a task")
|
|
64
64
|
self.tasks[task.name] = task
|
|
65
65
|
return task
|
|
66
|
-
except:
|
|
66
|
+
except Exception:
|
|
67
67
|
filename, lineno = util.get_location()
|
|
68
68
|
LOGGER.exception(
|
|
69
69
|
"failed to create task with name '%s' at %s:%d", name, filename, lineno
|
|
@@ -150,7 +150,7 @@ def create_task(
|
|
|
150
150
|
|
|
151
151
|
Args:
|
|
152
152
|
name: Name of the new task. Defaults to the string representation of the first
|
|
153
|
-
|
|
153
|
+
target if not provided.
|
|
154
154
|
action: Action to execute or a string for shell commands.
|
|
155
155
|
targets: Paths for files to be generated.
|
|
156
156
|
dependencies: Paths to files on which this task depends.
|
cook/util.py
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
cook/__init__.py,sha256=SCa9_i6B84IzSAwq0wnSQqvycyL4dvTO7dRIysJXZj4,179
|
|
2
|
+
cook/__main__.py,sha256=3vnDZOJLTMJGRQzPolqurVzKJ7KCUjosBuuUkW22tKY,13192
|
|
3
|
+
cook/actions.py,sha256=yxo_LQ42iejq0K92OnYs7x2GaTuyNK3ZmqnRikQUgWs,7537
|
|
4
|
+
cook/contexts.py,sha256=-L4o_b_XPNZ_MBuYAgR1LYbptWPankDyj-00Pji7EVg,10710
|
|
5
|
+
cook/controller.py,sha256=vs9wBfvdQzE5RGZxZtKef6hb8rUOKKMp_kuPnmVAf9s,13432
|
|
6
|
+
cook/manager.py,sha256=sLlqPjsi81UTSHNhNj-ZTDilCdVmEF992N94yxWPOlU,6247
|
|
7
|
+
cook/task.py,sha256=-LNMwHdFlTUG45DF_QyWlIHw55_n_u2Xf5beKB7LrdY,2308
|
|
8
|
+
cook/util.py,sha256=Ssk_79cB7tvxEk_I9VAjjzmEp1bJ95ELZHCAx6sybyo,2459
|
|
9
|
+
cook_build-0.7.3.dist-info/licenses/LICENSE,sha256=3Nuj_WTTcz7JDg4-9EzNf6vHlKRWpdLUccg-pvoZ3WE,1500
|
|
10
|
+
cook_build-0.7.3.dist-info/METADATA,sha256=M83L-Uar5tKKLB2OyqQ1IS3wxe-idBZIvH2foLTXxAE,4586
|
|
11
|
+
cook_build-0.7.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
12
|
+
cook_build-0.7.3.dist-info/entry_points.txt,sha256=5UP0ZmmxSNKevTVISUJxmdXEQsKrI4n54OQYkjrdX2c,48
|
|
13
|
+
cook_build-0.7.3.dist-info/top_level.txt,sha256=ewNQIn2oRSYV98vAsUnw88u2Q8XHKhAz70ed2PEdR2c,5
|
|
14
|
+
cook_build-0.7.3.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
cook/__init__.py,sha256=SCa9_i6B84IzSAwq0wnSQqvycyL4dvTO7dRIysJXZj4,179
|
|
2
|
-
cook/__main__.py,sha256=4sO22TsNTt3oirT71dJjZJwmynsRyUGT-CHJNlDgCkk,13242
|
|
3
|
-
cook/actions.py,sha256=1VLyWL-pACuWpAjIyNWPBeu6wlKkoqi6t3lHr6hV0EQ,7399
|
|
4
|
-
cook/contexts.py,sha256=AMKO7Uz-nI52OsdPQ_zCLXjOf77V4pgzvDyj6-N8Ods,10705
|
|
5
|
-
cook/controller.py,sha256=vpF3QhiM3HImaI0rHJ58T8i5AQi5_P4Z2p42qFvG1po,13426
|
|
6
|
-
cook/manager.py,sha256=Y-QGVw9x8ZpSBMRALJIHgcMmIZulAV9KxwtBJz35Gpw,6241
|
|
7
|
-
cook/task.py,sha256=-LNMwHdFlTUG45DF_QyWlIHw55_n_u2Xf5beKB7LrdY,2308
|
|
8
|
-
cook/util.py,sha256=15MMG07CYZZ-YdFE_2jzRRTaqHMsw83UFg0s7e72MhI,2435
|
|
9
|
-
cook_build-0.7.1.dist-info/licenses/LICENSE,sha256=3Nuj_WTTcz7JDg4-9EzNf6vHlKRWpdLUccg-pvoZ3WE,1500
|
|
10
|
-
cook_build-0.7.1.dist-info/METADATA,sha256=8CK_rxir23FBjEo5H3IhxB1tkzXlMrolpAuhwqYLP4g,4586
|
|
11
|
-
cook_build-0.7.1.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
12
|
-
cook_build-0.7.1.dist-info/entry_points.txt,sha256=5UP0ZmmxSNKevTVISUJxmdXEQsKrI4n54OQYkjrdX2c,48
|
|
13
|
-
cook_build-0.7.1.dist-info/top_level.txt,sha256=ewNQIn2oRSYV98vAsUnw88u2Q8XHKhAz70ed2PEdR2c,5
|
|
14
|
-
cook_build-0.7.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|