avoiding the use of `library` in Shiny apps
Closed this issue ยท 10 comments
Shiny apps require the use of library
or require
. It seems that the import
package would be a good option when an app requires several packages. My shiny app is an R-package with a namespace. I have been trying to figure out how to import the various functions from different packages (see attempt below) but haven't gotten things working yet. Any ideas / suggestions? Related to #4
copy_imported <- function(.from) {
from <- as.character(substitute(.from))
import_list <- getNamespaceImports(from)
parent <- parent.frame()
import_names <- names(import_list)
for (i in unique(import_names)) {
if (i %in% c("base","shiny","magrittr")) next
symbols <- unlist(import_list[which(i == import_names)])
for (j in symbols) {
# do.call(import::from, list(i = as.symbol(i), j = as.symbol(j)))
fn <- get(j, envir = asNamespace(i), inherits = TRUE)
assign(j, eval.parent(call("function", formals(fn), body(fn))), parent)
}
}
invisible(NULL)
}
To be honest, I am not sure exatly what it is you want. Can you provide a minimum working example packge where a problem is obvious?
In most shiny apps you simply specify a number of libraries to load. For example, if r_pkgs is a list of package names you could use something like the below. I'd rather not do that and use import
instead. However, the list of functions to import can be pretty long and, if the shiny app is part of a package, is likely already specified in NAMESPACE
. I tried to write a function that could be used to (1) query the package namespace and (2) import all functions specified there. Does this sound feasible?
sapply(r_pkgs, require, character.only = TRUE)
If you are deploying a shiny app as a package and want to avoid using library
or require
statements in server.r
itself, one simple option is use depends
rather than imports
in the package description. I agree that adding support for shiny apps to be evaluated locally in a package (and therefore being able to use functions imported using the namespace) would be great.
Thinking ...
-
Why is
library
not good enough here, if you're aiming at importing everything anyways? Is it because private objects are not exposed? -
I think it would be better if
shiny::runApp
would allow running within the current environment, if say.packageName
exists; specifically to allow packages to bundle apps, which could make use of both public and private API of the package.
@smbache That is a really interesting approach! I don't think it would work, however, when running on shiny-server. Suppose a shiny-app is not installed as a package on a server but available only as source code in a directory (i.e., the usual approach for shiny apps). The namespace file does, however, have the functions needed in the app for when it is run locally. So rather than use library(psych)
, for example, it would be preferable to source only the functions needed, thereby minimizing the risk of conflicting function names etc..
@smbache For now I'm use the following in global.R to import specific functions when (1) the shiny app is in an R-package and (2) is run on a server. Seems to work ok ... although I'd prefer to avoid eval-parse. Perhaps import
could have a character.only
option like library
?
import_fs <- function(ns, libs = c(), incl = c(), excl = c()) {
tmp <- sapply(libs, library, character.only = TRUE); rm(tmp)
if (length(incl) != 0 || length(excl) != 0) {
import_list <- getNamespaceImports(ns)
if (length(incl) == 0)
import_list[names(import_list) %in% c("base", "methods", "stats", "utils", libs, excl)] <- NULL
else
import_list <- import_list[names(import_list) %in% incl]
import_names <- names(import_list)
for (i in seq_len(length(import_list))) {
fun <- import_list[[i]]
lib <- import_names[[i]]
eval(parse(text = paste0("import::from(",lib,", '",paste0(fun,collapse="', '"),"')")))
}
}
invisible()
}
if (!"package:myrpackage" %in% search())
import_fs("myrpackage", incl = c("magrittr","ggplot2","lubridate","tidyr","dplyr","broom"))