-
Notifications
You must be signed in to change notification settings - Fork 15
Reticulate support #506
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Reticulate support #506
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
003c543
Add rpc method to query reticulate interpreters
dfalbel 239a6af
Add events for reticulate
dfalbel 475d2f6
Make sure thre's a single reticulate thread
dfalbel e698822
Remove unncessary reticulate focus command
dfalbel 6ecf221
Cleanup
dfalbel 30b22ce
Remove unnecessary id
dfalbel 8ef5899
Send code to R main thread
dfalbel 08de770
Input code
dfalbel 71089bf
Allow optional
dfalbel 37a665d
Correctly acquire input
dfalbel 607b252
Create a method to start kernels
dfalbel 9955037
Add install reticulate method
dfalbel 49c9a99
Add more method to make checks on R environment
dfalbel f582e91
Apply suggestions from code review
dfalbel 286a24a
Improvements to style and simplifications
dfalbel 8bf745f
Add some comments + install packages rpc method
dfalbel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
#' @export | ||
.ps.reticulate_open <- function(input="") { | ||
.ps.Call("ps_reticulate_open", input) | ||
} | ||
|
||
#' Called by the front-end right before starting the reticulate session. | ||
#' | ||
#' At this point it should be fine to load Python if it's not loaded, and | ||
#' check if it can be started and if necessary packages are installed. | ||
#' @export | ||
.ps.rpc.reticulate_check_prerequisites <- function() { | ||
|
||
# This should return a list with the following fields: | ||
# python: NULL or string | ||
# venv: NULL or string | ||
# ipykernel: NULL or string | ||
# error: NULL or string | ||
|
||
config <- tryCatch({ | ||
reticulate::py_discover_config() | ||
}, error = function(err) { | ||
err | ||
}) | ||
|
||
if (inherits(config, "error")) { | ||
# py_discover_config() can fail if the user forced a Python session | ||
# via RETICULATE_PYTHON, but this version doesn't exist. | ||
return(list(error = conditionMessage(config))) | ||
} | ||
|
||
if (is.null(config) || is.null(config$python)) { | ||
# The front-end will offer to install Python. | ||
return(list(python = NULL, error = NULL)) | ||
} | ||
|
||
python <- config$python | ||
venv <- config$virtualenv | ||
|
||
# Check that python can be loaded, if it can't we will throw | ||
# an error, which is unrecoverable. | ||
config <- tryCatch({ | ||
reticulate::py_config() | ||
}, error = function(err) { | ||
err | ||
}) | ||
|
||
if (inherits(config, "error")) { | ||
return(list(python = python, venv = venv, error = conditionMessage(config))) | ||
} | ||
|
||
# Now check ipykernel | ||
ipykernel <- tryCatch({ | ||
reticulate::py_module_available("ipykernel") | ||
}, error = function(err) { | ||
err | ||
}) | ||
|
||
if (inherits(ipykernel, "error")) { | ||
return(list(python = python, venv = venv, error = conditionMessage(ipykernel))) | ||
} | ||
|
||
list( | ||
python = config$python, | ||
venv = venv, | ||
ipykernel = ipykernel, | ||
error = NULL | ||
) | ||
} | ||
|
||
#' @export | ||
.ps.rpc.reticulate_start_kernel <- function(kernelPath, connectionFile, logFile, logLevel) { | ||
# Starts an IPykernel in a separate thread with information provided by | ||
# the caller. | ||
# It it's essentially executing the kernel startup script: | ||
# https://github.com/posit-dev/positron/blob/main/extensions/positron-python/python_files/positron/positron_language_server.py | ||
# and passing the communication files that Positron Jupyter's Adapter sets up. | ||
tryCatch({ | ||
reticulate:::py_run_file_on_thread( | ||
file = kernelPath, | ||
args = c( | ||
"-f", connectionFile, | ||
"--logfile", logFile, | ||
"--loglevel", logLevel, | ||
"--session-mode", "console" | ||
) | ||
) | ||
# Empty string means that no error happened. | ||
"" | ||
}, error = function(err) { | ||
conditionMessage(err) | ||
}) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
use std::ops::Deref; | ||
use std::sync::LazyLock; | ||
use std::sync::Mutex; | ||
|
||
use amalthea::comm::comm_channel::CommMsg; | ||
use amalthea::comm::event::CommManagerEvent; | ||
use amalthea::socket::comm::CommInitiator; | ||
use amalthea::socket::comm::CommSocket; | ||
use crossbeam::channel::Sender; | ||
use harp::RObject; | ||
use libr::R_NilValue; | ||
use libr::SEXP; | ||
use serde_json::json; | ||
use stdext::result::ResultOrLog; | ||
use stdext::spawn; | ||
use stdext::unwrap; | ||
use uuid::Uuid; | ||
|
||
use crate::interface::RMain; | ||
|
||
static RETICULATE_COMM_ID: LazyLock<Mutex<Option<String>>> = LazyLock::new(|| Mutex::new(None)); | ||
|
||
pub struct ReticulateService { | ||
comm: CommSocket, | ||
comm_manager_tx: Sender<CommManagerEvent>, | ||
} | ||
|
||
impl ReticulateService { | ||
fn start(comm_id: String, comm_manager_tx: Sender<CommManagerEvent>) -> anyhow::Result<String> { | ||
let comm = CommSocket::new( | ||
CommInitiator::BackEnd, | ||
comm_id.clone(), | ||
String::from("positron.reticulate"), | ||
); | ||
|
||
let service = Self { | ||
comm, | ||
comm_manager_tx, | ||
}; | ||
|
||
let event = CommManagerEvent::Opened(service.comm.clone(), serde_json::Value::Null); | ||
service | ||
.comm_manager_tx | ||
.send(event) | ||
.or_log_error("Reticulate: Could not open comm."); | ||
|
||
spawn!(format!("ark-reticulate-{}", comm_id), move || { | ||
service | ||
.handle_messages() | ||
.or_log_error("Reticulate: Error handling messages"); | ||
}); | ||
|
||
Ok(comm_id) | ||
} | ||
|
||
fn handle_messages(&self) -> Result<(), anyhow::Error> { | ||
loop { | ||
let msg = unwrap!(self.comm.incoming_rx.recv(), Err(err) => { | ||
log::error!("Reticulate: Error while receiving message from frontend: {err:?}"); | ||
break; | ||
}); | ||
|
||
if let CommMsg::Close = msg { | ||
break; | ||
} | ||
} | ||
|
||
// before finalizing the thread we make sure to send a close message to the front end | ||
self.comm | ||
.outgoing_tx | ||
.send(CommMsg::Close) | ||
.or_log_error("Reticulate: Could not send close message to the front-end"); | ||
|
||
// Reset the global comm_id before closing | ||
let mut comm_id_guard = RETICULATE_COMM_ID.lock().unwrap(); | ||
log::info!("Reticulate Thread closing {:?}", (*comm_id_guard).clone()); | ||
*comm_id_guard = None; | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
// Creates a client instance reticulate can use to communicate with the front-end. | ||
// We should aim at having at most **1** client per R session. | ||
// Further actions that reticulate can ask the front-end can be requested through | ||
// the comm_id that is returned by this function. | ||
#[harp::register] | ||
pub unsafe extern "C" fn ps_reticulate_open(input: SEXP) -> Result<SEXP, anyhow::Error> { | ||
let main = RMain::get(); | ||
|
||
let input: RObject = input.try_into()?; | ||
let input_code: Option<String> = input.try_into()?; | ||
|
||
let mut comm_id_guard = RETICULATE_COMM_ID.lock().unwrap(); | ||
|
||
// If there's an id already registered, we just need to send the focus event | ||
if let Some(id) = comm_id_guard.deref() { | ||
// There's a comm_id registered, we just send the focus event | ||
main.get_comm_manager_tx().send(CommManagerEvent::Message( | ||
id.clone(), | ||
CommMsg::Data(json!({ | ||
"method": "focus", | ||
"params": { | ||
"input": input_code | ||
} | ||
})), | ||
))?; | ||
return Ok(R_NilValue); | ||
} | ||
|
||
let id = Uuid::new_v4().to_string(); | ||
*comm_id_guard = Some(id.clone()); | ||
|
||
ReticulateService::start(id, main.get_comm_manager_tx().clone())?; | ||
|
||
Ok(R_NilValue) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.