-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Change
qenv
as environment
"type" -- adds `names(qenv/qenv.error)…
…`, `get()` and `$` S3 methods (#218) # Pull Request - Part of insightsengineering/teal.data#333 - Fixes #221 - closes #164 - Companion of insightsengineering/teal.data#347 #### Changes description - `qenv` S4 class inherits from `environment` data class - Removes `@env` slot in favor of `qenv` - Replace all instances of `@env` with `@.xData` (slot created by parent class) - All functions/methods that work for `environment` class are supported natively in `qenv` --------- Signed-off-by: André Veríssimo <[email protected]> Co-authored-by: 27856297+dependabot-preview[bot]@users.noreply.github.com <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Dawid Kałędkowski <[email protected]> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
622cb09
commit e14dc64
Showing
33 changed files
with
502 additions
and
202 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
#' If two `qenv` can be joined | ||
#' | ||
#' Checks if two `qenv` objects can be combined. | ||
#' For more information, please see [`join`] | ||
#' @param x (`qenv`) | ||
#' @param y (`qenv`) | ||
#' @return `TRUE` if able to join or `character` used to print error message. | ||
#' @keywords internal | ||
.check_joinable <- function(x, y) { | ||
checkmate::assert_class(x, "qenv") | ||
checkmate::assert_class(y, "qenv") | ||
|
||
common_names <- intersect(rlang::env_names(x@.xData), rlang::env_names(y@.xData)) | ||
is_overwritten <- vapply(common_names, function(el) { | ||
!identical(get(el, x@.xData), get(el, y@.xData)) | ||
}, logical(1)) | ||
if (any(is_overwritten)) { | ||
return( | ||
paste( | ||
"Not possible to join qenv objects if anything in their environment has been modified.\n", | ||
"Following object(s) have been modified:\n - ", | ||
paste(common_names[is_overwritten], collapse = "\n - ") | ||
) | ||
) | ||
} | ||
|
||
shared_ids <- intersect(x@id, y@id) | ||
if (length(shared_ids) == 0) { | ||
return(TRUE) | ||
} | ||
|
||
shared_in_x <- match(shared_ids, x@id) | ||
shared_in_y <- match(shared_ids, y@id) | ||
|
||
# indices of shared ids should be 1:n in both slots | ||
if (identical(shared_in_x, shared_in_y) && identical(shared_in_x, seq_along(shared_ids))) { | ||
TRUE | ||
} else if (!identical(shared_in_x, shared_in_y)) { | ||
paste( | ||
"The common shared code of the qenvs does not occur in the same position in both qenv objects", | ||
"so they cannot be joined together as it's impossible to determine the evaluation's order.", | ||
collapse = "" | ||
) | ||
} else { | ||
paste( | ||
"There is code in the qenv objects before their common shared code", | ||
"which means these objects cannot be joined.", | ||
collapse = "" | ||
) | ||
} | ||
} | ||
|
||
#' @rdname join | ||
#' @param ... (`qenv` or `qenv.error`). | ||
#' @examples | ||
#' q <- qenv() | ||
#' q1 <- within(q, { | ||
#' iris1 <- iris | ||
#' mtcars1 <- mtcars | ||
#' }) | ||
#' q1 <- within(q1, iris2 <- iris) | ||
#' q2 <- within(q1, mtcars2 <- mtcars) | ||
#' qq <- c(q1, q2) | ||
#' cat(get_code(qq)) | ||
#' | ||
#' @export | ||
c.qenv <- function(...) { | ||
dots <- rlang::list2(...) | ||
if (!checkmate::test_list(dots[-1], types = c("qenv", "qenv.error"))) { | ||
return(NextMethod(c, dots[[1]])) | ||
} | ||
|
||
first_non_qenv_ix <- which.min(vapply(dots, inherits, what = "qenv", logical(1))) | ||
if (first_non_qenv_ix > 1) { | ||
return(dots[[first_non_qenv_ix]]) | ||
} | ||
|
||
Reduce( | ||
x = dots[-1], | ||
init = dots[[1]], | ||
f = function(x, y) { | ||
join_validation <- .check_joinable(x, y) | ||
|
||
# join expressions | ||
if (!isTRUE(join_validation)) { | ||
stop(join_validation) | ||
} | ||
|
||
id_unique <- !y@id %in% x@id | ||
x@id <- c(x@id, y@id[id_unique]) | ||
x@code <- c(x@code, y@code[id_unique]) | ||
x@warnings <- c(x@warnings, y@warnings[id_unique]) | ||
x@messages <- c(x@messages, y@messages[id_unique]) | ||
|
||
# insert (and overwrite) objects from y to x | ||
x@.xData <- rlang::env_clone(x@.xData, parent = parent.env(.GlobalEnv)) | ||
rlang::env_coalesce(env = x@.xData, from = y@.xData) | ||
x | ||
} | ||
) | ||
} | ||
|
||
#' @rdname join | ||
#' @export | ||
c.qenv.error <- function(...) { | ||
rlang::list2(...)[[1]] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ | |
#' @name qenv-class | ||
#' @rdname qenv-class | ||
#' @slot code (`character`) representing code necessary to reproduce the environment | ||
#' @slot env (`environment`) environment which content was generated by the evaluation | ||
#' @slot .xData (`environment`) environment with content was generated by the evaluation | ||
#' of the `code` slot. | ||
#' @slot id (`integer`) random identifier of the code element to make sure uniqueness | ||
#' when joining. | ||
|
@@ -14,11 +14,60 @@ | |
#' @exportClass qenv | ||
setClass( | ||
"qenv", | ||
slots = c(env = "environment", code = "character", id = "integer", warnings = "character", messages = "character"), | ||
prototype = list( | ||
env = new.env(parent = parent.env(.GlobalEnv)), code = character(0), id = integer(0), | ||
warnings = character(0), messages = character(0) | ||
) | ||
slots = c( | ||
code = "character", | ||
id = "integer", | ||
warnings = "character", | ||
messages = "character" | ||
), | ||
contains = "environment" | ||
) | ||
|
||
#' It initializes the `qenv` class | ||
#' @noRd | ||
setMethod( | ||
"initialize", | ||
"qenv", | ||
function(.Object, # nolint: object_name. | ||
.xData, # nolint: object_name. | ||
code = character(0L), | ||
warnings = rep("", length(code)), | ||
messages = rep("", length(code)), | ||
id = integer(0L), | ||
...) { | ||
# # Pre-process parameters to ensure they are ready to be used by parent constructors | ||
stopifnot("`code` must be a character or language object." = any(is.language(code), is.character(code))) | ||
|
||
if (is.language(code)) { | ||
code <- paste(lang2calls(code), collapse = "\n") | ||
} | ||
if (length(code)) { | ||
code <- paste(code, collapse = "\n") | ||
} | ||
|
||
if (length(id) == 0L) { | ||
id <- sample.int(.Machine$integer.max, size = length(code)) | ||
} | ||
|
||
new_xdata <- if (rlang::is_missing(.xData)) { | ||
new.env(parent = parent.env(.GlobalEnv)) | ||
} else { | ||
checkmate::assert_environment(.xData) | ||
rlang::env_clone(.xData, parent = parent.env(.GlobalEnv)) | ||
} | ||
lockEnvironment(new_xdata, bindings = TRUE) | ||
|
||
# .xData needs to be unnamed as the `.environment` constructor allows at | ||
# most 1 unnamed formal argument of class `environment`. | ||
# See methods::findMethods("initialize")$.environment | ||
.Object <- methods::callNextMethod( # nolint: object_name. | ||
# Mandatory use of `xData` to build a correct [email protected] | ||
.Object, new_xdata, | ||
code = code, messages = messages, warnings = warnings, id = id, ... | ||
) | ||
|
||
.Object | ||
} | ||
) | ||
|
||
#' It takes a `qenv` class and returns `TRUE` if the input is valid | ||
|
@@ -33,6 +82,8 @@ setValidity("qenv", function(object) { | |
"@code and @messages slots must have the same length" | ||
} else if (any(duplicated(object@id))) { | ||
"@id contains duplicated values." | ||
} else if (!environmentIsLocked(object@.xData)) { | ||
"@.xData must be locked." | ||
} else { | ||
TRUE | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,10 @@ | ||
# needed to handle try-error | ||
setOldClass("qenv.error") | ||
|
||
#' @export | ||
as.list.qenv.error <- function(x, ...) { | ||
stop(errorCondition( | ||
list(message = conditionMessage(x)), | ||
class = c("validation", "try-error", "simpleError") | ||
)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
#' Access environment included in `qenv` | ||
#' | ||
#' The access of environment included in `qenv@env` allows to e.g. list object names included in `qenv@env` slot. | ||
#' The access of environment included in the `qenv` that contains all data objects. | ||
#' | ||
#' @param object (`qenv`) | ||
#' @param object (`qenv`). | ||
#' | ||
#' @return An `environment` stored in `qenv@env` slot. | ||
#' @return An `environment` stored in `qenv` slot with all data objects. | ||
#' | ||
#' @examples | ||
#' q <- qenv() | ||
|
@@ -13,7 +13,6 @@ | |
#' b <- data.frame(x = 1:10) | ||
#' }) | ||
#' get_env(q1) | ||
#' ls(get_env(q1)) | ||
#' | ||
#' @aliases get_env,qenv-method | ||
#' @aliases get_env,qenv.error-method | ||
|
@@ -23,10 +22,6 @@ setGeneric("get_env", function(object) { | |
standardGeneric("get_env") | ||
}) | ||
|
||
setMethod("get_env", "qenv", function(object) { | ||
object@env | ||
}) | ||
setMethod("get_env", "qenv", function(object) [email protected]) | ||
|
||
setMethod("get_env", "qenv.error", function(object) { | ||
object | ||
}) | ||
setMethod("get_env", "qenv.error", function(object) object) |
Oops, something went wrong.