MCPcopy Create free account
hub / github.com/WeaveMindAI/weft / run_local_python

Function run_local_python

catalog/code/:exec/python/backend.rs:380–430  ·  view source on GitHub ↗

Execute the wrapped script via the host's `python3` interpreter. Used only when DEPLOYMENT_MODE != cloud. No sandboxing, no isolation: this is intended for the developer running their own code on their own machine.

(code: &str)

Source from the content-addressed store, hash-verified

378/// Used only when DEPLOYMENT_MODE != cloud. No sandboxing, no isolation: this
379/// is intended for the developer running their own code on their own machine.
380async fn run_local_python(code: &str) -> Result<ExecOutcome, String> {
381 let output = Command::new("python3")
382 .arg("-c")
383 .arg(code)
384 .output()
385 .await
386 .map_err(|e| format!("Failed to spawn local python3: {}", e))?;
387
388 let stdout_full = String::from_utf8_lossy(&output.stdout).into_owned();
389 let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
390
391 if !output.status.success() {
392 let msg = if stderr.is_empty() {
393 format!("python3 exited with status {}", output.status)
394 } else {
395 stderr.trim_end().to_string()
396 };
397 return Ok(ExecOutcome::UserError(msg));
398 }
399
400 // Wrapper always prefixes the marker with "\n" and json.dumps produces a
401 // single line, so we can anchor on "\nWEFT_RESULT_JSON=" regardless of
402 // whether the user's last print had a trailing newline. Using the LAST
403 // marker occurrence means a user who prints the literal "WEFT_RESULT_JSON="
404 // themselves cannot hijack us. Content AFTER the marker's terminating "\n"
405 // (e.g. atexit / CPython shutdown prints) is preserved in the stdout port.
406 const MARKER_ANCHOR: &str = "\nWEFT_RESULT_JSON=";
407 let pos = stdout_full.rfind(MARKER_ANCHOR).ok_or_else(|| {
408 "Local python3 finished without emitting a result marker. The wrapper may have been altered.".to_string()
409 })?;
410 let after = &stdout_full[pos + MARKER_ANCHOR.len()..];
411 let (json_str, tail_after_marker) = match after.find('\n') {
412 Some(nl) => (&after[..nl], &after[nl + 1..]),
413 None => (after, ""),
414 };
415 let parsed: Value = serde_json::from_str(json_str)
416 .map_err(|e| format!("Could not parse local result JSON: {} (raw: {})", e, json_str))?;
417 let ports = parsed.as_object()
418 .ok_or_else(|| format!("Local result was not a JSON object: {}", parsed))?
419 .clone();
420
421 let mut user_stdout = stdout_full[..pos].to_string();
422 if !tail_after_marker.is_empty() {
423 if !user_stdout.is_empty() && !user_stdout.ends_with('\n') {
424 user_stdout.push('\n');
425 }
426 user_stdout.push_str(tail_after_marker);
427 }
428
429 Ok(ExecOutcome::Ok { ports, stdout: user_stdout, stderr })
430}
431
432/// Extracted sandbox handle. Owned by the caller; the caller is responsible
433/// for calling `delete_sandbox` once it is done.

Callers 1

executeMethod · 0.85

Calls 2

is_emptyMethod · 0.80
lenMethod · 0.80

Tested by

no test coverage detected