|
1 | 1 | #' Access a fishbase or sealifebase table |
2 | | -#' |
3 | | -#' |
| 2 | +#' |
| 3 | +#' |
4 | 4 | #' Please note that rfishbase accesses static snapshots of the raw database |
5 | 5 | #' tables used by FishBase and Sealifebase websites. Because these are static |
6 | 6 | #' 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 | +#' |
9 | 9 | #' Please also note that the website pages are not organized precisely along |
10 | 10 | #' the lines of these tables. A given page for a species may draw on data from |
11 | 11 | #' multiple tables, and sometimes presents the data in a processed or summarized |
12 | 12 | #' form. Following RDB design, it is often |
13 | 13 | #' necessary to join multiple tables. Other data cleaning steps are sometimes |
14 | | -#' necessary as well. |
| 14 | +#' necessary as well. |
15 | 15 | #' @param tbl table name, as it appears in the database. See [fb_tables()] |
16 | 16 | #' for a list. |
17 | 17 | #' @param server Access data from fishbase or sealifebase? |
|
23 | 23 | #' @export |
24 | 24 | #' @examplesIf interactive() |
25 | 25 | #' 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 | +) { |
31 | 33 | urls <- fb_urls(server, version) |
32 | 34 | names(urls) <- tbl_name(urls) |
| 35 | + |
| 36 | + duckdbfs::duckdb_config(enable_object_cache = 'true') |
33 | 37 | 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 |
38 | 44 | } |
39 | 45 |
|
| 46 | + |
40 | 47 | #' List the tables available on fishbase/sealifebase |
41 | | -#' |
| 48 | +#' |
42 | 49 | #' These table names can be used to access each of the corresponding tables |
43 | 50 | #' using `[fb_tbl()]`. Please note that following RDB design, it is often |
44 | 51 | #' necessary to join multiple tables. Other data cleaning steps are sometimes |
45 | | -#' necessary as well. |
| 52 | +#' necessary as well. |
46 | 53 | #' @inheritParams fb_tbl |
47 | 54 | #' @export |
48 | 55 | #' @examplesIf interactive() |
49 | 56 | #' 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 | +) { |
52 | 61 | fb_urls(server, version) |> tbl_name() |
53 | | - |
54 | 62 | } |
55 | 63 |
|
56 | 64 | #' List available releases |
57 | | -#' |
| 65 | +#' |
58 | 66 | #' @param server fishbase or sealifebase |
59 | 67 | #' @export |
60 | 68 | #' @examplesIf interactive() |
61 | 69 | #' available_releases() |
62 | 70 | available_releases <- function(server = c("fishbase", "sealifebase")) { |
63 | | - |
64 | 71 | 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}/") |
75 | 74 |
|
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)] |
90 | 96 | } |
91 | 97 |
|
92 | 98 |
|
93 | 99 | get_latest_release <- function() "latest" |
94 | 100 |
|
95 | 101 |
|
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 | + ) |
109 | 110 |
|
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) |
122 | 113 |
|
| 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) |
123 | 121 |
|
| 122 | + # Filter out invalid/empty parquet files (e.g., ".parquet" with no table name) |
| 123 | + object_keys <- object_keys[!grepl("/\\.parquet$", object_keys)] |
124 | 124 |
|
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") { |
128 | 134 | releases <- available_releases(server) |
129 | 135 | if (version == "latest") { |
130 | 136 | version <- max(releases) |
131 | 137 | } |
132 | | - if ( !(version %in% releases) ) { |
| 138 | + if (!(version %in% releases)) { |
133 | 139 | 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 | + ) |
137 | 148 | ) |
138 | 149 | } |
139 | | - |
| 150 | + |
140 | 151 | 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) |
143 | 154 | } |
144 | 155 |
|
145 | 156 | server_code <- function(server = c("fishbase", "sealifebase")) { |
146 | 157 | server <- match.arg(server) |
147 | | - switch(server, |
148 | | - "fishbase" = "fb", |
149 | | - "sealifebase" = "slb") |
| 158 | + switch(server, "fishbase" = "fb", "sealifebase" = "slb") |
150 | 159 | } |
151 | 160 |
|
152 | 161 |
|
|
0 commit comments