Add cacheAsInfo() to read back what produced a cache file
A cacheAs file records the parameters of the call, the KorAP instance,
the index revision that instance's corpus had at the time and the version
of RKorAPClient that wrote it - which is what makes such a file worth
keeping next to a document, and until now there was no documented way of
getting at it. cacheAsInfo() returns the record, or NULL for a file
written before 1.4.0, which holds none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Change-Id: If39a8e331a008529fda06d91862e1c498534d78a
diff --git a/NAMESPACE b/NAMESPACE
index 2fd3705..c059673 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -8,6 +8,7 @@
export(bind_cols)
export(buildWebUIRequestUrl)
export(buildWebUIRequestUrlFromString)
+export(cacheAsInfo)
export(ci)
export(complete)
export(defaultAssociationScoreFunctions)
diff --git a/NEWS.md b/NEWS.md
index e19d928..47153ba 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,7 @@
# unpublished dev version 1.3.0.9000
+- **`cacheAsInfo()`** reads back what a `cacheAs` file was produced by: the parameters of the call, the KorAP instance, the index revision its corpus had at the time, and the version of RKorAPClient that wrote it. For a result kept next to a document, that is what says which numbers it rests on
+
- **`cacheAs` is now offered by `frequencyQuery()`, `corpusStats()`, `collocationScoreQuery()` and `textMetadata()`** as well, not only by `collocationAnalysis()`. It is a different thing from the connection's `cache`, which is a transparent speed-up: a `cacheAs` file belongs to the caller and is what keeps an analysis reproducible, since KorAP corpora grow and the same query returns different numbers next year. That is worth having for the quick functions too, where caching for speed would be pointless
- **`cacheAs` files record the version that wrote them** and are refused, with a warning, when that is older than 1.4.0. Their contents are finished results including the association scores, which this version computes differently, so an old file would silently hand back numbers that would not be arrived at again - something no comparison of parameters can notice. The file is then recomputed and overwritten; pass a different name to keep it. How loud a query is no longer counts as a parameter either: `verbose` does not change what is returned
diff --git a/R/cacheAs.R b/R/cacheAs.R
index dfb41bb..6643d4a 100644
--- a/R/cacheAs.R
+++ b/R/cacheAs.R
@@ -116,6 +116,36 @@
}
}
+#' What produced a cacheAs file
+#'
+#' Reads back what a query function recorded in a [cacheAs] file: the parameters
+#' it was called with, the KorAP instance it asked, the index revision that
+#' instance's corpus had at the time, and the version of RKorAPClient that wrote
+#' the file. Useful for saying, of a result kept next to a document, what the
+#' numbers in it rest on.
+#'
+#' @param cacheAs path to the file, with or without its `.rds` extension
+#' @return a list with the elements `scoreVersion`, `packageVersion`,
+#' `parameters`, `dots`,
+#' `apiUrl` and `indexRevision`, or `NULL` for a file written by a version
+#' before 1.4.0, which recorded none of this
+#'
+#' @examples
+#' \dontrun{
+#' KorAPConnection() |> frequencyQuery("Ameisenplage", cacheAs = "ameisenplage.rds")
+#' cacheAsInfo("ameisenplage.rds")
+#' }
+#'
+#' @family cacheAs
+#' @export
+cacheAsInfo <- function(cacheAs) {
+ cacheAs <- cacheAsFileName(cacheAs)
+ if (!file.exists(cacheAs)) {
+ stop(sprintf("Cache file '%s' does not exist.", cacheAs), call. = FALSE)
+ }
+ attr(readRDS(cacheAs), cacheAsAttribute)
+}
+
#' Read back a result stored in a cache file, if it is the one being asked for
#'
#' Warns and returns `NULL` where the file exists but does not match, so that
diff --git a/man/cacheAsInfo.Rd b/man/cacheAsInfo.Rd
new file mode 100644
index 0000000..13bd1a6
--- /dev/null
+++ b/man/cacheAsInfo.Rd
@@ -0,0 +1,32 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/cacheAs.R
+\name{cacheAsInfo}
+\alias{cacheAsInfo}
+\title{What produced a cacheAs file}
+\usage{
+cacheAsInfo(cacheAs)
+}
+\arguments{
+\item{cacheAs}{path to the file, with or without its \code{.rds} extension}
+}
+\value{
+a list with the elements \code{scoreVersion}, \code{packageVersion},
+\code{parameters}, \code{dots},
+\code{apiUrl} and \code{indexRevision}, or \code{NULL} for a file written by a version
+before 1.4.0, which recorded none of this
+}
+\description{
+Reads back what a query function recorded in a \link{cacheAs} file: the parameters
+it was called with, the KorAP instance it asked, the index revision that
+instance's corpus had at the time, and the version of RKorAPClient that wrote
+the file. Useful for saying, of a result kept next to a document, what the
+numbers in it rest on.
+}
+\examples{
+\dontrun{
+KorAPConnection() |> frequencyQuery("Ameisenplage", cacheAs = "ameisenplage.rds")
+cacheAsInfo("ameisenplage.rds")
+}
+
+}
+\concept{cacheAs}
diff --git a/tests/testthat/test-cache-as.R b/tests/testthat/test-cache-as.R
index 2a15b75..0b56a9a 100644
--- a/tests/testthat/test-cache-as.R
+++ b/tests/testthat/test-cache-as.R
@@ -4,6 +4,38 @@
expect_equal(RKorAPClient:::cacheAsFileName("analysis.RDS"), "analysis.RDS")
})
+test_that("cacheAsInfo says what produced a file", {
+ skip_if_offline()
+ kco <- KorAPConnection(accessToken = NULL, verbose = FALSE)
+ file <- tempfile(fileext = ".rds")
+ on.exit(unlink(file), add = TRUE)
+
+ frequencyQuery(kco, "Ameisenplage", cacheAs = file)
+ info <- cacheAsInfo(file)
+
+ expect_equal(info$packageVersion, as.character(utils::packageVersion("RKorAPClient")))
+ expect_equal(info$apiUrl, kco@apiUrl)
+ expect_equal(info$indexRevision, kco@indexRevision)
+ expect_equal(info$parameters$query, "Ameisenplage")
+ # given without its extension, the .rds file is found
+ expect_equal(cacheAsInfo(sub("\\.rds$", "", file)), info)
+})
+
+test_that("cacheAsInfo has nothing to report for a file from before 1.4.0", {
+ file <- tempfile(fileext = ".rds")
+ on.exit(unlink(file), add = TRUE)
+ saveRDS(tibble::tibble(x = 1), file)
+
+ expect_null(cacheAsInfo(file))
+})
+
+test_that("cacheAsInfo refuses a file that is not there", {
+ expect_error(
+ cacheAsInfo(file.path(tempdir(), "no-such-cache.rds")),
+ "does not exist"
+ )
+})
+
test_that("a cache file is written and read back, without contacting the server", {
skip_if_offline()
kco <- KorAPConnection(accessToken = NULL, verbose = FALSE)