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

Jolpica #270

Merged
merged 10 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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 @@ -12,7 +12,7 @@
^LICENSE\.md$
^\d{4}/*$
^cran-comments\.md$
^vignettes/ergast-data-analysis.Rmd.orig$
^vignettes/jolpica-data-analysis.Rmd.orig$
^vignettes/introduction.Rmd.orig$
^doc$
^Meta$
Expand Down
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Authors@R: c(
person("Philip", "Bulsink", , "[email protected]", role = "aut",
comment = c(ORCID = "0000-0001-9668-2429"))
)
Description: Obtain Formula 1 data via the 'Ergast API' <https://ergast.com/mrd/> and the unofficial API <https://www.formula1.com/en/timing/f1-live> via the 'fastf1' 'Python' library <https://docs.fastf1.dev/>.
Description: Obtain Formula 1 data via the 'Jolpica API' <https://jolpi.ca> and the unofficial API <https://www.formula1.com/en/timing/f1-live> via the 'fastf1' 'Python' library <https://docs.fastf1.dev/>.
Config/reticulate:
list(
packages = list(
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

# f1dataR 1.6.0.9000

* Deprecated Ergast and moved to Jolpica API for Ergast Functions

# f1dataR 1.6.0

* Updates per FastF1 (python) updates at 3.4.0 #259
Expand Down
2 changes: 1 addition & 1 deletion R/clear_f1_cache.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#' Clear f1dataR Cache
#'
#' @description Clears the cache for f1dataR telemetry and Ergast API results.
#' @description Clears the cache for f1dataR telemetry and Jolpica API results.
#' Note that the cache directory can be set by setting `option(f1dataR.cache = [cache dir])`,
#' but the default is a temporary directory.
#'
Expand Down
2 changes: 1 addition & 1 deletion R/load_circuits.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ load_circuits <- function(season = get_current_season()) {
season = season
)

data <- get_ergast_content(url)
data <- get_jolpica_content(url)

if (is.null(data)) {
return(NULL)
Expand Down
28 changes: 25 additions & 3 deletions R/load_constructors.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,36 @@
#' @export
#' @return A tibble with one row per constructor
load_constructors <- function() {
url <- "constructors.json?limit=300"
data <- get_ergast_content(url)
lim <- 100
url <- glue::glue("constructors.json?limit={lim}", lim = lim)
data <- get_jolpica_content(url)

if (is.null(data)) {
return(NULL)
}

return(data$MRData$ConstructorTable$Constructors %>%
total <- data$MRData$total %>% as.numeric()
offset <- data$MRData$offset %>% as.numeric()

full <- data$MRData$ConstructorTable$Constructors

# Iterate over the request until completed
while (offset + lim <= total) {
offset <- offset + lim

url <- glue::glue("constructors.json?limit={lim}&offset={offset}",
lim = lim, offset = offset
)
data <- get_jolpica_content(url)

if (is.null(data)) {
return(NULL)
}

full <- dplyr::bind_rows(full, data$MRData$ConstructorTable$Constructors)
}

return(full %>%
dplyr::select("constructorId", "name", "nationality") %>%
janitor::clean_names())
}
4 changes: 2 additions & 2 deletions R/load_drivers.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ load_drivers <- function(season = get_current_season()) {
cli::cli_abort('{.var season} must be between 1950 and {get_current_season()} (or use "current")')
}

url <- glue::glue("{season}/drivers.json?limit=40",
url <- glue::glue("{season}/drivers.json?limit=50",
season = season
)
data <- get_ergast_content(url)
data <- get_jolpica_content(url)

if (is.null(data)) {
return(NULL)
Expand Down
30 changes: 18 additions & 12 deletions R/load_laps.R
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,37 @@ load_laps <- function(season = get_current_season(), round = "last", race = life
cli::cli_abort('{.var season} must be between 1996 and {get_current_season()} (or use "current")')
}

lim <- 100

# Function Code
url <- glue::glue("{season}/{round}/laps.json?limit=1000",
season = season, round = round
url <- glue::glue("{season}/{round}/laps.json?limit={lim}",
season = season, round = round, lim = lim
)
data <- get_ergast_content(url)
data <- get_jolpica_content(url)

if (is.null(data)) {
return(NULL)
}

total <- data$MRData$total %>% as.numeric()
if (total - 1000 > 0 && total - 1000 <= 1000) {
lim <- total - 1000
url2 <- glue::glue("{season}/{round}/laps.json?limit={lim}&offset=1000",
lim = lim, season = season, round = round
offset <- data$MRData$offset %>% as.numeric()

full <- data$MRData$RaceTable$Races$Laps[[1]][2]

# Iterate over the request until completed
while (offset + lim <= total) {
offset <- offset + lim

url <- glue::glue("{season}/{round}/laps.json?limit={lim}&offset={offset}",
lim = lim, season = season, round = round, offset = offset
)
data2 <- get_ergast_content(url2)
data <- get_jolpica_content(url)

if (is.null(data2)) {
if (is.null(data)) {
return(NULL)
}

full <- dplyr::bind_rows(data$MRData$RaceTable$Races$Laps[[1]][2], data2$MRData$RaceTable$Races$Laps[[1]][2])
} else {
full <- data$MRData$RaceTable$Races$Laps[[1]][2]
full <- dplyr::bind_rows(full, data$MRData$RaceTable$Races$Laps[[1]][2])
}

laps <- tibble::tibble()
Expand Down
4 changes: 2 additions & 2 deletions R/load_pitstops.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ load_pitstops <- function(season = get_current_season(), round = "last", race =
}

# Function Code
url <- glue::glue("{season}/{round}/pitstops.json?limit=80",
url <- glue::glue("{season}/{round}/pitstops.json?limit=100",
season = season, round = round
)
data <- get_ergast_content(url)
data <- get_jolpica_content(url)

if (is.null(data)) {
return(NULL)
Expand Down
2 changes: 1 addition & 1 deletion R/load_quali.R
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ load_quali <- function(season = get_current_season(), round = "last") {
season = season, round = round
)

data <- get_ergast_content(url)
data <- get_jolpica_content(url)

if (is.null(data)) {
return(NULL)
Expand Down
2 changes: 1 addition & 1 deletion R/load_results.R
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ load_results <- function(season = get_current_season(), round = "last") {
url <- glue::glue("{season}/{round}/results.json?limit=40",
season = season, round = round
)
data <- get_ergast_content(url)
data <- get_jolpica_content(url)

if (is.null(data)) {
return(NULL)
Expand Down
2 changes: 1 addition & 1 deletion R/load_schedule.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ load_schedule <- function(season = get_current_season()) {

url <- glue::glue("{season}.json?limit=30", season = season)

data <- get_ergast_content(url)
data <- get_jolpica_content(url)
if (is.null(data)) {
return(NULL)
}
Expand Down
2 changes: 1 addition & 1 deletion R/load_sprint.R
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ load_sprint <- function(season = get_current_season(), round = "last") {
season = season, round = round
)

data <- get_ergast_content(url)
data <- get_jolpica_content(url)

if (is.null(data)) {
return(NULL)
Expand Down
2 changes: 1 addition & 1 deletion R/load_standings.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ load_standings <- function(season = get_current_season(), round = "last", type =
season = season, round = round, type = type
)

data <- get_ergast_content(url)
data <- get_jolpica_content(url)

if (is.null(data)) {
return(NULL)
Expand Down
2 changes: 1 addition & 1 deletion R/plot_fastest.R
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ plot_fastest <- function(season = get_current_season(), round = 1, session = "R"

season_drivers <- load_drivers(season = season)
if (is.null(season_drivers)) {
# Ergast is down
# Jolpica is down
lap_time <- ""
if (get_fastf1_version() < "3.4") {
driver_name <- driver
Expand Down
90 changes: 88 additions & 2 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
#' Further processing is performed by specific functions
get_ergast_content <- function(url) {
# Function Deprecation Warning
lifecycle::deprecate_soft("at the end of 2024", "get_ergast_content()",
lifecycle::deprecate_warn("at the end of 2024", "get_ergast_content()",
details = c(
"i" = "By the end of 2024 the Ergast Motor Racing Database API will be shut down.",
" " = "This package will update with a replacement when one is available."
" " = "Update f1dataR to use the new jolpica-f1 API data source"
)
)

Expand Down Expand Up @@ -94,6 +94,92 @@ get_ergast_content <- function(url) {
}


#' Get Jolpica Content
#'
#' @description Gets Jolpica-F1 content and returns the processed json object if
#' no errors are found. This will automatically fall back from https://
#' to http:// if Jolpica suffers errors, and will automatically retry up to 5
#' times by each protocol
#'
#' Note in 2024 this replaced the deprecated Ergast API. Much of the historical data
#' is duplicated in Jolpica
#'
#' @param url the Jolpica URL tail to get from the API (for example,
#' `"{season}/circuits.json?limit=40"` is called from `load_circuits()`).
#' @keywords internal
#' @return the result of `jsonlite::fromJSON` called on Jolpica's return content.
#' Further processing is performed by specific functions
get_jolpica_content <- function(url) {
# Function Code

# note:
# Throttles at 200 req/hr requested.
# Caches requests at option = 'f1dataR.cache' location, if not 'current', 'last', or 'latest' result requested
# Automatically retries request up to 5 times. Back-off provided in httr2 documentation
# Automatically retries at http if https fails after retries.


jolpica_raw <- httr2::request("https://api.jolpi.ca/ergast/f1/") %>%
httr2::req_url_path_append(url) %>%
httr2::req_retry(max_tries = 10, backoff = function(x) runif(1, 1, 2^x)) %>%
httr2::req_user_agent(glue::glue("f1dataR/{ver}", ver = utils::installed.packages()["f1dataR", "Version"])) %>%
httr2::req_throttle(4 / 1) %>%
httr2::req_error(is_error = ~FALSE)

jolpica_res <- NULL

tryCatch(
{
jolpica_res <- jolpica_raw %>%
httr2::req_perform()
},
error = function(e) {
cli::cli_alert_danger(glue::glue("f1dataR: Error getting data from Jolpica:\n{e}", e = e))
}
)

# Restart retries to Jolpica with http (instead of https)
# No testing penalty for Jolpica functioning correct
# nocov start
if (is.null(jolpica_res) || httr2::resp_is_error(jolpica_res) || httr2::resp_body_string(jolpica_res) == "Unable to select database") {
cli::cli_alert_warning("Failure at Jolpica with https:// connection. Retrying as http://.")
tryCatch(
{
jolpica_res <- jolpica_raw %>%
httr2::req_url("http://api.jolpi.ca/ergast/f1/") %>%
httr2::req_url_path_append(url) %>%
httr2::req_perform()
},
error = function(e) {
cli::cli_alert_danger(glue::glue("f1dataR: Error getting data from Jolpica:\n{e}", e = e))
}
)
}

if (is.null(jolpica_res)) {
cli::cli_alert_danger("Couldn't connect to Jolpica to retrieve data.")
return(NULL)
}

if (httr2::resp_is_error(jolpica_res)) {
cli::cli_alert_danger(glue::glue("Error getting Jolpica data, http status code {code}.\n{msg}",
code = httr2::resp_status(jolpica_res),
msg = httr2::resp_status_desc(jolpica_res)
))
return(NULL)
}

if (httr2::resp_body_string(jolpica_res) == "Unable to select database") {
cli::cli_alert_danger("Jolpica is having database trouble. Please try again at a later time.")
return(NULL)
}
# nocov end

# else must be ok
return(jsonlite::fromJSON(httr2::resp_body_string(jolpica_res)))
}


#' Get Current Season
#'
#' @description Determines current season by System Date. Note returns the season prior to the current year
Expand Down
6 changes: 3 additions & 3 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ withr::local_timezone("UTC")

# f1dataR <img src='man/figures/logo.png' align="right" width="25%" min-width="120px"/>

An R package to access Formula 1 Data from the Ergast API and the official F1 data stream via the FastF1 Python library.
An R package to access Formula 1 Data from the Jolpica API (formerly Ergast) and the official F1 data stream via the FastF1 Python library.

<!-- badges: start -->

Expand Down Expand Up @@ -60,10 +60,10 @@ library(f1dataR)

Data is pulled from:

* [Ergast API](https://ergast.com/mrd/)
* [Jolpica F1 API](https://api.jolpi.ca/ergast/)
* [F1 Data Stream](https://www.formula1.com/en/timing/f1-live) via the [Fast F1 python library](https://docs.fastf1.dev/index.html)

Note the Ergast Motor Racing Database API will be shutting down at the end of 2024. When a new data source is identified the package will be migrated to that source.
Note the Ergast Motor Racing Database API will be shutting down at the end of 2024. A new data source (Jolpica-F1 project) was identified and implemented.

## Functions

Expand Down
Loading