Skip to content
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 .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@
^revdep
^.DS_Store
^help

^.claude$
14 changes: 10 additions & 4 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
Package: APCalign
Title: Resolving Plant Taxon Names Using the Australian Plant Census
Version: 1.1.6
Version: 1.2.0
Authors@R: c(
person(given = "Elizabeth", family = "Wenk", role = c("aut", "cre", "cph"), email = "e.wenk@unsw.edu.au", comment = c(ORCID = "0000-0001-5640-5910")),
person(given = "Daniel", family = "Falster", role = c("aut", "ctb"), email = "daniel.falster@unsw.edu.au", comment = c(ORCID = "0000-0002-9814-092X")),
person(given = "Will", family = "Cornwell", role = c("aut", "ctb"), email = "w.cornwell@unsw.edu.au", comment = c(ORCID = "0000-0003-4080-4073")),
person(given = "Fonti", family = "Kar", role = c("aut", "ctb"), email = "f.kar@unsw.edu.au", comment = c(ORCID = "0000-0002-2760-3974")),
person(given = "Carl", family= "Boettiger", role = c("ctb"), email = "cboettig@gmail.com", comment = c(ORCID = "0000-0002-1642-628X"))
)
Description: The process of resolving and updating taxon names is necessary when working with biodiversity data. 'APCalign' uses the Australian Plant Census (APC) and the Australian Plant Name Index (APNI) to align and update plant taxon names to current, accepted standards. 'APCalign' also supplies information about the establishment status (i.e. native or introduced) of plant taxa across different states/territories. https://doi.org/10.1071/BT24014
Description: The process of resolving and updating taxon names is necessary when
working with biodiversity data. 'APCalign' uses the Australian Plant Census
(APC) and the Australian Plant Name Index (APNI) to align and update plant
taxon names to current, accepted standards. 'APCalign' also supplies
information about the establishment status (i.e. native or introduced) of
plant taxa across different states/territories. See Falster et al. (2024)
<doi:10.1071/BT24014>.
License: MIT + file LICENSE
Encoding: UTF-8
Language: en
Expand Down Expand Up @@ -38,8 +44,8 @@ Suggests:
here,
testthat (>= 3.0.0)
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
RoxygenNote: 7.3.3
Config/testthat/edition: 3
VignetteBuilder: knitr
URL: https://traitecoevo.github.io/APCalign/, https://github.com/traitecoevo/APCalign, https://www.publish.csiro.au/bt/pdf/BT24014, https://doi.org/10.1071/BT24014
URL: https://traitecoevo.github.io/APCalign/, https://github.com/traitecoevo/APCalign, https://doi.org/10.1071/BT24014
BugReports: https://github.com/traitecoevo/APCalign/issues
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(align_taxa)
export(clear_cached_resources)
export(create_species_state_origin_matrix)
export(create_taxonomic_update_lookup)
export(default_version)
Expand Down
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# APCalign 1.2.0

- `load_taxonomic_resources()` now caches results in memory for the duration of the R session, so repeated calls with the same version return immediately without re-downloading or re-processing data.
- New function `clear_cached_resources()` to remove the session cache and force a reload.
- `load_taxonomic_resources()` now works offline when parquet files have been previously downloaded; `default_version()` falls back to the most recently cached local version when no internet connection is available.
# APCalign 1.1.6

- Fix issue #262: filter to accepted species only in genus-family lookup
Expand Down
174 changes: 134 additions & 40 deletions R/load_taxonomic_resources.R
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
# Package-internal cache environment.
# This is NOT a global variable — it lives in the package namespace only.
# CRAN policy prohibits modifying .GlobalEnv; using a package-private
# environment is the standard CRAN-compliant pattern for session-level caching.
.pkg_cache <- new.env(parent = emptyenv())

#' @title Load taxonomic reference lists, APC & APNI
#'
#' @description
#' This function loads two taxonomic datasets for Australia's vascular plants,
#' the APC and APNI, into the global environment. It creates several data frames
#' by filtering and selecting data from the loaded lists.
#' This function loads two taxonomic datasets for Australia's vascular plants,
#' the APC and APNI. It creates several data frames by filtering and selecting
#' data from the loaded lists.
#'
#' @details
#' - It accesses taxonomic data from a dataset using the provided version number
#' or the default version.
#' - It accesses taxonomic data from a dataset using the provided version number
#' or the default version.
#' - The output is several dataframes that include subsets of the APC/APNI based
#' on taxon rank and taxonomic status.
#' - Results are cached in memory for the R session so that repeated calls with
#' the same `version` and `stable_or_current_data` arguments return immediately
#' without re-downloading or re-processing the data. Use
#' [clear_cached_resources()] to force a reload.
#' - `"current"` data is not cached because it may change between calls.
#'
#' @param stable_or_current_data Type of dataset to access.
#' The default is "stable", which loads the dataset from a github archived file.
#' If set to "current", the dataset will be loaded from a URL which is the
#' @param stable_or_current_data Type of dataset to access.
#' The default is "stable", which loads the dataset from a github archived file.
#' If set to "current", the dataset will be loaded from a URL which is the
#' cutting edge version, but this may change at any time without notice.
#' @param version The version number of the dataset to use.
#' @param version The version number of the dataset to use.
#' Defaults to the default version.
#'
#'
#' @param quiet A logical indicating whether to print status of loading to screen.
#' Defaults to FALSE.
#'
#' @return The taxonomic resources data loaded into the global environment.
#' @return A list of taxonomic resource data frames.
#' @export
#'
#' @examples
Expand All @@ -34,7 +45,19 @@ load_taxonomic_resources <-
function(stable_or_current_data = "stable",
version = default_version(),
quiet = FALSE) {


# Session-level cache for stable data only.
# "current" data is explicitly cutting-edge and may change between calls.
# .pkg_cache is a package-private environment — NOT .GlobalEnv — so this
# is CRAN-compliant.
use_cache <- stable_or_current_data == "stable" && !is.null(version)
cache_key <- if (use_cache) paste0("stable_", version) else NULL

if (use_cache && !is.null(.pkg_cache[[cache_key]])) {
if (!quiet) message("Using cached taxonomic resources.")
return(.pkg_cache[[cache_key]])
}

if(is.null(version)){
message("No internet connection, please retry with stable connection or specify a local version of the data")
return(invisible(NULL))
Expand Down Expand Up @@ -276,9 +299,34 @@ load_taxonomic_resources <-

close(pb)
if(!quiet) message("...done")

# Store in session cache for future calls
if (use_cache) {
.pkg_cache[[cache_key]] <- taxonomic_resources
}

return(taxonomic_resources)
}

#' Clear cached taxonomic resources
#'
#' Removes any taxonomic resources that have been cached in memory during the
#' current R session. After calling this function, the next call to
#' [load_taxonomic_resources()] will re-download and re-process the data.
#'
#' This is useful if you want to force a reload of the resources, for example
#' after updating the package or switching to a different version.
#'
#' @return Invisibly returns `NULL`.
#' @export
#'
#' @examples
#' clear_cached_resources()
clear_cached_resources <- function() {
rm(list = ls(.pkg_cache), envir = .pkg_cache)
invisible(NULL)
}

##' Access Australian Plant Census Dataset
##'
##' This function provides access to the Australian Plant Census dataset
Expand Down Expand Up @@ -312,15 +360,21 @@ dataset_access_function <-

# Check if there is internet connection
## Dummy variable to allow testing of network
network <- as.logical(Sys.getenv("NETWORK_UP", unset = TRUE))


if (!curl::has_internet() | !network| is.null(version)) { # Simulate if network is down
network <- as.logical(Sys.getenv("NETWORK_UP", unset = TRUE))
offline <- !curl::has_internet() | !network

# "current" always requires a live internet connection
if (offline && type == "current") {
message("No internet connection, please retry with stable connection (dataset_access_function)")
return(invisible(NULL))
}

# Download from Github Release
}

if (is.null(version)) {
message("No internet connection, please retry with stable connection (dataset_access_function)")
return(invisible(NULL))
}

# Download from Github Release (dataset_get handles offline fallback)
if (type == "stable") {
return(dataset_get(version, path))
}
Expand Down Expand Up @@ -383,9 +437,16 @@ dataset_access_function <-
default_version <- function() {
# Check if there is internet connection
## Dummy variable to allow testing of network
network <- as.logical(Sys.getenv("NETWORK_UP", unset = TRUE))
network <- as.logical(Sys.getenv("NETWORK_UP", unset = TRUE))

if (!curl::has_internet() | !network) { # Simulate if network is down
# Fall back to the most recently downloaded local version, if any
local_versions <- local_cached_versions()
if (length(local_versions) > 0) {
version <- sort(local_versions, decreasing = TRUE)[1]
message("No internet connection; using most recent locally cached version: ", version)
return(version)
}
message("No internet connection, please retry with stable connection (default_version)")
return(invisible(NULL))
} else {
Expand Down Expand Up @@ -424,19 +485,62 @@ default_version <- function() {
}
}

# Returns a character vector of version strings (e.g. "2024-10-11") for which
# both APC and APNI parquet files exist in the local cache directory.
#' @noRd
local_cached_versions <- function(path = tools::R_user_dir("APCalign")) {
if (!dir.exists(path)) return(character(0))
apc_files <- list.files(path, pattern = "^apc\\d{4}-\\d{2}-\\d{2}\\.parquet$")
versions <- gsub("^apc|\\.parquet$", "", apc_files)
# Only return versions where the matching APNI file also exists
has_apni <- file.exists(file.path(path, paste0("apni", versions, ".parquet")))
versions[has_apni]
}

#' @noRd
dataset_get <- function(version = default_version(),
path = tools::R_user_dir("APCalign")) {

# Check if there is internet connection
## Dummy variable to allow testing of network
network <- as.logical(Sys.getenv("NETWORK_UP", unset = TRUE))

if (!curl::has_internet() | !network | is.null(version)) { # Simulate if network is down
message("No internet connection, please retry with stable connection (dataset_get)")
network <- as.logical(Sys.getenv("NETWORK_UP", unset = TRUE))
offline <- !curl::has_internet() | !network

if (is.null(version)) {
message(
"No internet connection and no locally cached version found (dataset_get)"
)
return(invisible(NULL))
} else{

}

if (!dir.exists(path)) {
dir.create(path, recursive = TRUE)
}

path_to_apc <- file.path(path, paste0("apc", version, ".parquet"))
path_to_apni <- file.path(path, paste0("apni", version, ".parquet"))

# If offline but both files are cached locally, read and return them
if (offline) {
if (file.exists(path_to_apc) && file.exists(path_to_apni)) {
message(
"No internet connection; loading locally cached version: ", version
)
APC <- arrow::read_parquet(path_to_apc)
APNI <- arrow::read_parquet(path_to_apni)
current_list <- list(APC, APNI)
names(current_list) <- c("APC", "APNI")
return(current_list)
} else {
message(
"No internet connection and no local data for version ", version,
"; please connect and retry (dataset_get)"
)
return(invisible(NULL))
}
}

# Online path — download if not already cached
#APC
apc.url <-
paste0(
Expand Down Expand Up @@ -467,33 +571,23 @@ dataset_get <- function(version = default_version(),
return(NULL)
})
}

if (!dir.exists(path)) {
dir.create(path, recursive = TRUE)
}

path_to_apc <- file.path(path, paste0("apc", version, ".parquet"))
path_to_apni <- file.path(path, paste0("apni", version, ".parquet"))


APC <- if (!file.exists(path_to_apc)) {
message("Downloading...")
download_and_read_parquet(apc.url, path_to_apc)
} else {
arrow::read_parquet(path_to_apc)
}

APNI <- if (!file.exists(path_to_apni)) {
download_and_read_parquet(apni.url, path_to_apni)
} else {
arrow::read_parquet(path_to_apni)
}

#combine
current_list <- list(APC, APNI)
names(current_list) <- c("APC", "APNI")
return(current_list)

}
}


Expand Down
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ reference:
- subtitle: Data
- contents:
- load_taxonomic_resources
- clear_cached_resources
- get_versions
- default_version
- gbif_lite
1 change: 0 additions & 1 deletion man/APCalign.Rd

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

23 changes: 23 additions & 0 deletions man/clear_cached_resources.Rd

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

11 changes: 8 additions & 3 deletions man/load_taxonomic_resources.Rd

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

4 changes: 4 additions & 0 deletions tests/testthat/teardown.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Free cached taxonomic resources after the test suite completes.
# This runs before covr saves its coverage traces, preventing the
# memory pressure that causes saveRDS() to segfault on large datasets.
clear_cached_resources()
Loading
Loading