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

Basic support for dependencies #5

Merged
merged 7 commits into from
Apr 5, 2023
Merged
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 @@ -18,7 +18,7 @@ Imports:
fs,
jsonlite,
orderly,
outpack,
outpack (>= 0.2.4),
withr,
yaml
Suggests:
Expand Down
1 change: 1 addition & 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

export(orderly_artefact)
export(orderly_depends)
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the most consistent with the other function names would be orderly_dependency

Copy link
Member Author

Choose a reason for hiding this comment

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

done in #9

export(orderly_parameters)
export(orderly_resource)
export(orderly_run)
16 changes: 16 additions & 0 deletions R/interactive.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## This is something that we might improve over time - it will likely
## be useful to have some sort of "register interactive" function
## which we could then just look up.
##
## I am not sure if we also want to allow working interactively from a
## draft directory too.
detect_orderly_interactive_path <- function(path = getwd()) {
path_split <- fs::path_split(path)[[1]]
is_plausible <- length(path_split) > 2 &&
path_split[[length(path_split) - 1]] == "src" &&
file.exists(file.path(path, "../..", "orderly_config.yml"))
if (!is_plausible) {
stop(sprintf("Failed to detect orderly path at '%s'", path))
}
orderly_root(file.path(path, "../.."), FALSE)
}
74 changes: 74 additions & 0 deletions R/metadata.R
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,79 @@ static_orderly_artefact <- function(args) {
}


##' Declare a dependency on another packet
##'
##' @title Declare a dependency
##'
##' @param name The name of the packet to depend on
##'
##' @param query The query to search for; often this will simply be
##' the string `latest`, indicating the most recent version. You may
##' want a more complex query here though.
##'
##' @param use A named character vector of filenames to copy from the
##' upstream packet. The name corresponds to the destination name,
##' so c(here.csv = "there.csv") will take the upstream file
##' `there.csv` and copy it over as `here.csv`.
##'
##' @return Undefined
##' @export
orderly_depends <- function(name, query, use) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Feels like it would be nice if you could just do function(name, query, ...) where ... is the named file arguments.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree, but here's the pair of issues issue with that:

First, In the case where you want to compute the filenames passing in dots is hard as R does not have a spread/splat operator. This will be an issue for Pete's case where we are pulling in things from different regions and renaming them as what we can do with an explicit use argument is:

for (r in regions) {
  query <- rlang::expr(parameter:region == !!r)
  use <- setNames("data.rds", file.path("data", paste0(r, ".rds")))
  orderly_depends("data", query, use)
}

Second, you can compromise by adding a get collisions with names, so we could write:

orderly_depends <- function(name, query, ..., use = c(...)) {
}

or by carefully processing ..., but this requires some care with any analysis of the contents in static_orderly_dependency I think.

I had a look at sorting this out but it looks complicated enough that I'd rather kick it down the road a bit

Copy link
Member Author

Choose a reason for hiding this comment

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

assert_scalar_character(name)
assert_scalar_character(query)

assert_character(use)
assert_named(use, unique = TRUE)

p <- get_active_packet()
if (is.null(p)) {
path <- getwd()
root <- detect_orderly_interactive_path(path)
env <- parent.frame()
id <- outpack::outpack_query(query, env, name = name,
require_unpacked = TRUE,
root = root$outpack)
outpack::outpack_copy_files(id, use, path, root$outpack)
} else {
id <- outpack::outpack_query(query, p$parameters, name = name,
require_unpacked = TRUE,
root = p$root)
outpack::outpack_packet_use_dependency(id, use, p)
}

invisible()
}


static_orderly_depends <- function(args) {
name <- args$name
query <- args$query
use <- args$use

name <- static_string(name)
use <- static_character_vector(use)
## TODO: allow passing expressions directly in, that will be much
## nicer, but possibly needs some care as we do want a consistent
## approach to NSE here
query <- static_string(query)
if (is.null(name) || is.null(use) || is.null(query)) {
return(NULL)
}
list(name = name, query = query, use = use)
}


static_string <- function(x) {
if (is.character(x)) {
x
} else if (is_call(x, "c") && length(x) == 2 && is.character(x[[2]])) {
x[[2]]
} else {
NULL
}
}


static_character_vector <- function(x) {
if (is.character(x)) {
x
Expand All @@ -133,6 +206,7 @@ static_character_vector <- function(x) {
}



static_eval <- function(fn, call) {
if (is_call(call[[1]], "::")) {
call[[1]] <- call[[1]][[3]]
Expand Down
6 changes: 5 additions & 1 deletion R/read.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ orderly_read_r <- function(path) {

check <- list(orderly_parameters = static_orderly_parameters,
orderly_resource = static_orderly_resource,
orderly_artefact = static_orderly_artefact)
orderly_artefact = static_orderly_artefact,
orderly_depends = static_orderly_depends)
dat <- set_names(rep(list(NULL), length(check)), names(check))

for (e in exprs) {
Expand Down Expand Up @@ -49,6 +50,9 @@ orderly_read_r <- function(path) {
if (length(dat$artefact) > 0) {
ret$artefacts <- dat$artefact
}
if (length(dat$depends) > 0) {
ret$depends <- drop_null(dat$depends, empty = NULL)
}

ret
}
Expand Down
6 changes: 6 additions & 0 deletions R/util.R
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,9 @@ data_frame <- function(...) {
squote <- function(x) {
sprintf("'%s'", x)
}


drop_null <- function(x, empty) {
i <- vlapply(x, is.null)
if (all(i)) empty else x[!i]
}
26 changes: 26 additions & 0 deletions man/orderly_depends.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions tests/testthat/examples/depends/orderly.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
orderly3::orderly_depends("explicit", "latest", c(graph.png = "mygraph.png"))
orderly3::orderly_artefact("Final plot", "graph.png")
Comment on lines +1 to +2
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a test case where the name of the dependency is different from the artefact? Ran into an issue with this in orderly2 which is what vimc/orderly2#8 is addressing. Could also do that in a separate PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

In this example the dependency is being renamed, but there's no "unexpected files" check yet here (it'll be a bit harder to add). Or is it something else that's missing in the orderly2 test?

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean the name of the dependency after being pulled into this report run is different from the name of the artefact. They are the same here as you say. Though actually if we have no unexpected files check here then comment above is not relevant!

14 changes: 14 additions & 0 deletions tests/testthat/test-interactive.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
test_that("can detect orderly directory", {
path <- test_prepare_orderly_example("explicit")
env <- new.env()
id <- orderly_run("explicit", root = path, envir = env)

expect_error(
detect_orderly_interactive_path(path),
"Failed to detect orderly path at")
expect_error(
detect_orderly_interactive_path(file.path(path, "src")),
"Failed to detect orderly path at")
root <- detect_orderly_interactive_path(file.path(path, "src", "explicit"))
expect_s3_class(root, "orderly_root")
})
30 changes: 29 additions & 1 deletion tests/testthat/test-read.R
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ test_that("Skip over computed resources", {
})


test_that("Can read string literals from expressions", {
test_that("Can read string vector literals from expressions", {
expect_equal(static_character_vector(quote("x")), "x")
expect_equal(static_character_vector(quote(c("x"))), "x")
expect_equal(static_character_vector(quote(c("x", "y"))), c("x", "y"))
Expand All @@ -34,3 +34,31 @@ test_that("Can read string literals from expressions", {
expect_null(static_character_vector(quote(c("x", c("y", b)))))
expect_null(static_character_vector(quote(c(a, c("x", "y")))))
})


test_that("Can read string from expressions", {
expect_equal(static_string(quote("x")), "x")
expect_equal(static_string(quote(c("x"))), "x")

expect_null(static_string(quote(a)))
expect_null(static_string(quote(c(a))))
})


test_that("read dependency", {
args <- list(name = "a", query = "latest", use = c(x = "y"))
expect_equal(static_orderly_depends(args), args)

expect_null(
static_orderly_depends(list(name = quote(a),
query = "latest",
use = c(x = "y"))))
expect_null(
static_orderly_depends(list(name = "a",
query = quote(latest),
use = c(x = "y"))))
expect_null(
static_orderly_depends(list(name = "a",
query = "latest",
use = quote(use))))
})
34 changes: 34 additions & 0 deletions tests/testthat/test-run.R
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,37 @@ test_that("can run orderly with parameters, without orderly", {
expect_true(file.exists(path_rds))
expect_equal(readRDS(path_rds), list(a = 10, b = 2, c = 30))
})


test_that("Can run simple case with dependency", {
path <- test_prepare_orderly_example(c("explicit", "depends"))
env1 <- new.env()
id1 <- orderly_run("explicit", root = path, envir = env1)
env2 <- new.env()
id2 <- orderly_run("depends", root = path, envir = env2)

path1 <- file.path(path, "archive", "explicit", id1)
path2 <- file.path(path, "archive", "depends", id2)

expect_true(file.exists(file.path(path2, "graph.png")))
expect_equal(
unname(tools::md5sum(file.path(path2, "graph.png"))),
unname(tools::md5sum(file.path(path1, "mygraph.png"))))
})


test_that("Can run dependencies case without orderly", {
path <- test_prepare_orderly_example(c("explicit", "depends"))
env1 <- new.env()
id1 <- orderly_run("explicit", root = path, envir = env1)

env2 <- new.env()
path_src <- file.path(path, "src", "depends")
withr::with_dir(path_src,
sys.source("orderly.R", env2))
expect_setequal(dir(path_src), c("orderly.R", "graph.png"))
expect_equal(
unname(tools::md5sum(file.path(path_src, "graph.png"))),
unname(tools::md5sum(file.path(path, "archive", "explicit", id1,
"mygraph.png"))))
})
15 changes: 15 additions & 0 deletions tests/testthat/test-util.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,18 @@ test_that("null-or-value works", {
expect_equal(NULL %||% NULL, NULL)
expect_equal(NULL %||% 2, 2)
})


test_that("drop null can return corner case", {
expect_null(drop_null(list(), NULL))
expect_null(drop_null(list(NULL), NULL))
expect_null(drop_null(list(NULL, NULL), NULL))
expect_equal(drop_null(list(NULL, NULL), list()), list())
})


test_that("drop_null can filter list", {
expect_equal(drop_null(list("x", NULL)), list("x"))
expect_equal(drop_null(list("x", "y")), list("x", "y"))
expect_equal(drop_null(list("x", NULL, "y")), list("x", "y"))
})