cook-build 0.7.2__tar.gz → 0.7.3__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.2/src/cook_build.egg-info → cook_build-0.7.3}/PKG-INFO +1 -1
- {cook_build-0.7.2 → cook_build-0.7.3}/pyproject.toml +1 -1
- {cook_build-0.7.2 → cook_build-0.7.3}/src/cook/__main__.py +4 -6
- {cook_build-0.7.2 → cook_build-0.7.3}/src/cook/actions.py +9 -8
- {cook_build-0.7.2 → cook_build-0.7.3}/src/cook/controller.py +10 -12
- {cook_build-0.7.2 → cook_build-0.7.3}/src/cook/manager.py +1 -1
- {cook_build-0.7.2 → cook_build-0.7.3}/src/cook/util.py +1 -0
- {cook_build-0.7.2 → cook_build-0.7.3/src/cook_build.egg-info}/PKG-INFO +1 -1
- {cook_build-0.7.2 → cook_build-0.7.3}/tests/test_contexts.py +3 -3
- {cook_build-0.7.2 → cook_build-0.7.3}/LICENSE +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/README.md +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/README.rst +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/setup.cfg +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/src/cook/__init__.py +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/src/cook/contexts.py +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/src/cook/task.py +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/src/cook_build.egg-info/SOURCES.txt +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/src/cook_build.egg-info/dependency_links.txt +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/src/cook_build.egg-info/entry_points.txt +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/src/cook_build.egg-info/requires.txt +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/src/cook_build.egg-info/top_level.txt +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/tests/test_actions.py +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/tests/test_controller.py +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/tests/test_examples.py +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/tests/test_main.py +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/tests/test_manager.py +0 -0
- {cook_build-0.7.2 → cook_build-0.7.3}/tests/test_util.py +0 -0
|
@@ -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)
|
|
@@ -122,21 +122,22 @@ class SubprocessAction(Action):
|
|
|
122
122
|
True
|
|
123
123
|
"""
|
|
124
124
|
|
|
125
|
-
def __init__(self,
|
|
125
|
+
def __init__(self, args: str | list[str], **kwargs) -> None:
|
|
126
126
|
# Validate shell argument early
|
|
127
|
-
if kwargs.get("shell", False) and
|
|
127
|
+
if kwargs.get("shell", False) and not isinstance(args, str):
|
|
128
128
|
raise ValueError("shell=True requires string args")
|
|
129
129
|
self.args = args
|
|
130
130
|
self.kwargs = kwargs
|
|
131
131
|
|
|
132
132
|
async def execute(self, task: "Task") -> None:
|
|
133
133
|
# Get the command arguments
|
|
134
|
-
|
|
134
|
+
args = self.args
|
|
135
135
|
shell = self.kwargs.get("shell", False)
|
|
136
136
|
other_kwargs = {k: v for k, v in self.kwargs.items() if k != "shell"}
|
|
137
137
|
|
|
138
138
|
# Create the subprocess
|
|
139
139
|
if shell:
|
|
140
|
+
assert isinstance(args, str)
|
|
140
141
|
process = await asyncio.create_subprocess_shell(args, **other_kwargs)
|
|
141
142
|
else:
|
|
142
143
|
# Exec mode: args can be a string (single command) or list
|
|
@@ -167,7 +168,7 @@ class SubprocessAction(Action):
|
|
|
167
168
|
@property
|
|
168
169
|
def hexdigest(self) -> str:
|
|
169
170
|
hasher = hashlib.sha1()
|
|
170
|
-
|
|
171
|
+
args = self.args
|
|
171
172
|
if isinstance(args, str):
|
|
172
173
|
hasher.update(args.encode())
|
|
173
174
|
else:
|
|
@@ -176,7 +177,7 @@ class SubprocessAction(Action):
|
|
|
176
177
|
return hasher.hexdigest()
|
|
177
178
|
|
|
178
179
|
def __repr__(self) -> str:
|
|
179
|
-
args
|
|
180
|
+
args = self.args
|
|
180
181
|
if not isinstance(args, str):
|
|
181
182
|
args = " ".join(map(shlex.quote, args))
|
|
182
183
|
return f"{self.__class__.__name__}({repr(args)})"
|
|
@@ -199,13 +200,13 @@ class CompositeAction(Action):
|
|
|
199
200
|
|
|
200
201
|
@property
|
|
201
202
|
def hexdigest(self) -> str | None:
|
|
202
|
-
|
|
203
|
+
hasher = hashlib.sha1()
|
|
203
204
|
for action in self.actions:
|
|
204
205
|
hexdigest = action.hexdigest
|
|
205
206
|
if hexdigest is None:
|
|
206
207
|
return None
|
|
207
|
-
|
|
208
|
-
return
|
|
208
|
+
hasher.update(bytearray.fromhex(hexdigest))
|
|
209
|
+
return hasher.hexdigest()
|
|
209
210
|
|
|
210
211
|
|
|
211
212
|
class ModuleAction(SubprocessAction):
|
|
@@ -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,7 +285,9 @@ class Controller:
|
|
|
290
285
|
dep_futures = [task_futures[dep] for dep in dep_tasks]
|
|
291
286
|
await asyncio.gather(*dep_futures)
|
|
292
287
|
|
|
293
|
-
digest =
|
|
288
|
+
digest: str | None = None
|
|
289
|
+
if not dry_run:
|
|
290
|
+
digest = self._evaluate_task_hexdigest(task)
|
|
294
291
|
start: datetime | None = None
|
|
295
292
|
|
|
296
293
|
try:
|
|
@@ -332,6 +329,7 @@ class Controller:
|
|
|
332
329
|
|
|
333
330
|
# Update DB for completion
|
|
334
331
|
if not dry_run:
|
|
332
|
+
assert digest is not None
|
|
335
333
|
params = {
|
|
336
334
|
"name": task.name,
|
|
337
335
|
"digest": digest,
|
|
@@ -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
|
|
@@ -110,14 +110,14 @@ def test_normalize_action(m: Manager) -> None:
|
|
|
110
110
|
task = m.create_task("foo", action="bar")
|
|
111
111
|
assert (
|
|
112
112
|
isinstance(task.action, SubprocessAction)
|
|
113
|
-
and task.action.args
|
|
113
|
+
and task.action.args == "bar"
|
|
114
114
|
and task.action.kwargs["shell"]
|
|
115
115
|
)
|
|
116
116
|
|
|
117
117
|
task = m.create_task("bar", action=["baz"])
|
|
118
118
|
assert (
|
|
119
119
|
isinstance(task.action, SubprocessAction)
|
|
120
|
-
and task.action.args
|
|
120
|
+
and task.action.args == ["baz"]
|
|
121
121
|
and not task.action.kwargs.get("shell")
|
|
122
122
|
)
|
|
123
123
|
|
|
@@ -131,7 +131,7 @@ def test_normalize_action(m: Manager) -> None:
|
|
|
131
131
|
assert isinstance(task.action, FunctionAction)
|
|
132
132
|
|
|
133
133
|
task = m.create_task("fizz", action=[pytest, "foo", "bar"])
|
|
134
|
-
assert isinstance(task.action, ModuleAction) and task.action.args
|
|
134
|
+
assert isinstance(task.action, ModuleAction) and task.action.args == [
|
|
135
135
|
sys.executable,
|
|
136
136
|
"-m",
|
|
137
137
|
"pytest",
|
|
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
|
|
File without changes
|