Skip to content

211 [.qenv S3 method + replacement of @id, @warnings, and @messages fields #216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 112 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
99ebe96
proposition of the implementation
m7pr Oct 23, 2024
4b2b2fb
let qenv, eval_code and get_code work on @code that has length as the…
m7pr Oct 24, 2024
2eab6d3
adjust tests
m7pr Oct 24, 2024
8349eef
[skip style] [skip vbump] Restyle files
github-actions[bot] Oct 24, 2024
984c439
Update R/qenv-get_code.R
m7pr Oct 24, 2024
a277b2d
fix join qenv tests
m7pr Oct 25, 2024
5f77d9d
adjust warning messages
m7pr Oct 25, 2024
8423110
fix R CMD checks just for now
m7pr Oct 25, 2024
cc17c8b
fix lintr
m7pr Oct 25, 2024
2391170
Update tests/testthat/test-qenv_get_warnings.R
m7pr Oct 25, 2024
3c8a070
Update tests/testthat/test-qenv_get_warnings.R
m7pr Oct 25, 2024
f8295f8
fix get_warnings
m7pr Oct 28, 2024
069538f
move back to regular subset in examples without prefix
m7pr Oct 28, 2024
9fad8dc
use `[.` instead of subset for qenv
m7pr Oct 28, 2024
0b42371
document x
m7pr Oct 28, 2024
2e53db4
[skip style] [skip vbump] Restyle files
github-actions[bot] Oct 28, 2024
7844ee8
add a warning
m7pr Oct 29, 2024
8eeeb40
Merge branch 'main' into 211_subset@main
m7pr Oct 29, 2024
38bd16f
use fix shifted comments in extract comments
m7pr Oct 29, 2024
8b0faa3
[skip style] [skip vbump] Restyle files
github-actions[bot] Oct 29, 2024
56b5c8e
prototype of the function that divides the character code by calls, a…
m7pr Oct 29, 2024
dc12c33
Merge branch '211_subset@main' of https://github.com/insightsengineer…
m7pr Oct 29, 2024
15a6cd7
[skip style] [skip vbump] Restyle files
github-actions[bot] Oct 29, 2024
8ddff1b
clean examples from prototype script
m7pr Oct 29, 2024
811092b
merge
m7pr Oct 29, 2024
f0b0e9e
[skip roxygen] [skip vbump] Roxygen Man Pages Auto Update
dependabot-preview[bot] Oct 29, 2024
a1a73fa
[skip style] [skip vbump] Restyle files
github-actions[bot] Oct 29, 2024
35de66b
incorporate split_code into codebase
m7pr Oct 29, 2024
760a5ed
Merge branch '211_subset@main' of https://github.com/insightsengineer…
m7pr Oct 29, 2024
0ced369
[skip style] [skip vbump] Restyle files
github-actions[bot] Oct 29, 2024
089514c
merge
m7pr Oct 30, 2024
dfd7657
merge
m7pr Oct 30, 2024
80d877f
cleanup
m7pr Oct 30, 2024
5e1bd45
[skip roxygen] [skip vbump] Roxygen Man Pages Auto Update
dependabot-preview[bot] Oct 30, 2024
08bd2da
[skip style] [skip vbump] Restyle files
github-actions[bot] Oct 30, 2024
166a8c1
Collate
m7pr Oct 30, 2024
a32939c
Merge branch '211_subset@main' of https://github.com/insightsengineer…
m7pr Oct 30, 2024
88a8c04
fix tests
m7pr Oct 30, 2024
ae51d8a
[skip style] [skip vbump] Restyle files
github-actions[bot] Oct 30, 2024
abcab21
add warnings about skipped objects
m7pr Oct 30, 2024
a9e9c0e
[skip style] [skip vbump] Restyle files
github-actions[bot] Oct 30, 2024
ed60b32
WIP #70
gogonzo Oct 31, 2024
84875c7
move @id, @warnings and @message to attributes of code and fix rest o…
m7pr Oct 31, 2024
69c08d3
code split
gogonzo Oct 31, 2024
29adf3a
use get_code_dependency in qenv
m7pr Oct 31, 2024
43f45cf
Merge branch '211_subset@main' of https://github.com/insightsengineer…
m7pr Oct 31, 2024
6d674ac
remove print
m7pr Oct 31, 2024
e1254e7
for cases with just one call, do not DROP matrix calss in call_breaks
m7pr Oct 31, 2024
13b7f32
handle code with single call in split_code
m7pr Oct 31, 2024
7d3138b
fix
gogonzo Oct 31, 2024
39363c6
add drop FALSE to split_code
m7pr Oct 31, 2024
043414a
fix warning messages for skipped objects
m7pr Oct 31, 2024
9304459
fix some tests
m7pr Oct 31, 2024
6338ce1
documentation changes for @names param - move to qenv-consrtuctor
m7pr Nov 4, 2024
930503f
fix typos in tests
m7pr Nov 4, 2024
0afef83
extract_calls do not drop @linksto tags
m7pr Nov 4, 2024
86e5d95
first clone object, then return
m7pr Nov 4, 2024
d91395a
revert the state of the test
m7pr Nov 4, 2024
b175c58
fix tests for comments
m7pr Nov 4, 2024
894f819
fix warnings test
m7pr Nov 4, 2024
2971a17
update documentation for qenv slots
m7pr Nov 4, 2024
e251952
Merge branch 'main' into 211_subset@main
m7pr Nov 4, 2024
f18d55a
bring @param names documentation
m7pr Nov 4, 2024
187350c
remove @ usage in vignette
m7pr Nov 5, 2024
a84f2bc
Update tests/testthat/test-qenv_extract.R
m7pr Nov 6, 2024
084382a
adjust description in test
m7pr Nov 6, 2024
6a5d149
fix descriptions in few more tests
m7pr Nov 6, 2024
26efa3f
extend possible_names to hidden names in extract/subset function
m7pr Nov 6, 2024
6f03292
assign `side_effects` and `occurrence` as attributes of `@code` (#223)
m7pr Nov 6, 2024
4c7fa7a
[skip style] [skip vbump] Restyle files
github-actions[bot] Nov 6, 2024
7bd1c2d
simplify get_warnings
m7pr Nov 7, 2024
7fa2369
revert get_code so it returns vector of length 1 (concatened with \n)
m7pr Nov 7, 2024
344f528
[skip style] [skip vbump] Restyle files
github-actions[bot] Nov 7, 2024
20b2c8f
fix warning tests
m7pr Nov 7, 2024
4e3aeee
Merge branch '211_subset@main' of https://github.com/insightsengineer…
m7pr Nov 7, 2024
b4e119c
no need to deparse
m7pr Nov 7, 2024
e28b937
extend comments testing and remove |>
m7pr Nov 7, 2024
c379419
[skip roxygen] [skip vbump] Roxygen Man Pages Auto Update
dependabot-preview[bot] Nov 7, 2024
115ea51
[skip style] [skip vbump] Restyle files
github-actions[bot] Nov 7, 2024
080e27f
substitute pasten with paste
m7pr Nov 7, 2024
14e78ef
Merge branch '211_subset@main' of https://github.com/insightsengineer…
m7pr Nov 7, 2024
c3b25e6
skip the test that breaks on CI but not locally
m7pr Nov 7, 2024
7cf13ac
fix the test where we dont want to eval the code
m7pr Nov 7, 2024
109877d
[skip style] [skip vbump] Restyle files
github-actions[bot] Nov 7, 2024
3e4582e
Update R/qenv-get_warnings.R
m7pr Nov 7, 2024
da9a69b
fix a test
m7pr Nov 7, 2024
23d9057
Merge branch '211_subset@main' of https://github.com/insightsengineer…
m7pr Nov 7, 2024
ef00c58
adjust test so it does not load libraries
m7pr Nov 7, 2024
6fcb8b9
Update tests/testthat/test-qenv_eval_code.R
m7pr Nov 7, 2024
3ba65f2
treat empty calls or comments as separate calls
m7pr Nov 7, 2024
ac6d56f
Merge branch '211_subset@main' of https://github.com/insightsengineer…
m7pr Nov 7, 2024
888faa8
[skip style] [skip vbump] Restyle files
github-actions[bot] Nov 7, 2024
fae2452
replace pasten with paste
m7pr Nov 8, 2024
49d342b
preserve original src when available
gogonzo Nov 8, 2024
b967cdd
bquote not needed
gogonzo Nov 8, 2024
cd70b45
remove pipes
m7pr Nov 8, 2024
4da6f65
fix lintr
m7pr Nov 8, 2024
ffa54c8
allow to pass ... trhough [. to get_code_dependency - needed in teal.…
m7pr Nov 8, 2024
7f80156
fixes #217
gogonzo Nov 8, 2024
7cbc93d
optimise get_messages/get_warnings
gogonzo Nov 8, 2024
b807058
Merge branch 'main' into 211_subset@main
gogonzo Nov 8, 2024
defa490
WIP - postmerge
gogonzo Nov 8, 2024
0f20544
fix obvious
gogonzo Nov 8, 2024
db68da0
fix: constructor creates a locked environment
averissimo Nov 8, 2024
bc95670
yabadabadoo
gogonzo Nov 8, 2024
6c5d5ab
use all.names = TRUE in as.list.environment
m7pr Nov 8, 2024
140ed13
Merge branch '211_subset@main' of https://github.com/insightsengineer…
m7pr Nov 8, 2024
173b3dd
fix warn for env and code in extract
gogonzo Nov 8, 2024
cc15b95
forbid names to be NA
gogonzo Nov 8, 2024
0cc2351
fix tests
gogonzo Nov 8, 2024
2a0f49b
names warning on subset
gogonzo Nov 8, 2024
ba1a552
fix for verified/unverified teal_data
gogonzo Nov 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ Collate:
'qenv-concat.R'
'qenv-constructor.R'
'qenv-eval_code.R'
'qenv-extract.R'
'qenv-get_code.R'
'qenv-get_env.R'
'qenv-get_messages.r'
'qenv-get_var.R'
'qenv-get_warnings.R'
'qenv-join.R'
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Generated by roxygen2: do not edit by hand

S3method("$",qenv.error)
S3method("[",qenv)
S3method("[[",qenv.error)
S3method(as.list,qenv.error)
S3method(c,qenv)
Expand All @@ -15,6 +16,7 @@ export(dev_suppress)
export(eval_code)
export(get_code)
export(get_env)
export(get_messages)
export(get_var)
export(get_warnings)
export(join)
Expand Down
5 changes: 4 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

### Enhancements

* Introduced `[.qenv` function to subset `qenv` object (code and environment) to specified object names. #211
* `get_code()` was extended with `names` parameter and allows the code extraction to be limited to objects stored in
`qenv` but limited to `names`.
`qenv` but limited to `names`. #210
* Introduced `get_messages()` to get messages produced during code evaluation. #217
* `get_code()` returns original code formatting (white spaces and comments) passed to `eval_code()`. #212
* `qenv` inherits from the `environment` class, allowing to use `ls()`, `names()`, `as.environment()` and other functions on `qenv` objects.
* `join()` method is deprecated, please use `c()` instead
* `get_var()` method is deprecated, please use `get`, `[[` or `$` instead.
Expand Down
15 changes: 7 additions & 8 deletions R/qenv-c.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@
)
}

shared_ids <- intersect(x@id, y@id)
x_id <- get_code_attr(x, "id")
y_id <- get_code_attr(y, "id")

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)
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))) {
Expand Down Expand Up @@ -86,11 +89,7 @@ c.qenv <- function(...) {
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])
x@code <- union(x@code, y@code)

# insert (and overwrite) objects from y to x
[email protected] <- rlang::env_clone([email protected], parent = parent.env(.GlobalEnv))
Expand Down
69 changes: 23 additions & 46 deletions R/qenv-class.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,25 @@
#' Reproducible class with environment and code.
#' @name qenv-class
#' @rdname qenv-class
#' @slot code (`character`) representing code necessary to reproduce the environment
#' @slot .xData (`environment`) environment with content was generated by the evaluation
#' @slot code (`list` of `character`) representing code necessary to reproduce the environment.
#' Read more in Code section.
#' of the `code` slot.
#' @slot id (`integer`) random identifier of the code element to make sure uniqueness
#' when joining.
#' @slot warnings (`character`) the warnings output when evaluating the code
#' @slot messages (`character`) the messages output when evaluating the code
#'
#' @section Code:
#'
#' Each code element is a character representing one call. Each element has possible attributes:
#' - `warnings` (`character`) the warnings output when evaluating the code element
#' - `messages` (`character`) the messages output when evaluating the code element
#' - `id (`integer`) random identifier of the code element to make sure uniqueness when joining
#' - `dependency` (`character`) names of objects that appear in this call and gets affected by this call,
#' separated by `<-` (objects on LHS of `<-` are affected by this line, and objects on RHS are affecting this line)
#'
#' @keywords internal
#' @exportClass qenv
setClass(
"qenv",
slots = c(
code = "character",
id = "integer",
warnings = "character",
messages = "character"
),
slots = c(code = "list"),
contains = "environment"
)

Expand All @@ -28,27 +30,7 @@ setClass(
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))
}

function(.Object, .xData, code = list(), ...) { # nolint: object_name.
new_xdata <- if (rlang::is_missing(.xData)) {
new.env(parent = parent.env(.GlobalEnv))
} else {
Expand All @@ -60,28 +42,23 @@ setMethod(
# .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 .Object@.xData
.Object, new_xdata,
code = code, messages = messages, warnings = warnings, id = id, ...
methods::callNextMethod(
.Object,
new_xdata, # Mandatory use of unnamed environment arg
code = code, ...
)

.Object
}
)

#' It takes a `qenv` class and returns `TRUE` if the input is valid
#' @name qenv-class
#' @keywords internal
setValidity("qenv", function(object) {
if (length(object@code) != length(object@id)) {
"@code and @id slots must have the same length."
} else if (length(object@code) != length(object@warnings)) {
"@code and @warnings slots must have the same length"
} else if (length(object@code) != length(object@messages)) {
"@code and @messages slots must have the same length"
} else if (any(duplicated(object@id))) {
"@id contains duplicated values."
ids <- lapply(object@code, "attr", "id")
if (any(sapply(ids, is.null))) {
"All @code slots must have an 'id' attribute"
} else if (any(duplicated(unlist(ids)))) {
"@code contains duplicated 'id' attributes."
} else if (!environmentIsLocked([email protected])) {
"@.xData must be locked."
} else {
Expand Down
3 changes: 0 additions & 3 deletions R/qenv-concat.R
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@
setGeneric("concat", function(x, y) standardGeneric("concat"))

setMethod("concat", signature = c("qenv", "qenv"), function(x, y) {
y@id <- c(x@id, y@id)
y@code <- c(x@code, y@code)
y@warnings <- c(x@warnings, y@warnings)
y@messages <- c(x@messages, y@messages)

# insert (and overwrite) objects from y to x
[email protected] <- rlang::env_clone([email protected], parent = parent.env(.GlobalEnv))
Expand Down
4 changes: 4 additions & 0 deletions R/qenv-constructor.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
#'
#' Create a `qenv` object and evaluate code in it to track code history.
#'
#' @param names (`character`) for `x[names]`, names of objects included in `qenv` to subset. Names not present in `qenv`
#' are skipped. For `get_code` `r lifecycle::badge("experimental")` vector of object names to return the code for.
#' For more details see the "Extracting dataset-specific code" section.
#'
#' @details
#'
#' `qenv()` instantiates a `qenv` with an empty environment.
Expand Down
49 changes: 31 additions & 18 deletions R/qenv-eval_code.R
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,27 @@
setGeneric("eval_code", function(object, code) standardGeneric("eval_code"))

setMethod("eval_code", signature = c("qenv", "character"), function(object, code) {
id <- sample.int(.Machine$integer.max, size = 1)

object@id <- c(object@id, id)
parsed_code <- parse(text = code, keep.source = TRUE)
[email protected] <- rlang::env_clone([email protected], parent = parent.env(.GlobalEnv))
code <- paste(code, collapse = "\n")
object@code <- c(object@code, code)
if (length(parsed_code) == 0) {
# empty code, or just comments
attr(code, "id") <- sample.int(.Machine$integer.max, size = 1)
attr(code, "dependency") <- extract_dependency(parsed_code) # in case comment contains @linksto tag
object@code <- c(object@code, list(code))
return(object)
}
code_split <- split_code(paste(code, collapse = "\n"))

current_warnings <- ""
current_messages <- ""
for (i in seq_along(code_split)) {
current_code <- code_split[[i]]
current_call <- parse(text = current_code, keep.source = TRUE)

parsed_code <- parse(text = code, keep.source = TRUE)
for (single_call in parsed_code) {
# Using withCallingHandlers to capture warnings and messages.
# Using tryCatch to capture the error and abort further evaluation.
x <- withCallingHandlers(
tryCatch(
{
eval(single_call, envir = [email protected])
eval(current_call, envir = [email protected])
if (!identical(parent.env([email protected]), parent.env(.GlobalEnv))) {
# needed to make sure that @.xData is always a sibling of .GlobalEnv
# could be changed when any new package is added to search path (through library or require call)
Expand All @@ -58,30 +61,31 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
message = sprintf(
"%s \n when evaluating qenv code:\n%s",
.ansi_strip(conditionMessage(e)),
deparse1(single_call)
current_code
),
class = c("qenv.error", "try-error", "simpleError"),
trace = object@code
trace = unlist(c(object@code, list(current_code)))
)
}
),
warning = function(w) {
current_warnings <<- paste0(current_warnings, .ansi_strip(sprintf("> %s\n", conditionMessage(w))))
attr(current_code, "warning") <<- .ansi_strip(sprintf("> %s\n", conditionMessage(w)))
invokeRestart("muffleWarning")
},
message = function(m) {
current_messages <<- paste0(current_messages, .ansi_strip(sprintf("> %s", conditionMessage(m))))
attr(current_code, "message") <<- .ansi_strip(sprintf("> %s", conditionMessage(m)))
invokeRestart("muffleMessage")
}
)

if (!is.null(x)) {
return(x)
}
}

object@warnings <- c(object@warnings, current_warnings)
object@messages <- c(object@messages, current_messages)
attr(current_code, "id") <- sample.int(.Machine$integer.max, size = 1)
attr(current_code, "dependency") <- extract_dependency(current_call)
object@code <- c(object@code, list(current_code))
}

lockEnvironment([email protected], bindings = TRUE)
object
Expand All @@ -92,7 +96,12 @@ setMethod("eval_code", signature = c("qenv", "language"), function(object, code)
})

setMethod("eval_code", signature = c("qenv", "expression"), function(object, code) {
eval_code(object, code = paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), collapse = "\n"))
srcref <- attr(code, "wholeSrcref")
if (length(srcref)) {
eval_code(object, code = paste(attr(code, "wholeSrcref"), collapse = "\n"))
} else {
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
}
})

setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code) {
Expand All @@ -108,3 +117,7 @@ setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code
chr
}
}

get_code_attr <- function(qenv, attr) {
unlist(lapply(qenv@code, function(x) attr(x, attr)))
}
52 changes: 52 additions & 0 deletions R/qenv-extract.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#'
#' @section Subsetting:
#' `x[names]` subsets objects in `qenv` environment and limit the code to the necessary needed to build limited objects.
#' `...` passes parameters to further methods.
#'
#' @param x (`qenv`)
#'
#' @examples
#'
#' # Subsetting
#' q <- qenv()
#' q <- eval_code(q, "a <- 1;b<-2")
#' q["a"]
#' q[c("a", "b")]
#'
#' @rdname qenv
#'
#' @export
`[.qenv` <- function(x, names, ...) {
checkmate::assert_character(names, any.missing = FALSE)
possible_names <- ls(get_env(x), all.names = TRUE)
names_corrected <- intersect(names, possible_names)
env <- if (length(names_corrected)) {
names_missing <- setdiff(names, possible_names)
if (length(names_missing)) {
warning(
sprintf(
"Some 'names' do not exist in the environment of the '%s'. Skipping those: %s.",
class(x)[1],
paste(names_missing, collapse = ", ")
)
)
}
list2env(as.list(x, all.names = TRUE)[names_corrected], parent = parent.env(.GlobalEnv))
} else {
warning(
sprintf(
"None of 'names' exist in the environment of the '%1$s'. Returning empty '%1$s'.",
class(x)[1]
),
call. = FALSE
)
new.env(parent = parent.env(.GlobalEnv))
}
lockEnvironment(env)
[email protected] <- env

normalized_names <- gsub("^`(.*)`$", "\\1", names)
x@code <- get_code_dependency(x@code, names = normalized_names, ...)

x
}
Loading
Loading