Skip to main content

Fast, pythonic access to MotoGP data — inspired by FastF1

Project description

PyMotoGP

CI Python License: MIT

Fast, pythonic access to MotoGP data — inspired by FastF1.

PyMotoGP gives you session-centric access to qualifying, sprint, and race data from the official PulseLive API and DORNA timing PDFs. Load a session, get a pandas DataFrame of laps, plot pace, run analytics, predict race outcomes.

import motogp

session = motogp.load(2024, 'qatar', 'Q2')
print(session.best_lap.rider_name)         # 'Jorge MARTIN'
df = session.laps.to_dataframe()           # full lap data, sectors, top speeds
session.plot.lap_times()                   # matplotlib figure
session.analysis.gap_to_pole()             # gap analysis

Install

pip install pymotogp

Requires Python 3.10+. Core dependencies: requests, pandas, pdfplumber, matplotlib.


Quickstart

Load a session

import motogp

# Year + event + session label. Event matches by name, country, or short code.
session = motogp.load(2024, 'cataluña', 'Q2')

Supported session labels: Q1, Q2, qualifying, FP1, FP2, practice, sprint, warm-up, race.

Stay current with the season

motogp.get_event_schedule(2026)     # calendar DataFrame with a `finished` flag
motogp.get_event_schedule()         # None → current season

# Sync every finished GP of the season into the local cache — laps,
# classification, and Analysis PDFs. Safe to re-run after each race weekend;
# already-cached sessions are skipped.
report = motogp.update(2026)

# Or narrow it down:
motogp.update(2026, events=['assen', 'GER'], sessions=['Q2', 'SPR', 'RAC'])

update() returns a per-session report (riders, laps, status). Sessions from a race weekend that just ended may show classification-only or empty until DORNA publishes the Analysis PDF — rerun later to fill them in.

Automated updates (no manual syncing)

python -m motogp [year] runs the same sync from the command line, so any scheduler can keep your data current. On macOS use the bundled launchd agent (preferred over cron — it runs missed jobs when the Mac wakes):

cp scripts/com.pymotogp.update.plist ~/Library/LaunchAgents/
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.pymotogp.update.plist

It syncs every Monday at 09:00 and logs to ~/.motogp/update.log. Edit the plist to change the python path, repo location, or schedule. On Linux, the cron equivalent is: 0 9 * * 1 cd /path/to/PyMotoGP && python3 -m motogp.

Prefer to keep your machine out of it entirely? The repo ships a scheduled GitHub Actions workflow (.github/workflows/update-data.yml) that runs the same sync on GitHub's runners every Monday 08:00 UTC and commits per-session JSON exports to data/<year>/ (python -m motogp --export data). It can also be triggered manually from the Actions tab.

Inspect laps

df = session.laps.to_dataframe()
# Columns: rider_name, lap_number, lap_time_ms, sector1_ms ... sector4_ms,
#          top_speed, run_number, front_tyre, rear_tyre,
#          front_tyre_age, rear_tyre_age, is_valid, is_cancelled, is_pit, is_best

session.best_lap                  # fastest valid lap
session.riders                    # list of rider names
session.classification            # official position list

Each lap carries the tyre it was set on, parsed from the run headers in the Analysis PDF: front_tyre / rear_tyre are the compounds (e.g. Slick-Medium, Wet-Soft), and front_tyre_age / rear_tyre_age are laps used at the start of the stint (0 = new). run_number groups laps into stints, so you can, for example, compare race pace by compound or isolate a flag-to-flag tyre change. Tyre data is absent (None) for qualifying sessions loaded from the pre-scraped JSON cache; load with prefer="api" to get it from the PDF.

Plot

fig = session.plot.lap_times(riders=['Bagnaia', 'Martin'])
fig.savefig('out.png')

session.plot.pace_distribution()      # boxplot per rider
session.plot.sector_comparison()      # grouped bars
session.plot.compare('Bagnaia', 'Martin')  # 2-panel comparison

All plotting methods return a matplotlib.figure.Figure. Cancelled laps, pit laps, and outlaps (>1.03× that rider's own best) are filtered by default, with a secondary cap at 1.10× the field best to handle riders with no flying lap.

Analyze

session.analysis.theoretical_best()       # sum of best sectors per rider
session.analysis.gain_potential('Bagnaia')   # ms left on the table
session.analysis.gap_to_pole()
session.analysis.sector_strength()
session.analysis.consistency_ranking()

Cross-season history

from motogp import HistoricalAnalyzer

hist = HistoricalAnalyzer()
hist.track_evolution('catalunya')         # pole-time progression by year
hist.rider_form('Bagnaia', year=2024)     # per-round qualifying form
hist.team_pace(year=2024)                 # aggregate by team

Predict race pace (transparent baseline)

from motogp import RacePaceEstimator

qual = motogp.load(2024, 'malaysia', 'Q2')
est = RacePaceEstimator()
est.predict(qual, n_laps=20)

The estimator uses a transparent linear model: race_lap(n) = q_best + race_offset + degradation × (n − 1). All assumptions are exposed as parameters. See "Honest limits" below for accuracy data.


Data sources

PyMotoGP uses a two-tier resolution strategy:

  1. Local cache (instant) — if you have a directory of pre-scraped JSON files, set MOTOGP_SCRAPER_OUTPUT to point at it. Hits return instantly.
  2. PulseLive API (live) — falls back to the official MotoGP API: api.motogp.pulselive.com/motogp/v1/. Discovers the session, downloads the official Analysis PDF, parses it with pdfplumber. PDFs are cached at ~/.motogp_pdfs/.

No API key required. Be considerate — the library caches everything.

Environment variables

Variable Default Purpose
MOTOGP_SCRAPER_OUTPUT ~/.motogp/scraper_output Pre-scraped JSON cache
MOTOGP_PDF_CACHE ~/.motogp_pdfs DORNA Analysis PDF cache

Honest limits

PyMotoGP includes a backtest pipeline so you can measure model accuracy instead of trusting it blindly.

from motogp.analysis import RacePaceValidator

v = RacePaceValidator()
df = v.validate_season(2024)
v.summarize(df)

2024 season backtest results (20 GP races validated):

Metric Value
Winner hit rate 30%
Podium overlap (mean of 3) 1.2
Position MAE 3.5 places
Kendall's tau 0.24

Treat RacePaceEstimator.predict() as a directional baseline, not a black-box predictor. The model assumes qualifying pace transfers linearly to race pace with uniform degradation — which is wrong for ~70% of MotoGP races because tire management, race craft, weather, and DNFs aren't captured. Calibrate per-track with est.calibrate_from_race(qual, race) once you have real race data.


Architecture

motogp/
├── core/           # Session, Lap, Sector, Rider data models
├── api/            # PulseLive client + DORNA PDF parser
├── plots/          # Matplotlib-based session plots
├── analysis/       # SessionAnalyzer, HistoricalAnalyzer,
│                   # RacePaceEstimator, RacePaceValidator

Roadmap

  • Phase 1 — PulseLive API wrapper, core data models, caching
  • Phase 2 — Real data integration (scraper cache + PulseLive + PDFs)
  • Phase 3 — Session/historical/race-pace analytics + validation
  • 🚧 Phase 4 — Sphinx docs, VCR-cassette integration tests, PyPI release

Contributing

Issues and PRs welcome. Run tests with:

pip install -e ".[dev]"
pytest

License

MIT

Not affiliated with Dorna Sports or MotoGP™. All data is sourced from publicly available APIs and PDFs for personal and research use.

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

pymotogp-0.3.0.tar.gz (64.1 kB view details)

Uploaded Source

Built Distribution

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

pymotogp-0.3.0-py3-none-any.whl (51.5 kB view details)

Uploaded Python 3

File details

Details for the file pymotogp-0.3.0.tar.gz.

File metadata

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

File hashes

Hashes for pymotogp-0.3.0.tar.gz
Algorithm Hash digest
SHA256 60227cb9d4aa98a3477e827b600e5091d1a992dba3a162724a9eb97f109f50d7
MD5 30d721045d4cbb137d32d61a1ee1769f
BLAKE2b-256 1c7bd867618f20056cd17b61f1c32815e74c13ad4302b0d9955ab374c74b2570

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymotogp-0.3.0.tar.gz:

Publisher: release.yml on tejred213/PyMotoGP

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

File details

Details for the file pymotogp-0.3.0-py3-none-any.whl.

File metadata

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

File hashes

Hashes for pymotogp-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 14dc53132503bc7136d8c412eb354a5dbfe0bd2210ce96226938cd96e36ca0a1
MD5 9f65f876dc3c33f07687dfcb49a0c1e3
BLAKE2b-256 515e0d76a9b43a2983f7f7aab368f0df4f419bf371e2c38d8e3ba69bf0be4a07

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymotogp-0.3.0-py3-none-any.whl:

Publisher: release.yml on tejred213/PyMotoGP

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