Skip to content

Commit f88e09d

Browse files
committed
feat: update rust to pip install code block requirements
Signed-off-by: Nick Mitchell <[email protected]>
1 parent 3ef4fdb commit f88e09d

File tree

15 files changed

+3128
-2091
lines changed

15 files changed

+3128
-2091
lines changed

examples/talk/6-code-json.pdl

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ text:
2626
- def: EVAL
2727
contribute: []
2828
lang: python
29+
requirements:
30+
- textdistance
2931
code:
3032
|
3133
import textdistance

pdl-live-react/src-tauri/src/cli/run.rs

+15-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,19 @@ use ::std::path::Path;
22
use duct::cmd;
33
use futures::executor::block_on;
44
use yaml_rust2::yaml::LoadError;
5+
use yaml_rust2::{ScanError, Yaml, YamlLoader};
56

6-
use crate::interpreter::pip::pip_install_internal_if_needed;
7+
use crate::interpreter::pip::{
8+
pip_install_code_blocks_if_needed, pip_install_internal_if_needed,
9+
};
710
use crate::interpreter::pull::pull_if_needed;
811

12+
/// Read the given filesystem path and produce a potentially multi-document Yaml
13+
fn from_path(path: &String) -> Result<Vec<Yaml>, ScanError> {
14+
let content = std::fs::read_to_string(path).unwrap();
15+
YamlLoader::load_from_str(&content)
16+
}
17+
918
#[cfg(desktop)]
1019
pub fn run_pdl_program(
1120
source_file_path: String,
@@ -20,9 +29,10 @@ pub fn run_pdl_program(
2029
);
2130

2231
// async the model pull and pip installs
23-
let pull_future = pull_if_needed(&source_file_path);
24-
let bin_path_future =
25-
pip_install_internal_if_needed(app_handle, &"interpreter/requirements.txt");
32+
let program = &from_path(&source_file_path).unwrap()[0];
33+
let pull_future = pull_if_needed(&program);
34+
let reqs_future = pip_install_code_blocks_if_needed(&app_handle, &program);
35+
let bin_path_future = pip_install_internal_if_needed(&app_handle, &"interpreter/requirements.txt");
2636

2737
// wait for any model pulls to finish
2838
block_on(pull_future).map_err(|e| match e {
@@ -33,6 +43,7 @@ pub fn run_pdl_program(
3343

3444
// wait for any pip installs to finish
3545
let bin_path = block_on(bin_path_future)?;
46+
block_on(reqs_future)?;
3647

3748
let mut args = vec![
3849
source_file_path,

pdl-live-react/src-tauri/src/compile/beeai.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ fn python_source_to_json(
291291
eprintln!("Compiling from Python source");
292292
}
293293
let bin_path = block_on(pip_install_internal_if_needed(
294-
app_handle,
294+
&app_handle,
295295
&"interpreter/beeai-requirements.txt",
296296
))?;
297297

pdl-live-react/src-tauri/src/interpreter/extract.rs

+15-9
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
use yaml_rust2::Yaml;
22

3-
/// Extract models referenced by the programs
4-
pub fn extract_models(programs: Vec<Yaml>) -> Vec<String> {
5-
extract_values(programs, "model")
3+
/// Extract models referenced by the program
4+
pub fn extract_models(program: &Yaml) -> Vec<String> {
5+
extract_values(program, "model")
6+
}
7+
8+
/// Extract requirements.txt referenced by the program
9+
pub fn extract_requirements(program: &Yaml) -> Vec<String> {
10+
extract_values(program, "requirements")
611
}
712

813
/// Take a list of Yaml fragments and produce a vector of the string-valued entries of the given field
9-
pub fn extract_values(programs: Vec<Yaml>, field: &str) -> Vec<String> {
10-
let mut values = programs
11-
.into_iter()
12-
.flat_map(|p| extract_one_values(p, field))
13-
.collect::<Vec<String>>();
14+
pub fn extract_values(program: &Yaml, field: &str) -> Vec<String> {
15+
let mut values = extract_one_values(program, field);
1416

1517
// A single program may specify the same model more than once. Dedup!
1618
values.sort();
@@ -20,7 +22,7 @@ pub fn extract_values(programs: Vec<Yaml>, field: &str) -> Vec<String> {
2022
}
2123

2224
/// Take one Yaml fragment and produce a vector of the string-valued entries of the given field
23-
fn extract_one_values(program: Yaml, field: &str) -> Vec<String> {
25+
fn extract_one_values(program: &Yaml, field: &str) -> Vec<String> {
2426
let mut values: Vec<String> = Vec::new();
2527

2628
match program {
@@ -31,6 +33,10 @@ fn extract_one_values(program: Yaml, field: &str) -> Vec<String> {
3133
Yaml::String(m) => {
3234
values.push(m.to_string());
3335
}
36+
Yaml::Array(a) => a.into_iter().for_each(|v| match v {
37+
Yaml::String(m) => values.push(m.to_string()),
38+
_ => {}
39+
}),
3440
_ => {}
3541
},
3642
_ => {}
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
1-
use ::std::fs::{copy, create_dir_all};
1+
use ::std::fs::{copy, create_dir_all, write};
22
use ::std::path::{Path, PathBuf};
33

44
use duct::cmd;
5+
use rayon::prelude::*;
56
use tauri::path::BaseDirectory;
67
use tauri::Manager;
8+
use tempfile::Builder;
9+
use yaml_rust2::Yaml;
710

11+
use crate::interpreter::extract;
812
use crate::interpreter::shasum;
913

1014
#[cfg(desktop)]
11-
pub async fn pip_install_if_needed(
15+
fn pip_install_if_needed_with_hash(
1216
cache_path: &Path,
1317
requirements_path: &Path,
18+
hash: String,
19+
force: bool,
1420
) -> Result<PathBuf, tauri::Error> {
1521
create_dir_all(&cache_path)?;
1622

17-
let hash = shasum::sha256sum(&requirements_path)?;
1823
let venv_path = cache_path.join("venvs").join(hash);
1924
let bin_path = venv_path.join(if cfg!(windows) { "Scripts" } else { "bin" });
2025

26+
// re: force, this is part of the short-term hack to install all
27+
// code block dependencies in the main interpreter venv. Once we
28+
// figure out how to support a separate venv for each code block
29+
// (that needs it), we can undo this hack.
2130
if !venv_path.exists() {
2231
println!("Creating virtual environment...");
2332
let python = if cfg!(target_os = "macos") {
@@ -27,18 +36,65 @@ pub async fn pip_install_if_needed(
2736
};
2837
cmd!(python, "-mvenv", &venv_path).run()?;
2938

30-
cmd!(bin_path.join("pip"), "install", "-r", &requirements_path,).run()?;
39+
if !force {
40+
cmd!(bin_path.join("pip"), "install", "-r", &requirements_path).run()?;
41+
42+
let cached_requirements_path = venv_path.join("requirements.txt");
43+
copy(requirements_path, cached_requirements_path)?;
44+
}
45+
}
3146

32-
let cached_requirements_path = venv_path.join("requirements.txt");
33-
copy(requirements_path, cached_requirements_path)?;
47+
if force {
48+
cmd!(bin_path.join("pip"), "install", "-r", &requirements_path).run()?;
3449
}
3550

3651
Ok(bin_path.to_path_buf())
3752
}
3853

54+
#[cfg(desktop)]
55+
fn pip_install_if_needed(
56+
cache_path: &Path,
57+
requirements_path: &Path,
58+
) -> Result<PathBuf, tauri::Error> {
59+
let hash = shasum::sha256sum(&requirements_path)?;
60+
pip_install_if_needed_with_hash(cache_path, requirements_path, hash, false)
61+
}
62+
63+
#[cfg(desktop)]
64+
pub async fn pip_install_code_blocks_if_needed(
65+
app_handle: &tauri::AppHandle,
66+
program: &Yaml,
67+
) -> Result<(), tauri::Error> {
68+
let cache_path = app_handle.path().cache_dir()?.join("pdl");
69+
70+
// for now, install the requirements in the main interpreter venv
71+
let requirements_path = app_handle
72+
.path()
73+
.resolve("interpreter/requirements.txt", BaseDirectory::Resource)?;
74+
75+
extract::extract_requirements(program)
76+
.into_par_iter()
77+
.try_for_each(|req| -> Result<(), tauri::Error> {
78+
let req_path = Builder::new()
79+
.prefix("pdl-requirements-")
80+
.suffix(".txt")
81+
.tempfile()?;
82+
// This is part of the "force" hack described above, where
83+
// we force the code block dependencies to be installed in
84+
// the main interpreter venv.
85+
let hash = shasum::sha256sum(&requirements_path)?;
86+
write(&req_path, req)?;
87+
pip_install_if_needed_with_hash(&cache_path, &req_path.path(), hash, true)?;
88+
Ok(())
89+
})
90+
.expect("code block requirements installed");
91+
92+
Ok(())
93+
}
94+
3995
#[cfg(desktop)]
4096
pub async fn pip_install_internal_if_needed(
41-
app_handle: tauri::AppHandle,
97+
app_handle: &tauri::AppHandle,
4298
requirements: &str,
4399
) -> Result<PathBuf, tauri::Error> {
44100
// the interpreter requirements.txt
@@ -48,5 +104,5 @@ pub async fn pip_install_internal_if_needed(
48104

49105
let cache_path = app_handle.path().cache_dir()?.join("pdl");
50106

51-
pip_install_if_needed(&cache_path, &requirements_path).await
107+
pip_install_if_needed(&cache_path, &requirements_path)
52108
}

pdl-live-react/src-tauri/src/interpreter/pull.rs

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
use duct::cmd;
22
use rayon::prelude::*;
33
use yaml_rust2::yaml::LoadError;
4-
use yaml_rust2::{ScanError, Yaml, YamlLoader};
4+
use yaml_rust2::Yaml;
55

66
use crate::interpreter::extract;
77

8-
/// Read the given filesystem path and produce a potentially multi-document Yaml
9-
fn from_path(path: &String) -> Result<Vec<Yaml>, ScanError> {
10-
let content = std::fs::read_to_string(path).unwrap();
11-
YamlLoader::load_from_str(&content)
12-
}
13-
148
/// Pull models (in parallel) from the PDL program in the given filepath.
15-
pub async fn pull_if_needed(path: &String) -> Result<(), LoadError> {
16-
extract::extract_models(from_path(path).unwrap())
9+
pub async fn pull_if_needed(program: &Yaml) -> Result<(), LoadError> {
10+
extract::extract_models(program)
1711
.into_par_iter()
1812
.try_for_each(|model| match model {
1913
m if model.starts_with("ollama/") => ollama_pull_if_needed(&m[7..]),

pdl-live-react/src/helpers.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import type {
55
PdlBlock,
66
TextBlock,
77
ArgsBlock,
8-
CodeBlock,
98
PdlModelInput,
109
LocalizedExpression,
10+
PythonCodeBlock,
11+
NonPythonCodeBlock,
1112
} from "./pdl_ast"
1213

1314
/** Re-export for convenience */
@@ -301,11 +302,13 @@ export function extractStructuredModelResponse({
301302
return { resultForDisplay, lang, meta }
302303
}
303304

304-
export function isArgs(block: ArgsBlock | CodeBlock): block is ArgsBlock {
305+
export function isArgs(
306+
block: ArgsBlock | PythonCodeBlock | NonPythonCodeBlock,
307+
): block is ArgsBlock {
305308
return Array.isArray((block as ArgsBlock).args)
306309
}
307310

308-
export function extractCode({ code }: CodeBlock): string {
311+
export function extractCode({ code }: PythonCodeBlock | NonPythonCodeBlock): string {
309312
if (
310313
isNonScalarPdlBlock(code) &&
311314
hasResult(code) &&

0 commit comments

Comments
 (0)