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

Persist registry ID in block #445

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
13 changes: 12 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ S3method(block_icon,data_block)
S3method(block_icon,default)
S3method(block_icon,plot_block)
S3method(block_icon,transform_block)
S3method(block_input_check,data_block)
S3method(block_input_check,parser_block)
S3method(block_input_check,result_block)
S3method(block_input_check,transform_block)
S3method(block_output_ptype,dataset_block)
S3method(block_output_ptype,filesbrowser_block)
S3method(block_output_ptype,result_block)
S3method(block_output_ptype,transform_block)
S3method(block_output_ptype,upload_block)
S3method(blockr_serialize,block)
S3method(blockr_serialize,field)
S3method(blockr_serialize,stack)
Expand Down Expand Up @@ -148,7 +157,9 @@ export(block_combiner)
export(block_descr)
export(block_header)
export(block_icon)
export(block_input_check)
export(block_name)
export(block_output_ptype)
export(blockr_deserialize)
export(blockr_serialize)
export(cat_logger)
Expand All @@ -166,7 +177,6 @@ export(generate_server)
export(generate_ui)
export(get_compatible_blocks)
export(get_field_name)
export(get_registry)
export(get_stack_name)
export(get_stack_title)
export(get_workspace)
Expand All @@ -180,6 +190,7 @@ export(init_lock)
export(initialize_block)
export(initialize_field)
export(inject_remove_button)
export(input_failure)
export(input_ids)
export(is_block)
export(is_field)
Expand Down
83 changes: 83 additions & 0 deletions R/block-core.R
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,86 @@ update_fields.transform_block <- function(x, session, data, ...) {
#' @rdname update_fields
#' @export
update_fields.plot_block <- update_fields.transform_block

#' Block input/output
#'
#' Used for checking whether blocks are compatible
#'
#' @param x Block
#' @rdname block_io
#' @export
block_input_check <- function(x, data, ...) UseMethod("block_input_check")

#' @rdname block_io
#' @export
block_input_check.data_block <- function(x, data, ...) {

if (missing(data) || is.null(data)) {
return(invisible(NULL))
}

input_failure("No (or empty) input expected.")
}

#' @rdname block_io
#' @export
block_input_check.transform_block <- function(x, data, ...) {

if (inherits(data, "data.frame")) {
return(invisible(NULL))
}

input_failure("Expecting data.frame input.")
}

#' @rdname block_io
#' @export
block_input_check.parser_block <- function(x, data, ...) {

if (is_string(data)) {
return(invisible(NULL))
}

input_failure("Expecting string-valued input.")
}

#' @rdname block_io
#' @export
block_input_check.result_block <- function(x, data, ...) {

if (length(get_workspace_stacks()) <= 1L) {
input_failure("Expecting at least two stacks.")
}

NextMethod()
Copy link
Collaborator

Choose a reason for hiding this comment

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

@nbenn Spotted a new issue when checking the tests:

Error: Get compatible blocks
Error in `NextMethod()`: 'NextMethod' called from an anonymous function

Looking at are_blocks_compatible, problem is that the stored function inside attr(x, "input") is anonymous.

Code to reproduce:

stack1 <- new_stack(
    new_dataset_block,
    new_filter_block
  )

  stack2 <- new_stack()

  set_workspace(stack1 = stack1, stack2 = stack2)
serve_workspace(clear = FALSE)

Then, click on the + button for the first stack.

}

#' @rdname block_io
#' @export
input_failure <- function(..., class = character()) {
rlang::abort(paste0(...), class = c(class, "input_failure"))
}

#' @rdname block_io
#' @export
block_output_ptype <- function(x, ...) UseMethod("block_output_ptype")

#' @rdname block_io
#' @export
block_output_ptype.dataset_block <- function(x, ...) data.frame()

#' @rdname block_io
#' @export
block_output_ptype.result_block <- function(x, ...) value(x[["stack"]])

#' @rdname block_io
#' @export
block_output_ptype.upload_block <- function(x, ...) character(1L)

#' @rdname block_io
#' @export
block_output_ptype.filesbrowser_block <- function(x, ...) character(1L)

#' @rdname block_io
#' @export
block_output_ptype.transform_block <- function(x, ...) data.frame()
184 changes: 74 additions & 110 deletions R/registry.R
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,16 @@ block_name <- block_descrs_getter(block_descr_getter("name"))
#' @export
block_descr <- block_descrs_getter(block_descr_getter("description"))

new_block_descr <- function(constructor, name, description, classes, input,
new_block_descr <- function(constructor, name, description, id, classes, input,
output, pkg, category) {
stopifnot(
is.function(constructor), is_string(name), is_string(description),
is_string(category),
is.character(classes), length(classes) >= 1L,
is_string(input), is_string(output), is_string(pkg)
is_string(category), is.character(classes), length(classes) >= 1L,
is.function(input), is_string(pkg), is_string(id)
)

structure(
constructor,
name = name, description = description,
constructor, name = name, description = description, id = id,
classes = classes, input = input, output = output,
package = pkg, category = category, class = "block_descr"
)
Expand All @@ -62,6 +60,7 @@ block_registry <- new.env()
#' @param classes Block classes
#' @param input,output Object types the block consumes and produces
#' @param package Package where block is defined
#' @param id Block registry ID
#' @param category Useful to sort blocks by topics. If not specified,
#' blocks are uncategorized.
#'
Expand All @@ -71,54 +70,66 @@ register_block <- function(
constructor,
name,
description,
classes,
input,
output,
ptype = constructor(),
classes = class(ptype),
input = get_s3_method("block_input_check", ptype),
output = block_output_ptype(ptype),
id = classes[1L],
package = NA_character_,
category = "uncategorized") {

descr <- new_block_descr(
constructor, name, description, classes, input,
output, package, category
constructor,
name,
description,
id, classes,
input,
output,
package,
category
)

id <- classes[1L]

if (id %in% list_blocks()) {
warning("block ", id, " already exists and will be overwritten.")
}

assign(id, descr, envir = block_registry)
}

get_s3_method <- function(generic, obj) {

for (cls in class(obj)) {
res <- try(utils::getS3method("block_input_check", cls), silent = TRUE)
if (!inherits(res, "try-error")) {
return(res)
}
}

stop("no method found for generic ", generic, "and classes ",
paste0(class(obj), collapse = ", "))
}

#' @param ... Forwarded to `register_block()`
#' @rdname available_blocks
#' @export
register_blocks <- function(
constructor,
name,
description,
classes,
input,
output,
package = NA_character_,
category = "uncategorized") {
if (length(constructor) == 1L && !is.list(classes)) {
classes <- list(classes)
register_blocks <- function(...) {

arg_processor <- function(constructor, ...) {

wrap_list <- function(x) {
if (length(x) > 1L) list(x) else x
}

if (length(constructor) > 1L) {
return(c(list(constructor), list(...)))
}

c(list(constructor), lapply(list(...), wrap_list))
}

res <- Map(
register_block,
constructor = constructor,
name = name,
description = description,
classes = classes,
input = input,
output = output,
package = package,
category = category
invisible(
do.call(Map, c(register_block, arg_processor(...)))
)

invisible(res)
}

list_blocks <- function() {
Expand Down Expand Up @@ -212,60 +223,6 @@ register_blockr_blocks <- function(pkg) {
"Select n first rows of dataset",
"Mutate block"
),
classes = list(
c("dataset_block", "data_block"),
c("result_block", "data_block"),
c("upload_block", "data_block"),
c("filesbrowser_block", "data_block"),
c("csv_block", "parser_block", "transform_block"),
c("rds_block", "parser_block", "transform_block"),
c("json_block", "parser_block", "transform_block"),
c("xpt_block", "parser_block", "transform_block"),
c("filter_block", "transform_block"),
c("select_block", "transform_block"),
c("summarize_block", "transform_block"),
c("arrange_block", "transform_block"),
c("group_by_block", "transform_block"),
c("join_block", "transform_block"),
c("head_block", "transform_block"),
c("mutate_block", "transform_block")
),
input = c(
NA_character_,
NA_character_,
NA_character_,
NA_character_,
"string",
"string",
"string",
"string",
"data.frame",
"data.frame",
"data.frame",
"data.frame",
"data.frame",
"data.frame",
"data.frame",
"data.frame"
),
output = c(
"data.frame",
"data.frame",
"string",
"string",
"data.frame",
"data.frame",
"data.frame",
"data.frame",
"data.frame",
"data.frame",
"data.frame",
"data.frame",
"data.frame",
"data.frame",
"data.frame",
"data.frame"
),
package = pkg,
category = c(
"data",
Expand All @@ -292,6 +249,7 @@ register_blockr_blocks <- function(pkg) {
#' @rdname available_blocks
#' @export
construct_block <- function(block, ...) {

if (is_string(block)) {
block <- get_block_descr(block)
}
Expand All @@ -301,29 +259,35 @@ construct_block <- function(block, ...) {
block(...)
}

#' List available blocks as a data.frame
#' Find stack compatible blocks
#'
#' Given a stack, we use the registry to find
#' what are the blocks compatible with the last stack block.
#' If the stack is empy, we return data blocks.
#'
#' Provides an alternate way of displaying
#' the registry information.
#' This can be useful to create dynamic UI elements
#' like in \link{add_block_ui}.
#' @param stack Stack object.
#'
#' @return A dataframe.
#' @return a dataframe.
#'
#' @export
get_registry <- function() {
res <- lapply(seq_along(available_blocks()), \(i) {
blk <- available_blocks()[[i]]
attrs <- attributes(blk)
data.frame(
ctor = names(available_blocks())[[i]],
description = attrs[["description"]],
category = attrs[["category"]],
classes = paste(c(attrs[["classes"]], "block"), collapse = ", "),
input = attrs[["input"]],
output = attrs[["output"]],
package = attrs[["package"]]
get_compatible_blocks <- function(stack) {

is_compat <- function(x, y) {
tryCatch(
{
attr(x, "input")(x(), y)
TRUE
},
input_failure = function(e) FALSE
)
})
do.call(rbind, res)
}

if (length(stack)) {
dat <- block_output_ptype(stack[[length(stack)]])
} else {
dat <- NULL
}

blocks <- available_blocks()
blocks[lgl_ply(blocks, is_compat, dat)]
}
Loading