cook-build 0.7.1__tar.gz → 0.7.2__tar.gz
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_build-0.7.1/src/cook_build.egg-info → cook_build-0.7.2}/PKG-INFO +1 -1
- {cook_build-0.7.1 → cook_build-0.7.2}/pyproject.toml +1 -1
- {cook_build-0.7.1 → cook_build-0.7.2}/src/cook/actions.py +4 -1
- {cook_build-0.7.1 → cook_build-0.7.2}/src/cook/contexts.py +1 -1
- {cook_build-0.7.1 → cook_build-0.7.2}/src/cook/controller.py +12 -14
- {cook_build-0.7.1 → cook_build-0.7.2}/src/cook/manager.py +1 -1
- {cook_build-0.7.1 → cook_build-0.7.2/src/cook_build.egg-info}/PKG-INFO +1 -1
- {cook_build-0.7.1 → cook_build-0.7.2}/tests/test_actions.py +12 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/tests/test_contexts.py +21 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/tests/test_controller.py +30 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/LICENSE +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/README.md +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/README.rst +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/setup.cfg +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/src/cook/__init__.py +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/src/cook/__main__.py +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/src/cook/task.py +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/src/cook/util.py +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/src/cook_build.egg-info/SOURCES.txt +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/src/cook_build.egg-info/dependency_links.txt +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/src/cook_build.egg-info/entry_points.txt +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/src/cook_build.egg-info/requires.txt +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/src/cook_build.egg-info/top_level.txt +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/tests/test_examples.py +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/tests/test_main.py +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/tests/test_manager.py +0 -0
- {cook_build-0.7.1 → cook_build-0.7.2}/tests/test_util.py +0 -0
|
@@ -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):
|
|
@@ -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)
|
|
@@ -290,8 +290,8 @@ class Controller:
|
|
|
290
290
|
dep_futures = [task_futures[dep] for dep in dep_tasks]
|
|
291
291
|
await asyncio.gather(*dep_futures)
|
|
292
292
|
|
|
293
|
-
start = datetime.now()
|
|
294
293
|
digest = self._evaluate_task_hexdigest(task)
|
|
294
|
+
start: datetime | None = None
|
|
295
295
|
|
|
296
296
|
try:
|
|
297
297
|
# Log what we're doing
|
|
@@ -307,22 +307,19 @@ class Controller:
|
|
|
307
307
|
" action: %s",
|
|
308
308
|
task.action,
|
|
309
309
|
)
|
|
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
310
|
|
|
323
311
|
# Execute the task
|
|
324
312
|
if not dry_run:
|
|
325
313
|
async with semaphore:
|
|
314
|
+
start = datetime.now()
|
|
315
|
+
LOGGER.log(
|
|
316
|
+
logging.DEBUG if task.name.startswith("_") else logging.INFO,
|
|
317
|
+
"executing %s ...",
|
|
318
|
+
task,
|
|
319
|
+
)
|
|
320
|
+
params = {"name": task.name, "last_started": start}
|
|
321
|
+
self.connection.execute(QUERIES["upsert_task_started"], params)
|
|
322
|
+
self.connection.commit()
|
|
326
323
|
await task.execute()
|
|
327
324
|
|
|
328
325
|
# Check that all targets were created
|
|
@@ -344,6 +341,7 @@ class Controller:
|
|
|
344
341
|
self.connection.commit()
|
|
345
342
|
|
|
346
343
|
# Log completion
|
|
344
|
+
assert start is not None
|
|
347
345
|
delta = util.format_timedelta(datetime.now() - start)
|
|
348
346
|
LOGGER.log(
|
|
349
347
|
logging.DEBUG if task.name.startswith("_") else logging.INFO,
|
|
@@ -362,7 +360,7 @@ class Controller:
|
|
|
362
360
|
self.connection.execute(QUERIES["upsert_task_failed"], params)
|
|
363
361
|
self.connection.commit()
|
|
364
362
|
|
|
365
|
-
delta = util.format_timedelta(datetime.now() - start)
|
|
363
|
+
delta = util.format_timedelta(datetime.now() - start) if start else "?"
|
|
366
364
|
LOGGER.exception("failed to execute %s after %s", task, delta)
|
|
367
365
|
raise util.FailedTaskError(ex, task=task) from ex
|
|
368
366
|
|
|
@@ -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.
|
|
@@ -158,6 +158,18 @@ async def test_module_action_debug() -> None:
|
|
|
158
158
|
create_subprocess.assert_called_with(sys.executable, "-m", "pytest")
|
|
159
159
|
|
|
160
160
|
|
|
161
|
+
@pytest.mark.asyncio
|
|
162
|
+
async def test_sync_function_action_with_kwargs() -> None:
|
|
163
|
+
calls = []
|
|
164
|
+
|
|
165
|
+
def func(task, *, key):
|
|
166
|
+
calls.append((task, key))
|
|
167
|
+
|
|
168
|
+
action = FunctionAction(func, key="value")
|
|
169
|
+
await action.execute("my_task") # type: ignore[arg-type]
|
|
170
|
+
assert calls == [("my_task", "value")]
|
|
171
|
+
|
|
172
|
+
|
|
161
173
|
def test_composite_digest() -> None:
|
|
162
174
|
actions = [
|
|
163
175
|
SubprocessAction("hello"),
|
|
@@ -84,6 +84,27 @@ async def test_create_target_directories_with_multiple_targets(
|
|
|
84
84
|
assert filename.parent.is_dir()
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
@pytest.mark.asyncio
|
|
88
|
+
async def test_create_target_directories_different_parents(
|
|
89
|
+
m: Manager, tmp_wd: Path, conn: sqlite3.Connection
|
|
90
|
+
) -> None:
|
|
91
|
+
# Targets in two different directories: the closure in create_target_directories
|
|
92
|
+
# must create both parents, not just the last one from the loop.
|
|
93
|
+
targets = [tmp_wd / "dir_a" / "file.txt", tmp_wd / "dir_b" / "file.txt"]
|
|
94
|
+
with normalize_action(), create_target_directories():
|
|
95
|
+
task = m.create_task(
|
|
96
|
+
"multi_dir",
|
|
97
|
+
targets=targets,
|
|
98
|
+
action=FunctionAction(
|
|
99
|
+
lambda t: [target.write_text("ok") for target in t.targets]
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
controller = Controller(m.resolve_dependencies(), conn)
|
|
103
|
+
await controller.execute(task)
|
|
104
|
+
for target in targets:
|
|
105
|
+
assert target.is_file(), f"{target} was not created"
|
|
106
|
+
|
|
107
|
+
|
|
87
108
|
def test_normalize_action(m: Manager) -> None:
|
|
88
109
|
with normalize_action():
|
|
89
110
|
task = m.create_task("foo", action="bar")
|
|
@@ -420,6 +420,36 @@ async def test_last_started_completed(m: Manager, conn: Connection) -> None:
|
|
|
420
420
|
assert delta.total_seconds() > 1
|
|
421
421
|
|
|
422
422
|
|
|
423
|
+
@pytest.mark.asyncio
|
|
424
|
+
async def test_last_started_reflects_actual_start(m: Manager, conn: Connection) -> None:
|
|
425
|
+
delay = 0.5
|
|
426
|
+
task1 = m.create_task(
|
|
427
|
+
"task1",
|
|
428
|
+
action=SubprocessAction(f"sleep {delay} && touch task1.txt", shell=True),
|
|
429
|
+
targets=["task1.txt"],
|
|
430
|
+
)
|
|
431
|
+
task2 = m.create_task(
|
|
432
|
+
"task2",
|
|
433
|
+
action=SubprocessAction("touch task2.txt", shell=True),
|
|
434
|
+
targets=["task2.txt"],
|
|
435
|
+
)
|
|
436
|
+
result = m.create_task("result", dependencies=[task1.targets[0], task2.targets[0]])
|
|
437
|
+
|
|
438
|
+
# With num_concurrent=1, task2 must wait for task1 to finish. Its last_started
|
|
439
|
+
# should reflect when it actually began executing, not when it was scheduled.
|
|
440
|
+
await Controller(m.resolve_dependencies(), conn).execute(result, num_concurrent=1)
|
|
441
|
+
rows = dict(
|
|
442
|
+
conn.execute(
|
|
443
|
+
"SELECT name, last_started FROM tasks WHERE name IN ('task1', 'task2')"
|
|
444
|
+
).fetchall()
|
|
445
|
+
)
|
|
446
|
+
delta = (rows["task2"] - rows["task1"]).total_seconds()
|
|
447
|
+
assert delta >= delay, (
|
|
448
|
+
f"task2 started only {delta:.3f}s after task1, expected >= {delay}s; "
|
|
449
|
+
"last_started is recorded at scheduling time, not actual execution time"
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
|
|
423
453
|
@pytest.mark.asyncio
|
|
424
454
|
async def test_sync_action_backwards_compat(
|
|
425
455
|
m: Manager, conn: Connection, caplog: pytest.LogCaptureFixture
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|