Skip to content

Commit a6c57d5

Browse files
committed
source.coop instead of hf
1 parent eca5134 commit a6c57d5

7 files changed

Lines changed: 262 additions & 180 deletions

File tree

DESCRIPTION

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Description: A programmatic interface to 'FishBase', re-written
66
supports experimental access to 'SeaLifeBase' data, which contains
77
nearly 200,000 species records for all types of aquatic life not covered by
88
'FishBase.'
9-
Version: 5.0.1
9+
Version: 5.0.2
1010
Encoding: UTF-8
1111
License: CC0
1212
Authors@R: c(person("Carl", "Boettiger",
@@ -25,18 +25,17 @@ Authors@R: c(person("Carl", "Boettiger",
2525
URL: https://docs.ropensci.org/rfishbase/, https://github.com/ropensci/rfishbase
2626
BugReports: https://github.com/ropensci/rfishbase/issues
2727
Depends:
28-
R (>= 4.0)
28+
R (>= 4.1.0)
2929
Imports:
3030
glue,
3131
stringr,
3232
purrr,
33-
httr,
34-
jsonlite,
3533
dplyr,
3634
duckdbfs (>= 0.0.9),
3735
rlang,
3836
magrittr,
39-
memoise
37+
memoise,
38+
xml2
4039
Suggests:
4140
testthat,
4241
rmarkdown,

NEWS.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ And constructed with the following guidelines:
2323

2424
For more information on SemVer, please visit http://semver.org/.
2525

26+
v 5.0.2
27+
-------
28+
29+
Data hosting has been migrated from HuggingFace to Source Cooperative (source.coop)
30+
S3 storage. This change should resolve firewall and access issues that some users
31+
experienced with HuggingFace, particularly in institutional or corporate environments.
32+
The data is now served directly from AWS S3 (us-west-2.opendata.source.coop), providing
33+
more reliable access worldwide.
34+
35+
Data updated to 2025-04 release as well.
36+
2637
v 5.0.1
2738
-------
2839
Fix the issue faced by users in China who can't access the URL https://huggingface.co directly.

R/fb_tbl.R

Lines changed: 92 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
#' Access a fishbase or sealifebase table
2-
#'
3-
#'
2+
#'
3+
#'
44
#' Please note that rfishbase accesses static snapshots of the raw database
55
#' tables used by FishBase and Sealifebase websites. Because these are static
66
#' snapshots, they may lag behind the latest available information on the web
7-
#' interface, but should provide stable results.
8-
#'
7+
#' interface, but should provide stable results.
8+
#'
99
#' Please also note that the website pages are not organized precisely along
1010
#' the lines of these tables. A given page for a species may draw on data from
1111
#' multiple tables, and sometimes presents the data in a processed or summarized
1212
#' form. Following RDB design, it is often
1313
#' necessary to join multiple tables. Other data cleaning steps are sometimes
14-
#' necessary as well.
14+
#' necessary as well.
1515
#' @param tbl table name, as it appears in the database. See [fb_tables()]
1616
#' for a list.
1717
#' @param server Access data from fishbase or sealifebase?
@@ -23,130 +23,139 @@
2323
#' @export
2424
#' @examplesIf interactive()
2525
#' fb_tbl("species")
26-
fb_tbl <- function(tbl,
27-
server = c("fishbase", "sealifebase"),
28-
version = "latest",
29-
db = NULL,
30-
collect = TRUE) {
26+
fb_tbl <- function(
27+
tbl,
28+
server = c("fishbase", "sealifebase"),
29+
version = "latest",
30+
db = NULL,
31+
collect = TRUE
32+
) {
3133
urls <- fb_urls(server, version)
3234
names(urls) <- tbl_name(urls)
35+
36+
duckdbfs::duckdb_config(enable_object_cache = 'true')
3337
out <- duckdbfs::open_dataset(urls[tbl])
34-
35-
if(collect) out <- dplyr::collect(out)
36-
37-
out
38+
39+
if (collect) {
40+
out <- dplyr::collect(out)
41+
}
42+
43+
out
3844
}
3945

46+
4047
#' List the tables available on fishbase/sealifebase
41-
#'
48+
#'
4249
#' These table names can be used to access each of the corresponding tables
4350
#' using `[fb_tbl()]`. Please note that following RDB design, it is often
4451
#' necessary to join multiple tables. Other data cleaning steps are sometimes
45-
#' necessary as well.
52+
#' necessary as well.
4653
#' @inheritParams fb_tbl
4754
#' @export
4855
#' @examplesIf interactive()
4956
#' fb_tables()
50-
fb_tables <- function(server = c("fishbase", "sealifebase"),
51-
version = "latest") {
57+
fb_tables <- function(
58+
server = c("fishbase", "sealifebase"),
59+
version = "latest"
60+
) {
5261
fb_urls(server, version) |> tbl_name()
53-
5462
}
5563

5664
#' List available releases
57-
#'
65+
#'
5866
#' @param server fishbase or sealifebase
5967
#' @export
6068
#' @examplesIf interactive()
6169
#' available_releases()
6270
available_releases <- function(server = c("fishbase", "sealifebase")) {
63-
6471
sv <- server_code(server)
65-
repo <- "datasets/cboettig/fishbase"
66-
path <- glue::glue("data/{sv}")
67-
is_user_in_china <- function() {
68-
response <- httr::GET("https://ipinfo.io")
69-
if (httr::status_code(response) == 200) {
70-
data <- jsonlite::fromJSON(httr::content(response, as = "text"))
71-
country <- data$country
72-
return(country == "CN")
73-
} else FALSE
74-
}
72+
bucket <- "us-west-2.opendata.source.coop"
73+
prefix <- glue::glue("cboettig/fishbase/{sv}/")
7574

76-
if (is_user_in_china()) {
77-
hf <- "https://hf-mirror.com"
78-
} else {
79-
hf <- "https://huggingface.co"
80-
}
81-
branch <- "main"
82-
versions <-
83-
glue::glue("{hf}/api/{repo}/tree/{branch}/{path}") |>
84-
jsonlite::read_json() |>
85-
purrr::map_chr('path') |>
86-
stringr::str_extract("\\/v(\\d{2}\\.\\d{2})", 1)
87-
88-
versions
89-
75+
# S3 List Objects API endpoint
76+
s3_endpoint <- glue::glue(
77+
"https://s3.us-west-2.amazonaws.com/{bucket}?list-type=2&prefix={prefix}&delimiter=/"
78+
)
79+
80+
# Parse XML response to get common prefixes (subdirectories)
81+
response <- xml2::read_xml(s3_endpoint)
82+
83+
# Extract version directories from CommonPrefixes
84+
prefixes <- xml2::xml_find_all(
85+
response,
86+
".//d1:CommonPrefixes/d1:Prefix",
87+
xml2::xml_ns(response)
88+
)
89+
prefix_paths <- xml2::xml_text(prefixes)
90+
91+
# Extract version numbers (e.g., v24.07)
92+
versions <- prefix_paths |>
93+
stringr::str_extract("v(\\d{2}\\.\\d{2})", 1)
94+
95+
versions[!is.na(versions)]
9096
}
9197

9298

9399
get_latest_release <- function() "latest"
94100

95101

96-
hf_urls <- function(path = "data/fb/v24.07/parquet",
97-
repo = "datasets/cboettig/fishbase",
98-
branch = "main"
99-
) {
100-
101-
is_user_in_china <- function() {
102-
response <- httr::GET("https://ipinfo.io")
103-
if (httr::status_code(response) == 200) {
104-
data <- jsonlite::fromJSON(httr::content(response, as = "text"))
105-
country <- data$country
106-
return(country == "CN")
107-
} else FALSE
108-
}
102+
s3_urls <- function(
103+
path = "cboettig/fishbase/fb/v24.07/parquet",
104+
bucket = "us-west-2.opendata.source.coop"
105+
) {
106+
# S3 List Objects API endpoint
107+
s3_endpoint <- glue::glue(
108+
"https://s3.us-west-2.amazonaws.com/{bucket}?list-type=2&prefix={path}/"
109+
)
109110

110-
if (is_user_in_china()) {
111-
hf <- "https://hf-mirror.com"
112-
} else {
113-
hf <- "https://huggingface.co"
114-
}
115-
paths <-
116-
glue::glue("{hf}/api/{repo}/tree/{branch}/{path}") |>
117-
jsonlite::read_json() |>
118-
purrr::map_chr('path')
119-
120-
glue::glue("{hf}/{repo}/resolve/{branch}/{path}", path=paths)
121-
}
111+
# Parse XML response to get objects
112+
response <- xml2::read_xml(s3_endpoint)
122113

114+
# Extract object keys
115+
keys <- xml2::xml_find_all(
116+
response,
117+
".//d1:Contents/d1:Key",
118+
xml2::xml_ns(response)
119+
)
120+
object_keys <- xml2::xml_text(keys)
123121

122+
# Filter out invalid/empty parquet files (e.g., ".parquet" with no table name)
123+
object_keys <- object_keys[!grepl("/\\.parquet$", object_keys)]
124124

125-
fb_urls <- function(server = c("fishbase", "sealifebase"),
126-
version = "latest") {
127-
125+
# Build full S3 URLs
126+
glue::glue(
127+
"https://s3.us-west-2.amazonaws.com/{bucket}/{key}",
128+
key = object_keys
129+
)
130+
}
131+
132+
133+
fb_urls <- function(server = c("fishbase", "sealifebase"), version = "latest") {
128134
releases <- available_releases(server)
129135
if (version == "latest") {
130136
version <- max(releases)
131137
}
132-
if ( !(version %in% releases) ) {
138+
if (!(version %in% releases)) {
133139
stop(
134-
glue::glue("version {version} not in ",
135-
glue::glue_collapse(
136-
glue::glue("{releases}"), ", ", last = " or "))
140+
glue::glue(
141+
"version {version} not in ",
142+
glue::glue_collapse(
143+
glue::glue("{releases}"),
144+
", ",
145+
last = " or "
146+
)
147+
)
137148
)
138149
}
139-
150+
140151
sv <- server_code(server)
141-
path <- glue::glue("data/{sv}/v{version}/parquet")
142-
hf_urls(path)
152+
path <- glue::glue("cboettig/fishbase/{sv}/v{version}/parquet")
153+
s3_urls(path)
143154
}
144155

145156
server_code <- function(server = c("fishbase", "sealifebase")) {
146157
server <- match.arg(server)
147-
switch(server,
148-
"fishbase" = "fb",
149-
"sealifebase" = "slb")
158+
switch(server, "fishbase" = "fb", "sealifebase" = "slb")
150159
}
151160

152161

0 commit comments

Comments
 (0)