Skip to content
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

Change qenv as environment "type" -- adds names(qenv/qenv.error), get() and $ S3 methods #218

Merged
merged 58 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
bfa8390
feat: initial support for names(teal_data)
averissimo Oct 28, 2024
127bc7c
docs: update NEWS
averissimo Oct 28, 2024
cb2cc57
docs: minor change
averissimo Oct 28, 2024
72f0595
fix: warning in R CMD check
averissimo Oct 28, 2024
9a6343a
docs: typo
averissimo Oct 28, 2024
e1a69b3
fix: remove implementation of names()<- as error message is self
averissimo Oct 28, 2024
c335f52
fix: remove extra arguments for names, not supported
averissimo Oct 28, 2024
873a1b6
fix: remove extra word from wordlist
averissimo Oct 28, 2024
a203f81
feat: `qenv` inherits from environment class
averissimo Oct 29, 2024
fd93704
Merge branch 'main' into 333_deprecate_datanames@main
averissimo Oct 29, 2024
8b855f3
[skip roxygen] [skip vbump] Roxygen Man Pages Auto Update
dependabot-preview[bot] Oct 29, 2024
4efca24
fix: improves tests and adds $ getter to qenv.error with similar beha…
averissimo Oct 29, 2024
0a02726
feat: prevent assignment to qenv.error
averissimo Oct 29, 2024
281c994
doc: update news
averissimo Oct 29, 2024
b1ffbe7
fix: problem when printing qenv.error
averissimo Oct 29, 2024
e457a4c
docs: adds .xData slot documentation
averissimo Oct 29, 2024
2c76a8a
chore: cleanup of previous implementation of names.qenv
averissimo Oct 29, 2024
ab9e342
chore: lintr cleanup
averissimo Oct 29, 2024
c56a5ca
feat: expand on compatibility with an environment
averissimo Oct 30, 2024
9904570
fix: complete tests for qenv-class
averissimo Oct 30, 2024
5c0ef7d
doc: adds section to qenv constructor
averissimo Oct 30, 2024
558bd72
fix: test and adds extra protection on qenv validation
averissimo Oct 30, 2024
4ec3e9f
fix: move constructor logic to "initialize" method of qenv
averissimo Oct 30, 2024
e00fd92
fix: problem with integer (1L) shorthand in within
averissimo Oct 30, 2024
be480f1
test: problem with integer (1L) shorthand in within
averissimo Oct 30, 2024
b7d1885
fix: order and formal of callNextMethod
averissimo Oct 30, 2024
2a32022
fix: minor bugs
averissimo Oct 30, 2024
9049379
chore: fix lintr
averissimo Oct 30, 2024
bb5c5fb
Apply suggestions from code review
averissimo Oct 31, 2024
709265f
docs: update
averissimo Oct 31, 2024
1fe8b18
docs: small improvements
averissimo Oct 31, 2024
3ae0541
[skip style] [skip vbump] Restyle files
github-actions[bot] Oct 31, 2024
0a64498
Update tests/testthat/test-qenv_within.R
averissimo Oct 31, 2024
d4ee6d0
docs: implements @gogonzo suggestions and cleans up docs
averissimo Oct 31, 2024
af7054b
docs: move section around
averissimo Oct 31, 2024
c668d98
docs: superseded
averissimo Oct 31, 2024
faa843b
fix: use newlines in code parseing on multiline expression with within
averissimo Nov 1, 2024
2f553eb
Merge branch 'main' into 333_deprecate_datanames@main
averissimo Nov 4, 2024
015f11c
fix: problems with check
averissimo Nov 4, 2024
7cd8949
chore: rename instances of ls to names
averissimo Nov 5, 2024
50be4b0
chore: rename instances of join to c
averissimo Nov 5, 2024
9d2ec00
docs: improvement on join() documentation
averissimo Nov 5, 2024
c76e148
Apply suggestions from code review
averissimo Nov 5, 2024
620849b
pr: apply suggestions
averissimo Nov 5, 2024
1e25681
pr: apply suggestions (remove duplicate test)
averissimo Nov 5, 2024
7b7ae6a
Update R/qenv-join.R
averissimo Nov 5, 2024
7ea19e3
fix: error with suggestion
averissimo Nov 5, 2024
bf5ed47
fix: tests
averissimo Nov 5, 2024
903a43c
feat: qenv constructor improvement
averissimo Nov 6, 2024
f9fef18
Update tests/testthat/test-qenv-class.R
averissimo Nov 7, 2024
3bd3ff5
[skip style] [skip vbump] Restyle files
github-actions[bot] Nov 7, 2024
d8d1a8e
chore: trigger CI
averissimo Nov 7, 2024
236ce59
Update README.md
averissimo Nov 7, 2024
4564f34
Update R/qenv-get_env.R
averissimo Nov 7, 2024
9f76e48
[skip roxygen] [skip vbump] Roxygen Man Pages Auto Update
dependabot-preview[bot] Nov 7, 2024
49e49c9
fix: remove unnecessary listing
averissimo Nov 7, 2024
1f4b755
[skip roxygen] [skip vbump] Roxygen Man Pages Auto Update
dependabot-preview[bot] Nov 7, 2024
a5fcb9a
chore: trigger CI
averissimo Nov 7, 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 @@ -53,6 +53,7 @@ Language: en-US
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
Collate:
'qenv-c.R'
'qenv-class.R'
'qenv-errors.R'
'qenv-concat.R'
Expand All @@ -63,6 +64,7 @@ Collate:
'qenv-get_var.R'
'qenv-get_warnings.R'
'qenv-join.R'
'qenv-length.R'
'qenv-show.R'
'qenv-within.R'
'teal.code-package.R'
Expand Down
7 changes: 7 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Generated by roxygen2: do not edit by hand

S3method("$",qenv.error)
S3method("[[",qenv.error)
S3method(as.list,qenv.error)
S3method(c,qenv)
S3method(c,qenv.error)
S3method(length,qenv)
S3method(length,qenv.error)
S3method(names,qenv.error)
S3method(within,qenv)
S3method(within,qenv.error)
export(concat)
Expand Down
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

* `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` inherits from the `environment` class, allowing to use `ls()`, `names()`, `as.environment()` and other functions on `qenv` objects.
averissimo marked this conversation as resolved.
Show resolved Hide resolved
* `join()` method is deprecated, please use `c()` instead
* `get_var()` method is deprecated, please use `get`, `[[` or `$` instead.

# teal.code 0.5.0

Expand Down
107 changes: 107 additions & 0 deletions R/qenv-c.R
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([email protected]), rlang::env_names([email protected]))
is_overwritten <- vapply(common_names, function(el) {
!identical(get(el, [email protected]), get(el, [email protected]))
}, 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
[email protected] <- rlang::env_clone([email protected], parent = parent.env(.GlobalEnv))
rlang::env_coalesce(env = [email protected], from = [email protected])
x
}
)
}

#' @rdname join
#' @export
c.qenv.error <- function(...) {
rlang::list2(...)[[1]]
}
63 changes: 57 additions & 6 deletions R/qenv-class.R
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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, ...
)
averissimo marked this conversation as resolved.
Show resolved Hide resolved

.Object
}
)

#' It takes a `qenv` class and returns `TRUE` if the input is valid
Expand All @@ -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([email protected])) {
"@.xData must be locked."
} else {
TRUE
}
Expand Down
4 changes: 2 additions & 2 deletions R/qenv-concat.R
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ setMethod("concat", signature = c("qenv", "qenv"), function(x, y) {
y@messages <- c(x@messages, y@messages)

# insert (and overwrite) objects from y to x
y@env <- rlang::env_clone(y@env, parent = parent.env(.GlobalEnv))
rlang::env_coalesce(env = y@env, from = x@env)
y@.xData <- rlang::env_clone(y@.xData, parent = parent.env(.GlobalEnv))
rlang::env_coalesce(env = y@.xData, from = x@.xData)
y
})

Expand Down
6 changes: 2 additions & 4 deletions R/qenv-constructor.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#'
#' @name qenv
#'
#' @return Returns a `qenv` object.
#' @return `qenv` returns a `qenv` object.
#'
#' @seealso [`base::within()`], [`get_var()`], [`get_env()`], [`get_warnings()`], [`join()`], [`concat()`]
#' @examples
Expand All @@ -21,7 +21,5 @@
#'
gogonzo marked this conversation as resolved.
Show resolved Hide resolved
#' @export
qenv <- function() {
q_env <- new.env(parent = parent.env(.GlobalEnv))
lockEnvironment(q_env, bindings = TRUE)
methods::new("qenv", env = q_env)
methods::new("qenv")
}
8 changes: 8 additions & 0 deletions R/qenv-errors.R
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")
))
}
19 changes: 9 additions & 10 deletions R/qenv-eval_code.R
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#' Evaluate code in `qenv`
#'
#' @details
#' `eval_code` evaluates given code in the `qenv` environment and appends it to the `code` slot.
#' `eval_code()` evaluates given code in the `qenv` environment and appends it to the `code` slot.
#' Thus, if the `qenv` had been instantiated empty, contents of the environment are always a result of the stored code.
#'
#' @param object (`qenv`)
Expand Down Expand Up @@ -31,7 +31,7 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
id <- sample.int(.Machine$integer.max, size = 1)

object@id <- c(object@id, id)
object@env <- rlang::env_clone(object@env, parent = parent.env(.GlobalEnv))
object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(.GlobalEnv))
averissimo marked this conversation as resolved.
Show resolved Hide resolved
code <- paste(code, collapse = "\n")
object@code <- c(object@code, code)

Expand All @@ -45,11 +45,11 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
x <- withCallingHandlers(
tryCatch(
{
eval(single_call, envir = object@env)
if (!identical(parent.env(object@env), parent.env(.GlobalEnv))) {
# needed to make sure that @env is always a sibling of .GlobalEnv
eval(single_call, envir = object@.xData)
if (!identical(parent.env(object@.xData), 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)
parent.env(object@env) <- parent.env(.GlobalEnv)
parent.env(object@.xData) <- parent.env(.GlobalEnv)
}
NULL
},
Expand Down Expand Up @@ -80,20 +80,19 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code
}
}


object@warnings <- c(object@warnings, current_warnings)
object@messages <- c(object@messages, current_messages)

lockEnvironment(object@env, bindings = TRUE)
lockEnvironment(object@.xData, bindings = TRUE)
object
})

setMethod("eval_code", signature = c("qenv", "language"), function(object, code) {
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
eval_code(object, code = paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), collapse = "\n"))
})

setMethod("eval_code", signature = c("qenv", "expression"), function(object, code) {
eval_code(object, code = paste(lang2calls(code), collapse = "\n"))
eval_code(object, code = paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), collapse = "\n"))
})

setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code) {
Expand Down
26 changes: 25 additions & 1 deletion R/qenv-get_code.R
Original file line number Diff line number Diff line change
@@ -1,7 +1,31 @@
#' @name qenv-inheritted
#' @rdname qenv
#'
#' @details
#'
#' `x[[name]]`, `x$name` and `get(name, x)` are generic \R operators to access the objects in the environment.
#' See [`[[`] for more details.
#' `names(x)` calls on the `qenv` object and will list all objects in the environment.
#'
#' @return `[[`, `$` and `get` return the value of the object named `name` in the `qenv` object.
#' @return `names` return a character vector of all the names of the objects in the `qenv` object.
#' @return `ls` return a character vector of the names of the objects in the `qenv` object.
#' It will only show the objects that are not named with a dot prefix, unless
#' the `all.names = TRUE`, which will show all objects.
#'
#' @examples
#' # Extract objects from qenv
#' q[["a"]]
#' q$a
#'
#' # list objects in qenv
#' names(q)
NULL

#' Get code from `qenv`
#'
#' @details
#' `get_code` retrieves the code stored in the `qenv`. `...` passes arguments to methods.
#' `get_code()` retrieves the code stored in the `qenv`. `...` passes arguments to methods.
#'
#' @param object (`qenv`)
#' @param deparse (`logical(1)`) flag specifying whether to return code as `character` or `expression`.
Expand Down
17 changes: 7 additions & 10 deletions R/qenv-get_env.R
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()
Expand All @@ -13,7 +13,8 @@
#' b <- data.frame(x = 1:10)
#' })
#' get_env(q1)
#' ls(get_env(q1))
#'
#' names(get_env(q1)) # list objects in the environment
averissimo marked this conversation as resolved.
Show resolved Hide resolved
#'
#' @aliases get_env,qenv-method
#' @aliases get_env,qenv.error-method
Expand All @@ -23,10 +24,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)
Loading
Loading