Skip to main content

Python client for the Jolpica F1 API (Ergast-compatible JSON)

Project description

BaseF1

Python package basef1 — a small, synchronous client for the Jolpica F1 API. The API serves Ergast-compatible JSON at https://api.jolpi.ca/ergast/f1/ so you can query historical and current Formula 1 data (seasons, races, results, drivers, constructors, standings, laps, pit stops, and more) without hand-crafting URLs for the common cases.

This repository is a third-party HTTP wrapper. It is not the Jolpica server itself; see the disclaimer below.


Disclaimer

  • Not affiliated with the Jolpica F1 project, the former Ergast service, Formula 1, the FIA, or any team. This client is an independent convenience library.
  • Data and availability come from api.jolpi.ca. Accuracy, completeness, schema changes, downtime, and rate limits are controlled by the API operators, not by this package. Do not rely on this client for safety-critical or compliance-critical systems.
  • Trademarks such as “Formula 1” and related marks belong to their respective owners. This project uses publicly documented HTTP endpoints only.
  • No warranty: the software is provided “as is” (see LICENSE). You are responsible for complying with the API’s terms of use and for respectful request volumes (caching, backoff, and spacing out bulk scripts).

Features

  • Sync HTTP via httpx, with optional custom httpx.Client for tests or proxies.
  • Chainable queries (F1Query) that mirror Jolpica’s path-based filters: season, round, circuit, constructor, driver, grid slot, finishing status, then a terminal resource (races, results, driver_standings, etc.).
  • Pagination helpers: limit (1–100) and offset validated before requests.
  • ApiResponse: raw JSON, MRData access, typed pagination strings, and get_data() to return the primary data table without hard-coding keys like RaceTable / SeasonTable.
  • Typed domain models (basef1.domain): dataclass hierarchy rooted at JolpicaModel with to_dict() for JSON-shaped serialization; parse_api_response() / ApiResponse.parse() return a ParsedApiResponse with table-specific datas (RaceTable, nested RaceCircuitLocation, results, standings rows, etc.). Unknown *Table keys fall back to RawTable.
  • In-memory cache (TTLCache): enabled by default (5-minute TTL, 256 entries) so identical GETs reuse responses and stay under rate limits. Pass cache=False to disable, or cache_ttl= / cache= to customize (see below).
  • Explicit errors via JolpicaHTTPError for non-2xx responses and malformed JSON.
  • Runnable examples under examples/ against the live API (use sparingly to avoid throttling).

Requirements

  • Python 3.10+
  • httpx (installed automatically with the package)

Installation

From a clone of this repository:

pip install -e .

For local development (tests and Ruff):

pip install -e ".[dev]"

Quick start

from basef1 import BaseF1Client
from basef1.domain.tables import SeasonTable

with BaseF1Client() as client:
    seasons = client.get_seasons(limit=100)
    table = seasons.get_data()
    rows = table.get("Seasons") or []
    print(seasons.pagination.total_int, len(rows))

    parsed = seasons.parse()
    assert isinstance(parsed.data, SeasonTable)
    print(parsed.data.seasons[0].season)

Always prefer a context manager (with BaseF1Client() as client:) so the internal httpx.Client is closed when you are done. If you construct BaseF1Client() without with, call client.close() when finished (unless you passed your own httpx.Client and manage its lifecycle yourself).


Core concepts

Base URL and client

BaseF1Client defaults to https://api.jolpi.ca/ergast/f1. Override with base_url=... for mirrors or tests. timeout is forwarded to httpx when the library creates the client. HTTP responses are cached in memory by default (see Request caching); use cache=False when you need every request to hit the network (for example some tests).

query() and path filters

client.query() returns a fresh F1Query. Fluent methods append URL segments; order matters and must follow the Jolpica / Ergast MRD style paths (same as calling the REST API manually).

Method Path effect
season(year) /{year}/ or current
round(n) /{round}/ — must come immediately after season(...) (year or current)
circuit(id) /circuits/{id}/
constructor(id) /constructors/{id}/
driver(id) /drivers/{id}/
grid(position) /grid/{position}/
with_status(id) /status/{id}/ (finishing status filter; avoids clashing with the get_statuses() resource)

Terminal resources (one GET each)

Call these at the end of the chain (each returns an ApiResponse):

seasons, races, results, qualifying, sprint, drivers, constructors, circuits, driver_standings, constructor_standings, laps, pitstops, statuses.

Shorthand on the client: client.get_seasons() and client.get_statuses() are equivalent to client.query().get_seasons() and client.query().get_statuses().

Pagination

Every terminal accepts limit= and offset=. The API defaults to 30 rows per page; maximum limit is 100. Invalid values raise ValueError before any network I/O.

ApiResponse

Member Purpose
.raw Full parsed JSON dict
.mrdata The MRData object (meta + one *Table data)
.pagination Pagination with limit, offset, total (strings from API) and total_int where parseable
.get_data() Returns the primary data dict (e.g. race table body) using MRDATA_TABLE_PRIORITY and a *Table fallback; {} if none
.parse() Builds a ParsedApiResponse: kind, pagination, and a typed data (or RawTable if the table is unknown)

Exported constants for advanced use: MRDATA_METADATA_KEYS, MRDATA_TABLE_KEYS, MRDATA_TABLE_PRIORITY.

Typed models and serialization

Use parse_api_response(resp) or resp.parse() after any terminal query. Inspect envelope.kind, then narrow envelope.data (for example RaceTable, SeasonTable, StandingsTable). Nested objects (Driver, Circuit, RaceResult, …) all subclass JolpicaModel and implement to_dict() (omit None fields; JSON keys use Jolpica camelCase via dataclass field metadata where needed).

Typed models mirror documented fields; new API properties may require library updates.

Model taxonomy (MRData table to Python types)

MRData *Table key data class Main row / nested types
RaceTable RaceTable races: list[Race]; each Race has `circuit: Circuit
RaceResultDriver, Constructor, TimeElement, FastestLapAverageSpeed
QualifyingResultDriver, Constructor; LapRowLapTimingEntry; PitStopRow
SeasonTable SeasonTable seasons: list[Season]
DriverTable DriverTable drivers: list[Driver]
ConstructorTable ConstructorTable constructors: list[Constructor]
CircuitTable CircuitTable circuits: list[Circuit]; each Circuit has `location: Location
StatusTable StatusTable statuses: list[StatusRow]
StandingsTable StandingsTable standings_lists: list[StandingsList]driver_standings / constructor_standings rows with nested Driver / Constructor

Import concrete types from basef1.domain or the package root: from basef1 import Race, Circuit, Location.

Request caching

By default the client keeps a TTLCache (300 second TTL via DEFAULT_CACHE_TTL_SECONDS, 256 entries, LRU eviction). Duplicate same URL and query parameters within the TTL window only performs one HTTP GET.

Disable caching entirely:

with BaseF1Client(cache=False) as client:
    ...

Customize TTL or inject your own cache (do not pass both cache= and cache_ttl=):

from basef1 import BaseF1Client

with BaseF1Client(cache_ttl=600.0, cache_max_entries=512) as client:
    client.get_seasons(limit=30, offset=0)
    client.get_seasons(limit=30, offset=0)  # cache hit if within TTL

from basef1 import TTLCache

cache = TTLCache(max_entries=64, ttl_seconds=120.0)
with BaseF1Client(cache=cache) as client:
    ...

Caching stores a deep copy of JSON datas; mutating response.raw does not corrupt the cache entry.

Errors

  • JolpicaHTTPError: HTTP status ≥ 400, or invalid JSON body, or transport failures (see .status_code and .body).
  • ValueError: invalid limit / offset, or round() used without a valid preceding season().

Standings and Jolpica-specific rules

Driver and constructor standings require a season in the path for Jolpica. Other nuances (e.g. differences vs legacy Ergast) are documented upstream: Ergast differences.


Use case examples

These snippets assume from basef1 import BaseF1Client and with BaseF1Client() as client:.

List every championship season (paginate with offset):

page = client.get_seasons(limit=100, offset=0)
seasons = page.get_data().get("Seasons") or []

All races in a calendar year:

races = client.query().season(2024).get_races(limit=100)
names = [r.get("raceName") for r in races.get_data().get("Races") or [] if isinstance(r, dict)]

Full result sheet for one round:

resp = client.query().season(2024).round(1).get_results(limit=100)
race = (resp.get_data().get("Races") or [None])[0]
results = race.get("Results") if isinstance(race, dict) else None

Races at a circuit, or for a constructor:

monza = client.query().circuit("monza").get_races(limit=50)
ferrari = client.query().constructor("ferrari").get_races(limit=50)

One driver’s results for a season (season before driver in the chain):

resp = client.query().season(2024).driver("hamilton").get_results(limit=50)

Qualifying and sprint entries for a season:

q = client.query().season(2024)
quali = q.get_qualifying(limit=100)
sprint = q.get_sprint(limit=100)

Championship tables (season required):

q = client.query().season(2024)
drivers = q.get_driver_standings(limit=100)
teams = q.get_constructor_standings(limit=100)

Lap timing and pit stops (season + round required):

q = client.query().season(2024).round(1)
laps = q.get_laps(limit=100)
stops = q.get_pitstops(limit=100)

Finishing status catalogue vs filtering races by status id:

catalogue = client.get_statuses(limit=100)
filtered = client.query().with_status(1).get_races(limit=50)

“Current” season and “next” / “last” round keywords:

next_race = client.query().season("current").round("next").get_races(limit=1)
last_results = client.query().season("current").round("last").get_results(limit=20)

Custom httpx.Client (testing, retries, proxies):

import httpx
from basef1 import BaseF1Client

with httpx.Client(timeout=60.0) as http:
    client = BaseF1Client(client=http, cache=False)  # optional: disable cache in tests
    client.query().get_circuits(limit=5)

Runnable scripts

The examples/ directory contains small programs that hit the live API. Run them from the repository root after pip install -e .. Do not hammer all scripts in a tight loop or you may receive HTTP 429; add delays or run selectively.

Script Use case
examples/01_global_seasons.py Global seasons (client.get_seasons())
examples/02_global_lists.py Global races, drivers, constructors, circuits
examples/03_season_races.py Calendar for one season
examples/04_season_round_results.py Results for season + round
examples/05_season_qualifying_and_sprint.py Qualifying and sprint
examples/06_circuit_filter_races.py Circuit filter + races
examples/07_constructor_filter_races.py Constructor filter + races
examples/08_driver_season_results.py Season + driver + results
examples/09_grid_filter_races.py Grid slot filter + races
examples/10_status_filter_races.py with_status + races
examples/11_status_catalogue.py Status catalogue (get_statuses())
examples/12_standings.py Driver and constructor standings
examples/13_laps_and_pitstops.py Laps and pit stops
examples/14_pagination_offset.py limit / offset
examples/15_current_season_next_round.py current, next, last
python examples/01_global_seasons.py

Scripts that unpack rows use get_data() so they do not depend on hard-coded RaceTable / SeasonTable keys.


Development

pip install -e ".[dev]"
ruff check src tests examples
pytest

Further reading


License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

basef1-0.1.0.tar.gz (20.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

basef1-0.1.0-py3-none-any.whl (20.7 kB view details)

Uploaded Python 3

File details

Details for the file basef1-0.1.0.tar.gz.

File metadata

  • Download URL: basef1-0.1.0.tar.gz
  • Upload date:
  • Size: 20.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for basef1-0.1.0.tar.gz
Algorithm Hash digest
SHA256 19da3963247e2cf7ddad27261a4ced6b71506213cc3c67e810dfe1020a1a3983
MD5 aafca882047eeff0844c8b1912b942f6
BLAKE2b-256 f232bec0e5a4d5e0b72d8a283d33ceb53a829014fb9a689982b31aa3cdd66657

See more details on using hashes here.

Provenance

The following attestation bundles were made for basef1-0.1.0.tar.gz:

Publisher: publish-pypi.yml on GoktugOcal/BaseF1

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file basef1-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: basef1-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 20.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for basef1-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b1495ec72e7a7a12f7e9c4ad2083c2ce4f78478cd83c0d7ff7582d6a5fea7d51
MD5 6c1bf78d6dc81be86a04a7ec98a5e8e6
BLAKE2b-256 b3acbd327a606c4b8a422dc785663b2cf96a374dda05954841f024c96ad8f4a1

See more details on using hashes here.

Provenance

The following attestation bundles were made for basef1-0.1.0-py3-none-any.whl:

Publisher: publish-pypi.yml on GoktugOcal/BaseF1

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page