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

Adds names() function and deprecates datanames() #347

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ Collate:
'join_keys.R'
'teal.data.R'
'teal_data-class.R'
'teal_data-datanames.R'
'teal_data-get_code.R'
'teal_data-names.R'
'teal_data-show.R'
'teal_data.R'
'testhat-helpers.R'
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ S3method("[[<-",join_keys)
S3method("join_keys<-",join_keys)
S3method("join_keys<-",teal_data)
S3method("names<-",join_keys)
S3method("names<-",teal_data)
S3method("parents<-",join_keys)
S3method("parents<-",teal_data)
S3method(c,join_key_set)
Expand All @@ -14,6 +15,7 @@ S3method(format,join_keys)
S3method(join_keys,default)
S3method(join_keys,join_keys)
S3method(join_keys,teal_data)
S3method(names,teal_data)
S3method(parents,join_keys)
S3method(parents,teal_data)
S3method(print,join_keys)
Expand Down
13 changes: 8 additions & 5 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@

### Breaking changes

- soft deprecate `datanames` argument of `get_code()`. Use `names` instead.
- Soft deprecate `datanames` argument of `get_code()`. Use `names` instead.
- Soft deprecate of `datanames()`. Use `names()` instead.
- Deprecate of `datanames(x) <- value`. Does nothing, replace with renaming the objects inside the environment.


### Enhancements

- `datanames()`
- if `join_keys` are provided, the `datanames()` are now sorted in topological way (`Kahn` algorithm),
- `names()` function is introduced replacing `datanames`.
- if `join_keys` are provided, the `names()` are now sorted in topological way (`Kahn` algorithm),
which means the parent dataset always precedes the child dataset.
- are extended by the parent dataset name, if one of the child dataset exist in `datanames()` and
- are extended by the parent dataset name, if one of the child dataset exist in `names()` and
the connection between child-parent is set through `join_keys` and `parent` exist in `teal_data` environment.
- do not allow to set a dataset name that do not exist in `teal_data` environment.
- `teal_data` no longer set default `datanames()` based on `join_keys` names - it uses only data names.
- `teal_data` no longer set default `names()` based on `join_keys` names - it uses only data names.

### Miscellaneous

Expand Down
48 changes: 48 additions & 0 deletions R/deprecated.R
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,51 @@ get_join_keys <- function(...) {
get_labels <- function(...) {
.deprecate_function("get_labels()", "Use col_labels(data)")
}

#' Names of data sets in `teal_data` object
#'
#' @description
#' `r lifecycle::badge("deprecated")`
#'
#' Use `names()` instead of `datanames()`.
#'
#' `datanames()` is deprecated. If object should be hidden, then use a `.` (dot)
#' prefix for the object's name.
#'
#' @param x (`teal_data` or `qenv_error`) object to access or modify
#' @param value (`character`) new value for `@datanames`; all elements must be names of variables existing in `@.xData`
#'
#' @return The contents of `@datanames` or `teal_data` object with updated `@datanames`.
#' @aliases `datanames<-.teal_data`
#'
#' @name datanames

#' @rdname datanames
#' @export
datanames <- function(x) {
lifecycle::deprecate_soft("0.6.1", "datanames()", details = "names()")
names(x)
}

#' @rdname datanames
#' @export
`datanames<-` <- function(x, value) {
lifecycle::deprecate_soft(
"0.6.1",
"`datanames<-`()",
details = "invalid to use `datanames()<-` or `names()<-` on an object of class `teal_data`. See ?names.teal_data"
)
names(x)
}

#' @rdname datanames
#' @export
#' @keywords internal
`names<-.teal_data` <- function(x, value) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I acknowledge introducing names<- just to deprecate itself

lifecycle::deprecate_warn(
"0.6.1",
"`names<-.teal_data`()",
details = "invalid to use `datanames()<-` or `names()<-` on an object of class `teal_data`. See ?names.teal_data"
)
x
}
1 change: 0 additions & 1 deletion R/join_keys.R
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ join_keys.teal_data <- function(...) {
#' join_keys(td)
`join_keys<-.teal_data` <- function(x, value) {
join_keys(x@join_keys) <- value
datanames(x) <- x@datanames # datanames fun manages some exceptions
x
}

Expand Down
39 changes: 15 additions & 24 deletions R/teal_data-class.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,34 @@ setOldClass("join_keys")
#' @name teal_data-class
#' @rdname teal_data-class
#'
#' @slot env (`environment`) environment containing data sets and possibly auxiliary variables.
#' Access variables with [get_var()] or [`[[`].
#' No setter provided. Evaluate code to add variables into `@env`.
#' @slot code (`character`) vector representing code necessary to reproduce the contents of `@env`.
#' @slot .xData (`environment`) environment containing data sets and possibly
#' auxiliary variables.
#' Access variables with [get()], [`$`], [get_var()] or [`[[`].
#' No setter provided. Evaluate code to add variables into `@.xData`.
#' @slot code (`character`) vector representing code necessary to reproduce the
#' contents of `@.xData`.
#' Access with [get_code()].
#' No setter provided. Evaluate code to append code to the slot.
#' @slot id (`integer`) random identifier assigned to each element of `@code`. Used internally.
#' @slot id (`integer`) random identifier assigned to each element of `@code`.
#' Used internally.
#' @slot warnings (`character`) vector of warnings raised when evaluating code.
#' Access with [get_warnings()].
#' @slot messages (`character`) vector of messages raised when evaluating code.
#' @slot join_keys (`join_keys`) object specifying joining keys for data sets in `@env`.
#' @slot join_keys (`join_keys`) object specifying joining keys for data sets in
#' `@.xData`.
#' Access or modify with [join_keys()].
#' @slot datanames (`character`) vector of names of data sets in `@env`.
#' Used internally to distinguish them from auxiliary variables.
#' Access or modify with [datanames()].
#' @slot verified (`logical(1)`) flag signifying that code in `@code` has been proven to yield contents of `@env`.
#' @slot verified (`logical(1)`) flag signifying that code in `@code` has been
#' proven to yield contents of `@.xData`.
#' Used internally. See [`verify()`] for more details.
#'
#' @import teal.code
#' @keywords internal
setClass(
Class = "teal_data",
contains = "qenv",
slots = c(join_keys = "join_keys", datanames = "character", verified = "logical"),
slots = c(join_keys = "join_keys", verified = "logical"),
prototype = list(
join_keys = join_keys(),
datanames = character(0),
verified = logical(0)
)
)
Expand All @@ -53,18 +54,11 @@ setClass(
#' @param code (`character` or `language`) code to reproduce the `data`.
#' Accepts and stores comments also.
#' @param join_keys (`join_keys`) object
#' @param datanames (`character`) names of datasets passed to `data`.
#' Needed when non-dataset objects are needed in the `env` slot.
#' @rdname new_teal_data
#' @keywords internal
new_teal_data <- function(data,
code = character(0),
join_keys = join_keys(),
datanames = names(data)) {
new_teal_data <- function(data, code = character(0), join_keys = join_keys()) {
checkmate::assert_list(data)
checkmate::assert_class(join_keys, "join_keys")
if (is.null(datanames)) datanames <- character(0) # todo: allow to specify
checkmate::assert_character(datanames)
if (!any(is.language(code), is.character(code))) {
stop("`code` must be a character or language object.")
}
Expand All @@ -82,17 +76,14 @@ new_teal_data <- function(data,
new_env <- rlang::env_clone(list2env(data), parent = parent.env(.GlobalEnv))
lockEnvironment(new_env, bindings = TRUE)

datanames <- .get_sorted_datanames(datanames = datanames, join_keys = join_keys, env = new_env)

methods::new(
"teal_data",
env = new_env,
.xData = new_env,
code = code,
warnings = rep("", length(code)),
messages = rep("", length(code)),
id = id,
join_keys = join_keys,
datanames = datanames,
verified = verified
)
}
68 changes: 0 additions & 68 deletions R/teal_data-datanames.R

This file was deleted.

6 changes: 4 additions & 2 deletions R/teal_data-get_code.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
#'
#' Retrieve code from `teal_data` object.
#'
#' Retrieve code stored in `@code`, which (in principle) can be used to recreate all objects found in `@env`.
#' Use `names` to limit the code to one or more of the datasets enumerated in `@datanames`.
#' Retrieve code stored in `@code`, which (in principle) can be used to recreate
#' all objects found in `@.xData`.
#' Use `names` to limit the code to one or more of the datasets enumerated in
#' the environment.
#'
#' @section Extracting dataset-specific code:
#' When `names` is specified, the code returned will be limited to the lines needed to _create_
Expand Down
41 changes: 41 additions & 0 deletions R/teal_data-names.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#' Names of data sets in `teal_data` object
#'
#' Functions to get the names of a `teal_data` object.
#' The names are extrapolated from the objects in the `qenv` environment and
#' are not stored statically, unlike the normal behavior of `names()` function.
#'
#' Objects named with a `.` (dot) prefix will be ignored and not returned,
#' unless `all.names` parameter is set to `TRUE`.
#'
#' @param x A (`teal_data`) object to access or modify.
#'
#' @return A character vector of names.
#'
#' @examples
#' td <- teal_data(iris = iris)
#' td <- within(td, mtcars <- mtcars)
#' names(td)
#'
#' td <- within(td, .CO2 <- CO2)
#' names(td)
#'
#' @export
names.teal_data <- function(x) {
names_x <- names(as.environment(x))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using names in body which will return all objects, including with dot prefix.

I don't think it makes sense to use ls(x, all.names = FALSE) as that changes the way the function works.

We could add support to ls via some trickery, but I'm leaning against it, mainly as it will appear as masking ls when loading the package.

#' @export
ls <- function(x, ...) {
  UseMethod("ls")
}

#' @export
ls.teal_data <- function(x, ...) {
  names_x <- base::ls(as.environment(x), ...)
  .get_sorted_names(names_x, join_keys(x), as.environment(x))
}

#' @export
ls.default <- function(x, ...) {
  base::ls(x, ...)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that by using names here, we need to make some changes to teal summary table (as that should be ordered with this behavior in mind)

.get_sorted_names(names_x, join_keys(x), as.environment(x))
}

#' @keywords internal
.get_sorted_names <- function(datanames, join_keys, env) {
child_parent <- sapply(
datanames,
function(name) parent(join_keys, name),
USE.NAMES = TRUE,
simplify = FALSE
)

union(
intersect(unlist(topological_sort(child_parent)), ls(env, all.names = TRUE)),
datanames
)
}
2 changes: 1 addition & 1 deletion R/teal_data-show.R
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ setMethod("show", signature = "teal_data", function(object) {
} else {
cat("\u2716", "unverified teal_data object\n")
}
rlang::env_print(object@env)
rlang::env_print(teal.code::get_env(object))
})
16 changes: 9 additions & 7 deletions R/verify.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
#'
#' Checks whether code in `teal_data` object reproduces the stored objects.
#'
#' If objects created by code in the `@code` slot of `x` are `all_equal` to the contents of the `@env` slot,
#' If objects created by code in the `@code` slot of `x` are `all_equal` to the
#' contents of the `@.xData` slot,
#' the function updates the `@verified` slot to `TRUE` in the returned `teal_data` object.
#' Once verified, the slot will always be set to `TRUE`.
#' If the `@code` fails to recreate objects in `teal_data@env`, an error is raised.
#' If the `@code` fails to recreate objects in `teal_data`'s environment, an
#' error is raised.
#'
#' @return Input `teal_data` object or error.
#'
Expand Down Expand Up @@ -65,7 +67,7 @@ setMethod("verify", signature = "teal_data", definition = function(x) {
stop(conditionMessage(y), call. = FALSE)
}

reproduced <- isTRUE(all.equal(x@env, y@env))
reproduced <- isTRUE(all.equal(teal.code::get_env(x), teal.code::get_env(y)))
if (reproduced) {
x@verified <- TRUE
methods::validObject(x)
Expand All @@ -74,15 +76,15 @@ setMethod("verify", signature = "teal_data", definition = function(x) {
error <- "Code verification failed."

objects_diff <- vapply(
intersect(names(x@env), names(y@env)),
intersect(names(x), names(y)),
function(element) {
isTRUE(all.equal(x@env[[element]], y@env[[element]]))
isTRUE(all.equal(x[[element]], y[[element]]))
},
logical(1)
)

names_diff_other <- setdiff(names(y@env), names(x@env))
names_diff_inenv <- setdiff(names(x@env), names(y@env))
names_diff_other <- setdiff(names(y), names(x))
names_diff_inenv <- setdiff(names(x), names(y))

if (length(objects_diff)) {
error <- c(
Expand Down
Loading
Loading