Skip to content

Commit 3a766ac

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

File tree

9 files changed

+113
-23
lines changed

9 files changed

+113
-23
lines changed

examples/talk/6-code-json.pdl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ text:
2626
- def: EVAL
2727
contribute: []
2828
lang: python
29+
requirements: textdistance
2930
code:
3031
|
3132
import textdistance

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

Lines changed: 15 additions & 3 deletions
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_interpreter_if_needed;
7+
use crate::interpreter::pip::{
8+
pip_install_code_blocks_if_needed, pip_install_interpreter_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,8 +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 = pip_install_interpreter_if_needed(app_handle);
32+
let programs = from_path(&source_file_path).unwrap();
33+
let pull_future = pull_if_needed(programs.clone());
34+
let reqs_future = pip_install_code_blocks_if_needed(&app_handle, programs.clone());
35+
let bin_path_future = pip_install_interpreter_if_needed(&app_handle);
2536

2637
// wait for any model pulls to finish
2738
block_on(pull_future).map_err(|e| match e {
@@ -32,6 +43,7 @@ pub fn run_pdl_program(
3243

3344
// wait for any pip installs to finish
3445
let bin_path = block_on(bin_path_future)?;
46+
block_on(reqs_future)?;
3547

3648
let mut args = vec![
3749
source_file_path,

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ pub fn extract_models(programs: Vec<Yaml>) -> Vec<String> {
55
extract_values(programs, "model")
66
}
77

8+
/// Extract requirements.txt referenced by the programs
9+
pub fn extract_requirements(programs: Vec<Yaml>) -> Vec<String> {
10+
extract_values(programs, "requirements")
11+
}
12+
813
/// Take a list of Yaml fragments and produce a vector of the string-valued entries of the given field
914
pub fn extract_values(programs: Vec<Yaml>, field: &str) -> Vec<String> {
1015
let mut values = programs
Lines changed: 65 additions & 9 deletions
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)?;
18-
let venv_path = cache_path.join(hash);
23+
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+
programs: Vec<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(programs)
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_interpreter_if_needed(
41-
app_handle: tauri::AppHandle,
97+
app_handle: &tauri::AppHandle,
4298
) -> Result<PathBuf, tauri::Error> {
4399
// the interpreter requirements.txt
44100
let requirements_path = app_handle
@@ -47,5 +103,5 @@ pub async fn pip_install_interpreter_if_needed(
47103

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

50-
pip_install_if_needed(&cache_path, &requirements_path).await
106+
pip_install_if_needed(&cache_path, &requirements_path)
51107
}

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

Lines changed: 3 additions & 9 deletions
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(programs: Vec<Yaml>) -> Result<(), LoadError> {
10+
extract::extract_models(programs)
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-tauri/src/interpreter/shasum.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use ::std::fs::File;
22
use ::std::io::{copy, Result};
33
use ::std::path::Path;
44

5-
use base64ct::{Base64, Encoding};
5+
use base64ct::{Base64Url, Encoding};
66
use sha2::{Digest, Sha256};
77

88
pub fn sha256sum(path: &Path) -> Result<String> {
@@ -12,5 +12,5 @@ pub fn sha256sum(path: &Path) -> Result<String> {
1212
copy(&mut file, &mut hasher)?;
1313
let hash_bytes = hasher.finalize();
1414

15-
Ok(Base64::encode_string(&hash_bytes))
15+
Ok(Base64Url::encode_string(&hash_bytes))
1616
}

pdl-live-react/src/pdl_ast.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1919,6 +1919,11 @@ export type Kind15 = "code"
19191919
*
19201920
*/
19211921
export type Lang = "python" | "command" | "jinja" | "pdl"
1922+
/**
1923+
* Pip requirements.txt
1924+
*
1925+
*/
1926+
export type Requirements = string | null
19221927
/**
19231928
* Code to execute.
19241929
*
@@ -2837,6 +2842,7 @@ export interface CodeBlock {
28372842
pdl__is_leaf?: PdlIsLeaf15
28382843
kind?: Kind15
28392844
lang: Lang
2845+
requirements: Requirements
28402846
code: Code
28412847
}
28422848
/**

src/pdl/pdl-schema.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,18 @@
13061306
"title": "Lang",
13071307
"type": "string"
13081308
},
1309+
"requirements": {
1310+
"anyOf": [
1311+
{
1312+
"type": "string"
1313+
},
1314+
{
1315+
"type": "null"
1316+
}
1317+
],
1318+
"description": "Pip requirements.txt\n ",
1319+
"title": "Requirements"
1320+
},
13091321
"code": {
13101322
"anyOf": [
13111323
{
@@ -1390,6 +1402,7 @@
13901402
},
13911403
"required": [
13921404
"lang",
1405+
"requirements",
13931406
"code"
13941407
],
13951408
"title": "CodeBlock",

src/pdl/pdl_ast.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,9 @@ class CodeBlock(LeafBlock):
462462
]
463463
"""Programming language of the code.
464464
"""
465+
requirements: Optional[str]
466+
"""Pip requirements.txt
467+
"""
465468
code: "BlockType"
466469
"""Code to execute.
467470
"""

0 commit comments

Comments
 (0)