Files
windmill/backend/windmill-worker/loader.py
T
Ruben Fiszel 2a780ad87a feat: resolve relative imports from local content in script/flow preview (#9233)
* feat: thread temp_script_refs into preview jobs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: resolve python preview relative imports from temp script refs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: use local relative imports in wmill script preview

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: use local relative imports in wmill flow preview

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add temp_script_refs to Preview and FlowPreview openapi schemas

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: pass temp_script_refs to bun lockfile gen for no-lock preview

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: route script preview through shared buildPreviewTempScriptRefs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: resolve local relative imports in wmill app dev inline scripts

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address cubic review — bundle cache key, preview-mode gate, error masking

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf: skip dep-tree build when previewed script has no relative imports; narrow old-backend classifier

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address review issues (preview-only gate, bundle preview, app dev cwd)

Three P1s flagged in repeated codex/pi reviews on PR #9233:

- Gate _TEMP_SCRIPT_REFS extraction on JobKind::Preview (bun + python
  executors) and propagation in worker_flow on JobKind::FlowPreview. job.args
  includes caller-controlled request args, so honoring this key on deployed
  runs would let a caller swap import resolution to local content uploaded
  via /raw_temp.
- run_bundle_preview_script now injects temp_script_refs into PushArgs.extra,
  mirroring run_preview_script — closes the silent data drop for the bundle
  preview path.
- wmill app dev chdirs to the wmill.yaml root before buildPreviewTempScriptRefs
  and restores after, so the `cd <app>__raw_app && wmill app dev` invocation
  (cwd is the raw_app folder, no app_folder arg) still walks sibling workspace
  scripts like f/lib.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(ee): bump ee-repo-ref to 5b347d6 (handle_python_deps arity fix)

Picks up the EE arity fix so cargo_test + check_ee_full compile cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(ee): bump ee-repo-ref to 52e273d (agent-workers bundle path arity fix)

Picks up windmill-ee-private 52e273d which adds the missing &None arg to
compute_bundle_local_and_remote_path in windmill-api-agent-workers/src/ee.rs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(ee): bump ee-repo-ref to 2d6ffd3 (EE main merged in)

Previous bump pinned an older EE commit, missing the audit-log object-store
export module (EE PR #579, commit ec3cd35) and other EE main updates. The
CE backend's `crate::ee_oss::anchor_audit_logs_s3_checkpoint_env_var` and
`export_audit_logs_to_object_store` references need the new EE definitions.
Merged origin/main into the EE branch and pinned the merge commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to b0c87b1272c25dca4aa9148c87fb024a9d9ef322

This commit updates the EE repository reference after PR #583 was merged in windmill-ee-private.

Previous ee-repo-ref: 2d6ffd32c99bd93e79cf78675cb89499a81b17e1

New ee-repo-ref: b0c87b1272c25dca4aa9148c87fb024a9d9ef322

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-05-20 12:44:29 +00:00

92 lines
3.6 KiB
Python

import sys
import os
from importlib.abc import MetaPathFinder, Loader
from importlib.machinery import ModuleSpec, SourceFileLoader
import time
# Injected by backend: maps script path -> temp storage hash so preview jobs
# resolve relative imports from not-yet-deployed local content. Empty ({}) for
# deployed runs.
TEMP_SCRIPT_REFS = TEMP_SCRIPT_REFS_PLACEHOLDER
class WindmillLoader(Loader):
def __init__(self, path):
self.path = path
def create_module(self, spec):
return None
def exec_module(self, module):
module.__path__ = self.path
return None
class WindmillFinder(MetaPathFinder):
@classmethod
def find_spec(cls, name, path, target=None):
splitted = name.split(".")
if splitted[0] != "f" and splitted[0] != "u":
return None
l = len(splitted) # noqa: E741
if l <= 2:
return ModuleSpec(name, WindmillLoader(name))
elif l > 2:
script_path = "/".join(splitted)
folder = os.getcwd() + "/tmp/" + "/".join(splitted[:-1])
fullpath = folder + "/" + splitted[-1] + ".py"
if os.path.exists(fullpath):
return ModuleSpec(name, SourceFileLoader(name, fullpath))
import urllib.parse
import urllib.request
headers = {
"Authorization": f"Bearer {os.environ.get('WM_TOKEN')}",
"User-Agent": "windmill/beta"
}
query_params = "?cache_folders=true"
runnable_id = os.environ.get('WM_RUNNABLE_ID')
if runnable_id:
query_params += f"&cache_key={runnable_id}"
temp_hash = TEMP_SCRIPT_REFS.get(script_path) if TEMP_SCRIPT_REFS else None
if temp_hash:
query_params += f"&temp_script_hash={temp_hash}"
url = f"{os.environ.get('BASE_INTERNAL_URL')}/api/w/{os.environ.get('WM_WORKSPACE')}/scripts/raw/p/{script_path}.py{query_params}"
req = urllib.request.Request(url, None, headers)
for attempt in range(4): # 0, 1, 2, 3 = up to 3 retries
try:
req_start = time.time()
with urllib.request.urlopen(req) as response:
os.makedirs(folder, exist_ok=True)
r = response.read().decode("utf-8")
if r == "WINDMILL_IS_FOLDER":
return ModuleSpec(name, WindmillLoader(name))
with open(fullpath, "w+") as f:
f.write(r)
return ModuleSpec(name, SourceFileLoader(name, fullpath))
except urllib.error.HTTPError as e:
duration = time.time() - req_start
if e.code != 404:
print(f"Error fetching script {script_path}: HTTP {e.code} - {e.reason} - {duration}s")
return ModuleSpec(name, WindmillLoader(name))
except Exception as e:
duration = time.time() - req_start
# Check if this is errno 104 (Connection reset by peer) and we have retries left
if (hasattr(e, 'errno') and e.errno == 104) and attempt < 3:
print(f"Connection reset (errno 104) fetching script {script_path}, retrying in 3s (attempt {attempt + 1}/3)")
time.sleep(3)
continue
print(f"Error fetching script {script_path}: {e} - {duration}s")
return ModuleSpec(name, WindmillLoader(name))
sys.meta_path.append(WindmillFinder)