Skip to content

Commit d7bb74a

Browse files
committed
WIP: working static mode in notebook. Cannot get bidirectional comms working. Implemented anywidget JS installation script based on Deno equivalent but cannot get JupyterLab to render the application/widget mimebundle as a widget
1 parent 8379805 commit d7bb74a

File tree

4 files changed

+1243
-3
lines changed

4 files changed

+1243
-3
lines changed

DESCRIPTION

+3-1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ Suggests:
3232
bslib,
3333
pkgdown,
3434
knitr,
35-
rmarkdown
35+
rmarkdown,
36+
IRkernel,
37+
IRdisplay

R/jupyter_install.R

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
user_data_dir <- function() {
2+
if (Sys.info()["sysname"] == "Windows") {
3+
appdata <- Sys.getenv("APPDATA")
4+
if(appdata == "") {
5+
stop("APPDATA environment variable not set")
6+
}
7+
return(file.path(appdata, "jupyter"))
8+
}
9+
if (Sys.info()["sysname"] == "Darwin") {
10+
home <- Sys.getenv("HOME")
11+
if(home == "") {
12+
stop("HOME environment variable not set")
13+
}
14+
return(file.path(home, "Library", "Jupyter"))
15+
}
16+
home <- Sys.getenv("XDG_DATA_HOME")
17+
if(home == "") {
18+
home <- Sys.getenv("HOME")
19+
}
20+
if(home == "") {
21+
stop("HOME environment variable not set")
22+
}
23+
return(file.path(home, ".local", "share", "jupyter"))
24+
}
25+
26+
guess_sys_prefix <- function() {
27+
dirs <- strsplit(Sys.getenv("PATH"), ":")[[1]]
28+
is_windows <- Sys.info()["sysname"] == "Windows"
29+
if(is_windows) {
30+
pathext <- strsplit(Sys.getenv("PATHEXT"), ";")
31+
} else {
32+
pathext <- c("")
33+
}
34+
for (dir in dirs) {
35+
for (ext in pathext) {
36+
bin <- file.path(dir, paste0("python", ext))
37+
if (file.exists(bin)) {
38+
if(is_windows) {
39+
return(normalizePath(dirname(bin)))
40+
} else {
41+
return(normalizePath(dirname(dirname(bin))))
42+
}
43+
}
44+
}
45+
}
46+
}
47+
48+
system_data_dirs <- function() {
49+
if (Sys.info()["sysname"] == "Windows") {
50+
programdata <- Sys.getenv("PROGRAMDATA")
51+
if(programdata == "") {
52+
stop("PROGRAMDATA environment variable not set")
53+
}
54+
return(c(file.path(programdata, "jupyter")))
55+
}
56+
return(c("/usr/local/share/jupyter", "/usr/share/jupyter"))
57+
}
58+
59+
find_data_dir <- function() {
60+
sys_prefix <- guess_sys_prefix()
61+
if (!is.null(sys_prefix)) {
62+
return(file.path(sys_prefix, "share", "jupyter"))
63+
}
64+
user_dir <- user_data_dir()
65+
if(dir.exists(user_dir)) {
66+
return(user_dir)
67+
}
68+
return(system_data_dirs()[0])
69+
}
70+
71+
library(httr2)
72+
fetch_package_info <- function(name) {
73+
req <- request(paste0("https://pypi.org/pypi/", name, "/json"))
74+
resp <- req_perform(req) |> resp_body_json()
75+
return(resp)
76+
}
77+
78+
fetch_wheel <- function(info, version = NA) {
79+
if(!is.na(version)) {
80+
releases <- info[["releases"]][[version]]
81+
} else {
82+
releases <- info[["urls"]]
83+
}
84+
if (length(releases) < 1) {
85+
stop("No entries found for version ${version}");
86+
}
87+
wheel <- NA
88+
for(release in releases) {
89+
if(release[["packagetype"]] == "bdist_wheel") {
90+
wheel <- release
91+
}
92+
}
93+
if (all(is.na(wheel))) {
94+
stop("No wheel found for version ${version}");
95+
}
96+
97+
req <- request(wheel[["url"]])
98+
resp <- req_perform(req) |> resp_body_raw()
99+
100+
tmp <- tempfile()
101+
writeBin(resp, con = tmp)
102+
103+
result <- list(
104+
version = version,
105+
wheel = tmp
106+
)
107+
if(is.na(version)) {
108+
result[["version"]] <- info[["info"]][["version"]]
109+
}
110+
111+
return(result)
112+
}
113+
114+
library(stringr)
115+
116+
extract_data_files <- function(tmp_zipped_wheel, out_dir) {
117+
zip_list <- unzip(tmp_zipped_wheel, list = TRUE)$Name
118+
data_prefix <- "^.*\\.data\\/data\\/share\\/jupyter\\/"
119+
120+
print(zip_list)
121+
matches <- zip_list[str_detect(zip_list, data_prefix)]
122+
print(matches)
123+
for(match in matches) {
124+
out_path <- file.path(out_dir, gsub(data_prefix, "", match))
125+
unzip(tmp_zipped_wheel, files = match, exdir = dirname(out_path), junkpaths = TRUE)
126+
}
127+
}
128+
129+
# Reference: https://github.com/manzt/anywidget/blob/ed4176ac71da232b676124a5236367ce09703896/packages/deno/src/install.ts#L99
130+
install_anywidget <- function() {
131+
out_dir <- find_data_dir()
132+
133+
info <- fetch_package_info("anywidget")
134+
wheel_info <- fetch_wheel(info)
135+
extract_data_files(wheel_info[["wheel"]], out_dir)
136+
version <- wheel_info[["version"]]
137+
print("Installed anywidget ${version} in ${out_dir}")
138+
}
139+
140+
install_jupyterlab_widgets <- function() {
141+
out_dir <- find_data_dir()
142+
143+
info <- fetch_package_info("jupyterlab_widgets")
144+
wheel_info <- fetch_wheel(info)
145+
extract_data_files(wheel_info[["wheel"]], out_dir)
146+
version <- wheel_info[["version"]]
147+
print("Installed jupyterlab_widgets ${version} in ${out_dir}")
148+
}
149+
150+
install <- function() {
151+
install_anywidget()
152+
install_jupyterlab_widgets()
153+
}

R/widget.R

+10-2
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ AnyHtmlWidget <- R6::R6Class("AnyHtmlWidget",
111111
private$server_host <- "0.0.0.0"
112112
private$server_port <- httpuv::randomPort(min = 8000, max = 9000, n = 1000)
113113

114-
if(!.mode %in% c("static", "gadget", "shiny", "dynamic")) {
114+
if(!.mode %in% c("static", "gadget", "shiny", "dynamic", "jupyter")) {
115115
stop("Invalid widget mode.")
116116
}
117117
private$mode <- .mode
@@ -194,7 +194,7 @@ AnyHtmlWidget <- R6::R6Class("AnyHtmlWidget",
194194
#' Set the widget mode.
195195
#' @param mode The new widget mode.
196196
set_mode = function(mode) {
197-
if(!mode %in% c("static", "gadget", "shiny", "dynamic")) {
197+
if(!mode %in% c("static", "gadget", "shiny", "dynamic", "jupyter")) {
198198
stop("Invalid widget mode.")
199199
}
200200
private$mode <- mode
@@ -244,6 +244,14 @@ AnyHtmlWidget <- R6::R6Class("AnyHtmlWidget",
244244
render = function() {
245245
if(private$mode == "static") {
246246
invoke_static(self)
247+
} else if(private$mode == "jupyter") {
248+
if (!(require("IRkernel", quietly = TRUE) && require("IRdisplay", quietly = TRUE))) {
249+
stop("IRkernel and IRdisplay packages are required to use Jupyter mode.")
250+
}
251+
if(!getOption('jupyter.rich_display')) {
252+
stop("jupyter.rich_display is not TRUE but jupyter mode was specified.")
253+
}
254+
IRdisplay::display(invoke_static(self))
247255
} else if(private$mode == "gadget") {
248256
invoke_gadget(self)
249257
} else if(private$mode == "dynamic") {

0 commit comments

Comments
 (0)