From 0b24c189a02d4527c6715f018f7e5c562f588d07 Mon Sep 17 00:00:00 2001 From: Scott Chamberlain Date: Mon, 8 Jan 2024 06:47:37 -0800 Subject: [PATCH] first draft of the package --- .Rbuildignore | 7 +++++ .gitignore | 1 + DESCRIPTION | 17 +++++++++++ Makefile | 42 ++++++++++++++++++++++++++ NAMESPACE | 12 ++++++++ R/auth.R | 47 ++++++++++++++++++++++++++++++ R/cancel.R | 14 +++++++++ R/job_status.R | 13 +++++++++ R/proofr-package.R | 7 +++++ R/start.R | 21 +++++++++++++ R/utils.R | 5 ++++ README.Rmd | 34 +++++++++++++++++++++ README.md | 26 +++++++++++++++++ _pkgdown.yml | 4 +++ man/proof_authenticate.Rd | 25 ++++++++++++++++ man/proof_header.Rd | 19 ++++++++++++ man/proof_job_cancel.Rd | 22 ++++++++++++++ man/proof_job_start.Rd | 26 +++++++++++++++++ man/proof_job_status.Rd | 22 ++++++++++++++ man/proofr-package.Rd | 15 ++++++++++ tests/testthat.R | 4 +++ tests/testthat/test-proof_header.R | 14 +++++++++ 22 files changed, 397 insertions(+) create mode 100644 .Rbuildignore create mode 100644 .gitignore create mode 100644 DESCRIPTION create mode 100644 Makefile create mode 100644 NAMESPACE create mode 100644 R/auth.R create mode 100644 R/cancel.R create mode 100644 R/job_status.R create mode 100644 R/proofr-package.R create mode 100644 R/start.R create mode 100644 R/utils.R create mode 100644 README.Rmd create mode 100644 README.md create mode 100644 _pkgdown.yml create mode 100644 man/proof_authenticate.Rd create mode 100644 man/proof_header.Rd create mode 100644 man/proof_job_cancel.Rd create mode 100644 man/proof_job_start.Rd create mode 100644 man/proof_job_status.Rd create mode 100644 man/proofr-package.Rd create mode 100644 tests/testthat.R create mode 100644 tests/testthat/test-proof_header.R diff --git a/.Rbuildignore b/.Rbuildignore new file mode 100644 index 0000000..cc7cf42 --- /dev/null +++ b/.Rbuildignore @@ -0,0 +1,7 @@ +^LICENSE\.md$ +^_pkgdown\.yml$ +^docs$ +^pkgdown$ +^README\.Rmd$ +^\.github$ +^Makefile$ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8f8d46 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +docs diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..48b7e3c --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,17 @@ +Package: proofr +Title: Client for the PROOF API +Version: 0.0.0.91 +Authors@R: + person("Scott", "Chamberlain", , "sachamber@fredhutch.org", role = c("aut", "cre"), + comment = c(ORCID = "0000-0003-1444-9135")) +Description: Client for the PROOF API. +License: MIT + file LICENSE +Encoding: UTF-8 +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.2.3 +Imports: + httr +Suggests: + testthat (>= 3.0.0), + withr +Config/testthat/edition: 3 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..40edfca --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +PACKAGE := $(shell grep '^Package:' DESCRIPTION | sed -E 's/^Package:[[:space:]]+//') +RSCRIPT = Rscript --no-init-file +FILE_TARGET := "R/${FILE}.R" + +install: doc build + R CMD INSTALL . && rm *.tar.gz + +build: + R CMD build . + +doc: + ${RSCRIPT} -e "devtools::document()" + +eg: + ${RSCRIPT} -e "devtools::run_examples(run_dontrun = TRUE)" + +check: build + _R_CHECK_CRAN_INCOMING_=FALSE R CMD CHECK --as-cran --no-manual `ls -1tr ${PACKAGE}*gz | tail -n1` + @rm -f `ls -1tr ${PACKAGE}*gz | tail -n1` + @rm -rf ${PACKAGE}.Rcheck + +vign_getting_started: + cd vignettes;\ + ${RSCRIPT} -e "Sys.setenv(NOT_CRAN='true'); knitr::knit('${PACKAGE}.Rmd.og', output = '${PACKAGE}.Rmd')";\ + cd .. + +test: + ${RSCRIPT} -e "devtools::test()" + +readme: + ${RSCRIPT} -e "knitr::knit('README.Rmd')" + +lint_package: + ${RSCRIPT} -e "lintr::lint_package()" + +# use: `make style_file FILE=stuff.R` +# ("R/" is prepended); accepts 1 file only +style_file: + ${RSCRIPT} -e 'styler::style_file(${FILE_TARGET})' + +style_package: + ${RSCRIPT} -e "styler::style_pkg()" diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000..522089e --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,12 @@ +# Generated by roxygen2: do not edit by hand + +export(proof_authenticate) +export(proof_job_cancel) +export(proof_job_start) +export(proof_job_status) +importFrom(httr,DELETE) +importFrom(httr,GET) +importFrom(httr,POST) +importFrom(httr,add_headers) +importFrom(httr,content) +importFrom(httr,stop_for_status) diff --git a/R/auth.R b/R/auth.R new file mode 100644 index 0000000..711e513 --- /dev/null +++ b/R/auth.R @@ -0,0 +1,47 @@ +find_token <- function(token = NULL) { + if (!is.null(token)) { + return(token) + } + token <- Sys.getenv("PROOF_TOKEN") + if (identical(token, "")) { + stop("token not found - see ?proof_authenticate") + } + token +} + +#' Get header for PROOF API calls +#' +#' Utility method to get header for PROOF API calls +#' +#' @keywords internal +#' @param token PROOF API token +#' @return A `request` S3 class with the HTTP header that can be passed +#' to `httr::GET()`, `httr::POST()`, etc. +proof_header <- function(token = NULL) { + add_headers(Authorization = paste0("Bearer ", find_token(token))) +} + +#' Authenticate with PROOF API +#' +#' Authenticates with HutchNet credentials, returns PROOF API token +#' +#' @export +#' @param username (character) HutchNet username +#' @param password (character) HutchNet password +#' @return A single token (character) for bearer authentication with +#' the PROOF API +#' @examples +#' # Sys.getenv("PROOF_TOKEN") +#' # x <- proof_authenticate() +#' # Sys.getenv("PROOF_TOKEN") +proof_authenticate <- function(username, password) { + response <- POST(make_url("authenticate"), body = list( + username = username, + password = password + ), encode = "json") + stop_for_status(response) + parsed <- content(response, as = "parsed") + token <- parsed$token + Sys.setenv(PROOF_TOKEN = token) + token +} diff --git a/R/cancel.R b/R/cancel.R new file mode 100644 index 0000000..fe72378 --- /dev/null +++ b/R/cancel.R @@ -0,0 +1,14 @@ +#' Delete PROOF Cromwell server +#' +#' @export +#' @references +#' @details Does not return PROOF/Cromwell server URL, for that you have to +#' periodically call [proof_job_status()], or wait for the email from the +#' PROOF API +#' @return A list with fields `job_id` and `info` +proof_job_cancel <- function() { + response <- DELETE(make_url("cromwell-server"), proof_header()) + # FIXME: better error handling - surface error messages + stop_for_status(response) + content(response, as = "parsed") +} diff --git a/R/job_status.R b/R/job_status.R new file mode 100644 index 0000000..adc93d5 --- /dev/null +++ b/R/job_status.R @@ -0,0 +1,13 @@ +#' Get PROOF API job status - is job running, what's its URL... +#' +#' @export +#' @references +#' @return A list with slots: +#' - `canJobStart` +#' - `jobStatus` +#' - `cromwellUrl` +proof_job_status <- function() { + response <- GET(make_url("cromwell-server"), proof_header()) + stop_for_status(response) + content(response, as = "parsed") +} diff --git a/R/proofr-package.R b/R/proofr-package.R new file mode 100644 index 0000000..e4e39ca --- /dev/null +++ b/R/proofr-package.R @@ -0,0 +1,7 @@ +#' @keywords internal +"_PACKAGE" + +## usethis namespace: start +#' @importFrom httr GET POST DELETE add_headers content stop_for_status +## usethis namespace: end +NULL diff --git a/R/start.R b/R/start.R new file mode 100644 index 0000000..80e48ee --- /dev/null +++ b/R/start.R @@ -0,0 +1,21 @@ +#' Start PROOF Cromwell server +#' +#' @export +#' @param pi_name (character) PI name in the form last_f; only needed if user +#' is in more than one SLURM account +#' @references +#' @details Does not return PROOF/Cromwell server URL, for that you have to +#' periodically call [proof_job_status()], or wait for the email from the +#' PROOF API +#' @return A list with fields `job_id` and `info` +proof_job_start <- function(pi_name = NULL) { + response <- POST( + make_url("cromwell-server"), + proof_header(), + body = list(pi_name = pi_name), + encode = "json" + ) + # FIXME: better error handling - surface error messages + stop_for_status(response) + content(response, as = "parsed") +} diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 0000000..6c78b7e --- /dev/null +++ b/R/utils.R @@ -0,0 +1,5 @@ +proof_base <- "https://proof-api.fredhutch.org" + +make_url <- function(...) { + file.path(proof_base, ...) +} diff --git a/README.Rmd b/README.Rmd new file mode 100644 index 0000000..95941d1 --- /dev/null +++ b/README.Rmd @@ -0,0 +1,34 @@ + + +```{r include=FALSE} +knitr::opts_chunk$set( + comment = "#>", + collapse = TRUE, + warning = FALSE, + fig.path = "man/figures/README-", + out.width = "100%" +) +``` + +# proofr + + +[![Project Status: Concept – Not useable, no support, not open to feedback, unstable API.](https://getwilds.github.io/badges/badges/concept.svg)](https://getwilds.github.io/badges/#concept) +[![R-CMD-check](https://github.com/getwilds/proofr/actions/workflows/R-CMD-check.yaml/badge.svg?branch=dev)](https://github.com/getwilds/proofr/actions/workflows/R-CMD-check.yaml) +[![R-CMD-check](https://github.com/getwilds/proofr/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/getwilds/proofr/actions/workflows/R-CMD-check.yaml) + + +R client for the PROOF API + +PROOF API is at + + + +## Installation + +You can install the development version of proofr from [GitHub](https://github.com/) with: + +```r +# install.packages("pak") +pak::pak("getwilds/proofr") +``` diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c4d6a9 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ + + + + +# proofr + + +[![Project Status: Concept – Not useable, no support, not open to feedback, unstable API.](https://getwilds.github.io/badges/badges/concept.svg)](https://getwilds.github.io/badges/#concept) +[![R-CMD-check](https://github.com/getwilds/proofr/actions/workflows/R-CMD-check.yaml/badge.svg?branch=dev)](https://github.com/getwilds/proofr/actions/workflows/R-CMD-check.yaml) +[![R-CMD-check](https://github.com/getwilds/proofr/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/getwilds/proofr/actions/workflows/R-CMD-check.yaml) + + +R client for the PROOF API + +PROOF API is at + + + +## Installation + +You can install the development version of proofr from [GitHub](https://github.com/) with: + +```r +# install.packages("pak") +pak::pak("getwilds/proofr") +``` diff --git a/_pkgdown.yml b/_pkgdown.yml new file mode 100644 index 0000000..d71acfb --- /dev/null +++ b/_pkgdown.yml @@ -0,0 +1,4 @@ +url: ~ +template: + bootstrap: 5 + diff --git a/man/proof_authenticate.Rd b/man/proof_authenticate.Rd new file mode 100644 index 0000000..71e7636 --- /dev/null +++ b/man/proof_authenticate.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/auth.R +\name{proof_authenticate} +\alias{proof_authenticate} +\title{Authenticate with PROOF API} +\usage{ +proof_authenticate(username, password) +} +\arguments{ +\item{username}{(character) HutchNet username} + +\item{password}{(character) HutchNet password} +} +\value{ +A single token (character) for bearer authentication with +the PROOF API +} +\description{ +Authenticates with HutchNet credentials, returns PROOF API token +} +\examples{ +# Sys.getenv("PROOF_TOKEN") +# x <- proof_authenticate() +# Sys.getenv("PROOF_TOKEN") +} diff --git a/man/proof_header.Rd b/man/proof_header.Rd new file mode 100644 index 0000000..7d1667e --- /dev/null +++ b/man/proof_header.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/auth.R +\name{proof_header} +\alias{proof_header} +\title{Get header for PROOF API calls} +\usage{ +proof_header(token = NULL) +} +\arguments{ +\item{token}{PROOF API token} +} +\value{ +A \code{request} S3 class with the HTTP header that can be passed +to \code{httr::GET()}, \code{httr::POST()}, etc. +} +\description{ +Utility method to get header for PROOF API calls +} +\keyword{internal} diff --git a/man/proof_job_cancel.Rd b/man/proof_job_cancel.Rd new file mode 100644 index 0000000..f0eb4c8 --- /dev/null +++ b/man/proof_job_cancel.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/cancel.R +\name{proof_job_cancel} +\alias{proof_job_cancel} +\title{Delete PROOF Cromwell server} +\usage{ +proof_job_cancel() +} +\value{ +A list with fields \code{job_id} and \code{info} +} +\description{ +Delete PROOF Cromwell server +} +\details{ +Does not return PROOF/Cromwell server URL, for that you have to +periodically call \code{\link[=proof_job_status]{proof_job_status()}}, or wait for the email from the +PROOF API +} +\references{ +\url{https://github.com/FredHutch/proof-api#delete-cromwell-server} +} diff --git a/man/proof_job_start.Rd b/man/proof_job_start.Rd new file mode 100644 index 0000000..8dc42e0 --- /dev/null +++ b/man/proof_job_start.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/start.R +\name{proof_job_start} +\alias{proof_job_start} +\title{Start PROOF Cromwell server} +\usage{ +proof_job_start(pi_name = NULL) +} +\arguments{ +\item{pi_name}{(character) PI name in the form last_f; only needed if user +is in more than one SLURM account} +} +\value{ +A list with fields \code{job_id} and \code{info} +} +\description{ +Start PROOF Cromwell server +} +\details{ +Does not return PROOF/Cromwell server URL, for that you have to +periodically call \code{\link[=proof_job_status]{proof_job_status()}}, or wait for the email from the +PROOF API +} +\references{ +\url{https://github.com/FredHutch/proof-api#post-cromwell-server} +} diff --git a/man/proof_job_status.Rd b/man/proof_job_status.Rd new file mode 100644 index 0000000..b39fa91 --- /dev/null +++ b/man/proof_job_status.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/job_status.R +\name{proof_job_status} +\alias{proof_job_status} +\title{Get PROOF API job status - is job running, what's its URL...} +\usage{ +proof_job_status() +} +\value{ +A list with slots: +\itemize{ +\item \code{canJobStart} +\item \code{jobStatus} +\item \code{cromwellUrl} +} +} +\description{ +Get PROOF API job status - is job running, what's its URL... +} +\references{ +\url{https://github.com/FredHutch/proof-api#get-cromwell-server} +} diff --git a/man/proofr-package.Rd b/man/proofr-package.Rd new file mode 100644 index 0000000..82f83c5 --- /dev/null +++ b/man/proofr-package.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/proofr-package.R +\docType{package} +\name{proofr-package} +\alias{proofr} +\alias{proofr-package} +\title{proofr: Client for the PROOF API} +\description{ +Client for the PROOF API. +} +\author{ +\strong{Maintainer}: Scott Chamberlain \email{sachamber@fredhutch.org} (\href{https://orcid.org/0000-0003-1444-9135}{ORCID}) + +} +\keyword{internal} diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..a8f8e87 --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,4 @@ +library(testthat) +library(proofr) + +test_check("proofr") diff --git a/tests/testthat/test-proof_header.R b/tests/testthat/test-proof_header.R new file mode 100644 index 0000000..40a4cf1 --- /dev/null +++ b/tests/testthat/test-proof_header.R @@ -0,0 +1,14 @@ +# These tests do not touch the API + +test_that("proof_header", { + # errors if no env var set and no string supplied + expect_error(proof_header(), "token not found") + + # returns token if given + expect_match(proof_header("adf")$headers[[1]], "adf") + + # If PROOF_TOKEN env var set, fxn can find it + withr::with_envvar(c("PROOF_TOKEN" = "world"), { + expect_match(proof_header()$headers[[1]], "world") + }) +})