diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ed0c34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.Rproj.user +.Rhistory +.RData +.Ruserdata +webmice.Rproj +swagger-ui.html diff --git a/docker/Dockerfile b/docker/Dockerfile index b6a8133..9d0d2eb 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ FROM rocker/r-ver:4.3.1 -LABEL version="0.1" +LABEL version="0.1.1" LABEL description="Docker container running webmice (R, RestRserve)" ARG R_VERSION=4.3.1 @@ -16,19 +16,19 @@ RUN apt update && \ #RUN echo 'options(repos = c(CRAN = "https://cloud.r-project.org"))' >>"${R_HOME}/etc/Rprofile.site" RUN R -e "install.packages('RestRserve', repos = 'https://cloud.r-project.org')" -RUN R -e "install.packages('jsonlite')" RUN R -e "install.packages('remotes')" RUN R -e "remotes::install_github('amices/mice')" +RUN R -e "remotes::install_github('amices/micerocker')" # 0.1.3 -# Pull in webmice code -RUN mkdir /home/webmice -RUN rm -rf /home/webmice +# Create working directory RUN mkdir /home/webmice ENV WEBMICE_LOC="/home/webmice" +# Create data directory +RUN mkdir /home/webmice/data_uploads +ENV MICEROCKER_DATA_UPLOADS="/home/webmice/data_uploads" + COPY webmice.R /home/webmice/webmice.R -COPY webmice_handlers.R /home/webmice/webmice_handlers.R -COPY webmice_functions.R /home/webmice/webmice_functions.R COPY ../openapi.yaml /home/webmice/openapi.yaml WORKDIR /home/webmice diff --git a/webmice.R b/webmice.R index 0a5b85e..562b487 100644 --- a/webmice.R +++ b/webmice.R @@ -1,30 +1,24 @@ -library(RestRserve) -library(mice, warn.conflicts = FALSE) -library(jsonlite) -library(readr) -library(digest) +# remotes::install_github("amices/micerocker") + +require(mice, warn.conflicts = FALSE) # >= 3.16.4 +require(micerocker) # >= 0.1.3 +require(RestRserve) +require(readr) #' Parameters #' Code location in Docker, set through bash variable base_folder <- Sys.getenv("WEBMICE_LOC") -if(base_folder == "") { +if (base_folder == "") { base_folder <- getwd() print('No base folder for webmice set (export WEBMICE_LOC="directory").') print(paste("Set to: ", base_folder)) } -#' Imports -source(file.path(base_folder, "webmice_handlers.R")) -source(file.path(base_folder, "webmice_functions.R")) - -#' Data upload location -data_uploads = file.path(base_folder, "data_uploads") -if(!file.exists(data_uploads)){ - dir.create(data_uploads) -} +data_uploads <- "data_uploads" +Sys.setenv(MICEROCKER_DATA_UPLOADS = data_uploads) #' Application -webmice = Application$new() +webmice = RestRserve::Application$new() #' Endpoints webmice$add_post( @@ -48,7 +42,7 @@ webmice$add_get(path = "/long", FUN = impute_longfmt_handler) webmice$add_get(path = "/fit", FUN = fit_handler) webmice$add_get(path = "/pool", FUN = pool_handler) -#' Swagger +#' Swagger yaml_file = file.path(base_folder, "openapi.yaml") webmice$add_openapi(path = "/openapi.yaml", file_path = yaml_file) webmice$add_swagger_ui(path = "/doc", path_openapi = "/openapi.yaml", use_cdn = TRUE) diff --git a/webmice_functions.R b/webmice_functions.R deleted file mode 100644 index 847234d..0000000 --- a/webmice_functions.R +++ /dev/null @@ -1,134 +0,0 @@ -#' Data -#' Fetches example data from mice, returns data as json -example_data_to_json = function(name) { - result = tryCatch({ - return(toJSON(get(name))) - }, error = function(e) { - err <- c() - err$error <- "Error: data name not found" - return(toJSON(err)) - }) - return(result) -} - -#' Reads a csv file -read_file = function(path){ - tryCatch({ - data <- read.csv(path) - return(data) - }, error = function(e){ - return(NULL) - }) -} - -#' Creates a hash for a data file name -md5_string = function(string) { - return(digest(paste(Sys.time(), string), algo="md5", serialize=F)) -} - -#' Convert JSON to R -#' Takes a json string and returns it as R list -#' Test: -#' input <- list(data="nhanes", maxit=2, m=2, seed=1) -json_to_parameters = function(json_payload){ - result = tryCatch({ - params = fromJSON(json_payload) - return(params) - }, error = function(e) { - return(NULL) - }) -} - -#' Mice return functions -#' Takes the result of the imputation and returns the long format of the data -imp_result_long_fmt = function(imp){ - return(toJSON(complete(imp, "long"))) -} - -imp_result_pred_matrix = function(imp){ - res <- c() - res$error <- "not implemented" - return(toJSON(res)) -} - -#' Mice functions -impute = function(data, maxit, m, seed) { - imp <- list() - imp$error <- "" - result = tryCatch({ - imp <- mice(data, maxit=maxit, m=m, seed=seed) - return(imp) - }, error = function(e) { - return("Failure: mice") - }) - - if(result == "Failure: mice"){ - imp$error <- result - return(imp) - } -} - -#' Calls mice's imputation function with parameters provided in a list 'params' -#' Expected: params$data, params$maxit, params$m, params$seed -#' data: example data name, a hash from an uploaded file, or a csv filee -call_mice = function(params){ - imp <- list() - result = tryCatch({ - data <- get(params$data) - }, error = function(e){ - return(-1) - }) - - if(typeof(result) == "list") { - print("DEBUG: Imputation on example data set") - imp <- impute(data, maxit=params$maxit, m=params$m, seed=params$seed) - return(imp) - } - if(typeof(params$data) == "character" && endsWith(params$data, ".csv")){ - print("DEBUG: Imputation on local csv file") - df <- read_file(params$data) - if(is.null(df)){ - imp$error <- "Failure: reading local csv file" - return(imp) - } - imp <- impute(nhanes, maxit=params$maxit, m=params$m, seed=params$seed) - return(imp) - } - if(typeof(params$data) == "character"){ - print("DEBUG: Imputation on uploaded file") - df <- read_file(file.path(data_uploads, params$data)) - if(is.null(df)){ - imp$error <- "Failure: reading file, not an example dataset or file on server" - return(imp) - } - imp <- impute(df, maxit=params$maxit, m=params$m, seed=params$seed) - return(imp) - } -} - -call_with = function(data, model, formula){ - fit <- c() - if(model == "lm"){ - fit <- with(data, lm(as.formula(formula))) - } - if(model == "glm"){ - fit <- with(data, glm(as.formula(formula))) - } - fit$error <- "Model not known" - return(toJSON(summary(fit), force=TRUE)) -} - -call_pool = function(data){ - pool <- c() - if(packageVersion("mice") < "3.16.4" ){ - pool$error <- "ERROR pool.table: need mice version 3.16.4 or higher" - return(toJSON(pool, force=TRUE)) - } - - if(typeof(data) == "list"){ - pool <- pool.table(data) - } else { - pool$error <- "Input data not of correct type (summary(fit))" - } - return(toJSON(pool, force=TRUE)) -} diff --git a/webmice_handlers.R b/webmice_handlers.R deleted file mode 100644 index 7d1572a..0000000 --- a/webmice_handlers.R +++ /dev/null @@ -1,69 +0,0 @@ -pool_handler = function(.req, .res) { - poolJson <- '' - json_payload <- as.character(.req$parameters_query[["payload"]]) - if (length(json_payload) == 0L) {raise(HTTPError$bad_request())} - params <- json_to_parameters(json_payload) - if(is.null(params$data)) {poolJson <- "Error: no data"} - if(poolJson == ''){ - print("DEBUG: Calling pool.table (requires 3.16.4)") - poolJson <- call_pool(params$data) - } - .res$set_body(poolJson) - .res$set_content_type("text/plain") -} - -fit_handler = function(.req, .res) { - fitJson <- '' - json_payload <- as.character(.req$parameters_query[["payload"]]) - # if answers are copied straight from the Swagger interface, there are too many backslashes - # json_payload <- gsub('\\\\', '', input) - if (length(json_payload) == 0L) {raise(HTTPError$bad_request())} - params <- json_to_parameters(json_payload) - if(is.null(params$data)) {fitJson <- "Error: no data"} - if(is.null(params$model)) {fitJson <- "Error: no model"} - if(is.null(params$formula)) {fitJson <- "Error: no formula"} - - if(fitJson == ''){ - print("DEBUG: Calling with (fitting function)") - fitJson <- call_with(params$data, params$model, params$formula) - } - .res$set_body(fitJson) - .res$set_content_type("text/plain") -} - -impute_longfmt_handler = function(.req, .res) { - json_payload <- as.character(.req$parameters_query[["payload"]]) - - if (length(json_payload) == 0L) {raise(HTTPError$bad_request())} - # check if convertible to json - params <- json_to_parameters(json_payload) - - # impute function needs data, maxit, m, seed - if(is.null(params$data)) {raise(HTTPError$not_acceptable())} - if(is.null(params$maxit)) {raise(HTTPError$not_acceptable())} - if(is.null(params$m)) {raise(HTTPError$not_acceptable())} - if(is.null(params$seed)) {raise(HTTPError$not_acceptable())} - - print("DEBUG: Calling mice") - imp <- call_mice(params) - - if(is.null(imp$error)){ - .res$set_body(imp_result_long_fmt(imp)) - } else{ - .res$set_body(toJSON(imp)) - } - .res$set_content_type("text/plain") -} - -example_data_handler = function(.req, .res) { - example_name <- as.character(.req$parameters_query[["name"]]) - .res$set_body(example_data_to_json(example_name)) - .res$set_content_type("text/plain") -} - -mice_version_handler = function(.req, .res) { - version <- list() - version$mice <- sessionInfo("mice")$otherPkgs$mice$Version - .res$set_body(toJSON(version)) - .res$set_content_type("text/plain") -}