diff --git a/DESCRIPTION b/DESCRIPTION index 4c01fc6..19da745 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: assertr Type: Package Title: Assertive Programming for R Analysis Pipelines -Version: 0.5 +Version: 0.5.5 Authors@R: person("Tony", "Fischetti", email="tony.fischetti@gmail.com", role = c("aut", "cre")) Maintainer: Tony Fischetti diff --git a/NEWS b/NEWS index b615d5f..fd280bb 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,7 @@ +# assertr 0.5.5 + +* added support for parameterized error functions + # assertr 0.5 * improved performance by adding support for vectorized predicates diff --git a/R/assertions.R b/R/assertions.R index 27ffea2..1ab4bdc 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -19,6 +19,8 @@ #' Uses dplyr's \code{select} to select #' columns from data. #' @param .dots Use assert_() to select columns using standard evaluation. +#' @param error_fun Function to call if assertion fails. Takes one error +#' string. Uses \code{stop} by default #' @param .nameofpred Text representation of predicate for printing in case #' of assertion violation. Will automatically be retrieved if left #' blank (default) @@ -125,6 +127,8 @@ assert_ <- function(data, predicate, ..., .dots, error_fun=assertr_stop, #' Uses dplyr's \code{select} to select #' columns from data. #' @param .dots Use insist_() to select columns using standard evaluation. +#' @param error_fun Function to call if assertion fails. Takes one error +#' string. Uses \code{stop} by default #' @param .nameofpred Text representation of predicate for printing in case #' of assertion violation. Will automatically be retrieved if left #' blank (default) @@ -218,6 +222,8 @@ insist_ <- function(data, predicate_generator, ..., .dots, #' #' @param data A data frame, list, or environment #' @param expr A logical expression +#' @param error_fun Function to call if assertion fails. Takes one error +#' string. Uses \code{stop} by default #' #' @return data if verification passes. error if not. #' @note See \code{vignette("assertr")} for how to use this in context @@ -252,7 +258,7 @@ insist_ <- function(data, predicate_generator, ..., .dots, #' #' #' @export -verify <- function(data, expr){ +verify <- function(data, expr, error_fun=stop){ expr <- substitute(expr) # conform to terminology from subset envir <- data @@ -262,5 +268,6 @@ verify <- function(data, expr){ return(data) num.violations <- sum(!logical.results) error.message <- make.verify.error.message(num.violations) - stop(error.message) + error.message <- paste0(error.message, collapse = '') + error_fun(error.message) } diff --git a/inst/doc/assertr.Rmd b/inst/doc/assertr.Rmd index 3190242..6db3824 100644 --- a/inst/doc/assertr.Rmd +++ b/inst/doc/assertr.Rmd @@ -342,6 +342,55 @@ Awesome! Now we can add an arbitrary number of assertions, as the need arises, without touching the real logic. +### advanced: send email reports using custom error functions + +One particularly cool application of `assertr` is to use it as a data integrity +checker for frequently updated data sources. A script can download new data as +it becomes available, and then run `assertr` checks on it. This makes assertr +into a sort of "continuous integration" tool (but for data, +not code.) + +In an unsupervised "continuous integration" environment, you need a way to +discover that the assertions failed. In CI-as-a-service in the software world, +failed automated checks often send an email of reporting the maintainer of a +botched build; why not bring that functionality to `assertr`?! + +All assertion verbs in `assertr` support a custom error function. By default, +the error function is R's built-in `stop` function, which displays a message +and halts execution. You can specify your own to hijack this behavior and +redirect flow-of-control wherever you want. + +All custom error functions take one argument: the actual error message that +`assertr` assembles based on which assertions fail. In this case, we will +build a function that takes that message and emails it to someone before +halting execution. We will use the `mailR` package to send the mail. + +```{r eval=FALSE, purl = FALSE} + +library(mailR) + +email_me <- function(err_str){ + send.mail(from="assertr@gmail.com", to="YOU@gmail.com", + subject="error from assertr", body=err_str, + smtp = list(host.name="aspmx.l.google.com", port=25), + authenticate = FALSE, send=TRUE) + stop(err_str, call.=FALSE) +} + +questionable_mtcars %>% + verify(nrow(.) > 10, error_fun=email_me) %>% + verify(mpg > 0, error_fun=email_me) %>% + insist(within_n_sds(4), mpg, error_fun=email_me) %>% + assert(in_set(0,1), am, vs, error_fun=email_me) + # ... +``` + +(this particular `send.mail` formulation will only for gmail receptients; +see the `mailR` documentation for more information) + +Now you'll get notified of any failed assertions via email. Groovy! + + ### advanced: creating your own predicate generators for `insist` `assertr` is build with robustness, correctness, and extensibility in mind. diff --git a/inst/doc/assertr.html b/inst/doc/assertr.html index d53afbc..a5ebc74 100644 --- a/inst/doc/assertr.html +++ b/inst/doc/assertr.html @@ -10,7 +10,7 @@ - + Assertive R Programming with assertr @@ -54,7 +54,7 @@ @@ -259,6 +259,31 @@

combining chains of assertions

## 3 8 15.10000

Awesome! Now we can add an arbitrary number of assertions, as the need arises, without touching the real logic.

+
+

advanced: send email reports using custom error functions

+

One particularly cool application of assertr is to use it as a data integrity checker for frequently updated data sources. A script can download new data as it becomes available, and then run assertr checks on it. This makes assertr into a sort of “continuous integration” tool (but for data, not code.)

+

In an unsupervised “continuous integration” environment, you need a way to discover that the assertions failed. In CI-as-a-service in the software world, failed automated checks often send an email of reporting the maintainer of a botched build; why not bring that functionality to assertr?!

+

All assertion verbs in assertr support a custom error function. By default, the error function is R’s built-in stop function, which displays a message and halts execution. You can specify your own to hijack this behavior and redirect flow-of-control wherever you want.

+

All custom error functions take one argument: the actual error message that assertr assembles based on which assertions fail. In this case, we will build a function that takes that message and emails it to someone before halting execution. We will use the mailR package to send the mail.

+
library(mailR)
+
+email_me <- function(err_str){
+  send.mail(from="assertr@gmail.com", to="YOU@gmail.com",
+            subject="error from assertr", body=err_str,
+            smtp = list(host.name="aspmx.l.google.com", port=25),
+            authenticate = FALSE, send=TRUE)
+  stop(err_str, call.=FALSE)
+}
+
+questionable_mtcars %>%
+  verify(nrow(.) > 10, error_fun=email_me) %>%
+  verify(mpg > 0, error_fun=email_me) %>%
+  insist(within_n_sds(4), mpg, error_fun=email_me) %>%
+  assert(in_set(0,1), am, vs, error_fun=email_me)
+  # ...
+

(this particular send.mail formulation will only for gmail receptients; see the mailR documentation for more information)

+

Now you’ll get notified of any failed assertions via email. Groovy!

+

advanced: creating your own predicate generators for insist

assertr is build with robustness, correctness, and extensibility in mind. Just like assertr makes it easy to create your own custom predicates, so too does this package make it easy to create your own custom predicate generators.

diff --git a/man/assert.Rd b/man/assert.Rd index d7c7d63..1431a74 100644 --- a/man/assert.Rd +++ b/man/assert.Rd @@ -5,9 +5,10 @@ \alias{assert_} \title{Raises error if predicate is FALSE in any columns selected} \usage{ -assert(data, predicate, ...) +assert(data, predicate, ..., error_fun = assertr_stop) -assert_(data, predicate, ..., .dots, .nameofpred = "") +assert_(data, predicate, ..., .dots, error_fun = assertr_stop, + .nameofpred = "") } \arguments{ \item{data}{A data frame} @@ -18,6 +19,9 @@ assert_(data, predicate, ..., .dots, .nameofpred = "") Uses dplyr's \code{select} to select columns from data.} +\item{error_fun}{Function to call if assertion fails. Takes one error +string. Uses \code{stop} by default} + \item{.dots}{Use assert_() to select columns using standard evaluation.} \item{.nameofpred}{Text representation of predicate for printing in case diff --git a/man/insist.Rd b/man/insist.Rd index 3822267..f03b33f 100644 --- a/man/insist.Rd +++ b/man/insist.Rd @@ -5,9 +5,10 @@ \alias{insist_} \title{Raises error if dynamically created predicate is FALSE in any columns selected} \usage{ -insist(data, predicate_generator, ...) +insist(data, predicate_generator, ..., error_fun = assertr_stop) -insist_(data, predicate_generator, ..., .dots, .nameofpred = "") +insist_(data, predicate_generator, ..., .dots, error_fun = assertr_stop, + .nameofpred = "") } \arguments{ \item{data}{A data frame} @@ -21,6 +22,9 @@ every element in the column vectors selected} Uses dplyr's \code{select} to select columns from data.} +\item{error_fun}{Function to call if assertion fails. Takes one error +string. Uses \code{stop} by default} + \item{.dots}{Use insist_() to select columns using standard evaluation.} \item{.nameofpred}{Text representation of predicate for printing in case diff --git a/man/verify.Rd b/man/verify.Rd index 4e30c51..9ce79d0 100644 --- a/man/verify.Rd +++ b/man/verify.Rd @@ -4,12 +4,15 @@ \alias{verify} \title{Raises error if expression is FALSE anywhere} \usage{ -verify(data, expr) +verify(data, expr, error_fun = stop) } \arguments{ \item{data}{A data frame, list, or environment} \item{expr}{A logical expression} + +\item{error_fun}{Function to call if assertion fails. Takes one error + string. Uses \code{stop} by default} } \value{ data if verification passes. error if not. diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 4b93b62..a17aa74 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -65,7 +65,7 @@ test_that("verify breaks appropriately", { "coercing argument of type 'double' to logical") expect_error(suppressWarnings(verify(mtcars, "1")), "missing value where TRUE/FALSE needed") - expect_error(verify(mtcars, 2 > 1, "tree"), "unused argument \\(\"tree\")") + expect_error(verify(mtcars, 0 > 1, "tree"), "could not find function \"error_fun\"") expect_error(verify(mtcars, d > 1), "object 'd' not found") }) ###################################### @@ -146,6 +146,10 @@ test_that("assert raises *custom error* if verification fails (using se)", { test_that("assert breaks appropriately", { expect_error(assert(in_set(0,1), mtcars$vs), "no applicable method for 'select.?' applied to an object of class \"function\"") + expect_error(assert(mtcars, in_set(0,1), vs, tree), + "object 'tree' not found") + expect_error(assert(mtcars, in_set(0,1), vs, "tree"), + "All select\\(\\) inputs must resolve to integer column positions") expect_error(assert("tree"), "no applicable method for 'select.?' applied to an object of class \"character\"") }) @@ -153,6 +157,10 @@ test_that("assert breaks appropriately", { test_that("assert breaks appropriately (using se)", { expect_error(assert_(in_set(0,1), mtcars$vs), "no applicable method for 'select.?' applied to an object of class \"function\"") + expect_error(assert_(mtcars, in_set(0,1), vs), + "object 'vs' not found") + expect_error(assert_(mtcars, in_set(0,1), "vs", "tree"), + "object 'tree' not found") expect_error(assert_("tree"), "no applicable method for 'select.?' applied to an object of class \"character\"") }) @@ -232,6 +240,10 @@ test_that("insist raises *custom error* if verification fails (using se)", { test_that("insist breaks appropriately", { expect_error(insist(within_n_sds(5), mtcars$vs), "no applicable method for 'select.?' applied to an object of class \"function\"") + expect_error(insist(mtcars, mtwithin_n_sds(5), "vs"), + "All select\\(\\) inputs must resolve to integer column positions") + expect_error(insist(mtcars, within_n_sds(5), tree), + "object 'tree' not found") expect_error(insist("tree"), "no applicable method for 'select.?' applied to an object of class \"character\"") expect_error(insist(iris, within_n_sds(5), Petal.Width:Species), @@ -241,6 +253,8 @@ test_that("insist breaks appropriately", { test_that("insist breaks appropriately (using se)", { expect_error(insist_(within_n_sds(5), "mtcars$vs"), "no applicable method for 'select.?' applied to an object of class \"function\"") + expect_error(insist_(mtcars, within_n_sds(5), tree), + "object 'tree' not found") expect_error(insist_("tree"), "no applicable method for 'select.?' applied to an object of class \"character\"") expect_error(insist_(iris, within_n_sds(5), "Petal.Width:Species"), diff --git a/vignettes/assertr.Rmd b/vignettes/assertr.Rmd index 3190242..6db3824 100644 --- a/vignettes/assertr.Rmd +++ b/vignettes/assertr.Rmd @@ -342,6 +342,55 @@ Awesome! Now we can add an arbitrary number of assertions, as the need arises, without touching the real logic. +### advanced: send email reports using custom error functions + +One particularly cool application of `assertr` is to use it as a data integrity +checker for frequently updated data sources. A script can download new data as +it becomes available, and then run `assertr` checks on it. This makes assertr +into a sort of "continuous integration" tool (but for data, +not code.) + +In an unsupervised "continuous integration" environment, you need a way to +discover that the assertions failed. In CI-as-a-service in the software world, +failed automated checks often send an email of reporting the maintainer of a +botched build; why not bring that functionality to `assertr`?! + +All assertion verbs in `assertr` support a custom error function. By default, +the error function is R's built-in `stop` function, which displays a message +and halts execution. You can specify your own to hijack this behavior and +redirect flow-of-control wherever you want. + +All custom error functions take one argument: the actual error message that +`assertr` assembles based on which assertions fail. In this case, we will +build a function that takes that message and emails it to someone before +halting execution. We will use the `mailR` package to send the mail. + +```{r eval=FALSE, purl = FALSE} + +library(mailR) + +email_me <- function(err_str){ + send.mail(from="assertr@gmail.com", to="YOU@gmail.com", + subject="error from assertr", body=err_str, + smtp = list(host.name="aspmx.l.google.com", port=25), + authenticate = FALSE, send=TRUE) + stop(err_str, call.=FALSE) +} + +questionable_mtcars %>% + verify(nrow(.) > 10, error_fun=email_me) %>% + verify(mpg > 0, error_fun=email_me) %>% + insist(within_n_sds(4), mpg, error_fun=email_me) %>% + assert(in_set(0,1), am, vs, error_fun=email_me) + # ... +``` + +(this particular `send.mail` formulation will only for gmail receptients; +see the `mailR` documentation for more information) + +Now you'll get notified of any failed assertions via email. Groovy! + + ### advanced: creating your own predicate generators for `insist` `assertr` is build with robustness, correctness, and extensibility in mind.