mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 08:02:38 +00:00
fix: keep the step key in a cached task's identity
This commit is contained in:
@@ -1863,3 +1863,28 @@ class TestTaskFingerprint:
|
||||
)
|
||||
assert [s["key"] for s in second["steps"]] == ["add_one", "add_one_2"]
|
||||
assert second["steps"][0]["fn_id"] != first["steps"][0]["fn_id"]
|
||||
|
||||
def test_a_builtin_task_still_decorates_and_dispatches(self):
|
||||
"""The fingerprint is taken for every task, cached or not, so a callable
|
||||
with neither source nor code object must not break the decorator."""
|
||||
builtin_task = task(pow)
|
||||
|
||||
@workflow
|
||||
async def wf():
|
||||
return await builtin_task(2, 3)
|
||||
|
||||
result = _run_workflow(wf, {}, {})
|
||||
assert result["type"] == "dispatch"
|
||||
assert result["steps"][0]["key"] == "pow"
|
||||
|
||||
def test_two_lambdas_on_one_line_are_told_apart(self):
|
||||
"""``inspect.getsource`` gives each the whole line, so the code's shape is
|
||||
what separates them, and it must not depend on where the line sits."""
|
||||
first, second = task(lambda x: x + 1), task(lambda x: x + 2)
|
||||
|
||||
@workflow
|
||||
async def wf():
|
||||
return await asyncio.gather(first(x=1), second(x=1))
|
||||
|
||||
result = _run_workflow(wf, {}, {})
|
||||
assert result["steps"][0]["fn_id"] != result["steps"][1]["fn_id"]
|
||||
|
||||
@@ -3291,14 +3291,23 @@ def _fn_fingerprint(func) -> str:
|
||||
tasks called at the same position, so neither can tell them apart."""
|
||||
import hashlib
|
||||
import inspect
|
||||
import marshal
|
||||
|
||||
# Runs for every task at decoration, cached or not, so it must never raise: a
|
||||
# builtin has neither source nor code object. Source alone cannot separate two
|
||||
# lambdas written on one line, so the code's shape goes in as well, without the
|
||||
# line numbers that would move whenever the file is edited above it.
|
||||
parts = []
|
||||
try:
|
||||
src = inspect.getsource(func).encode()
|
||||
except (OSError, TypeError):
|
||||
# No source on disk: the whole code object, constants and names included.
|
||||
src = marshal.dumps(func.__code__)
|
||||
return hashlib.sha256(src).hexdigest()
|
||||
parts.append(inspect.getsource(func).encode())
|
||||
except Exception:
|
||||
pass
|
||||
code = getattr(func, "__code__", None)
|
||||
if code is not None:
|
||||
consts = tuple(c for c in code.co_consts if not isinstance(c, type(code)))
|
||||
parts.append(repr((code.co_code, code.co_names, code.co_varnames, consts)).encode())
|
||||
if not parts:
|
||||
parts.append(repr(func).encode())
|
||||
return hashlib.sha256(b"\x1f".join(parts)).hexdigest()
|
||||
|
||||
|
||||
def task(
|
||||
@@ -3344,10 +3353,11 @@ def task(
|
||||
no ``delay`` all go out in a single round.
|
||||
|
||||
``cache_ttl`` serves a previous result of the task for that many seconds
|
||||
instead of running it again. The result is keyed on the task and the
|
||||
arguments it is called with, so anything a cached task reads from its
|
||||
closure must be passed in as an argument. It has no effect on a
|
||||
``task_flow`` target, which keeps its flow's own cache policy.
|
||||
instead of running it again. The result is keyed on the task, the step it
|
||||
runs as and the arguments it is called with, so anything a cached task reads
|
||||
from its closure, the receiver of a bound method included, must be passed in
|
||||
as an argument. It has no effect on a ``task_flow`` target, which keeps its
|
||||
flow's own cache policy.
|
||||
|
||||
Usage::
|
||||
|
||||
|
||||
Reference in New Issue
Block a user