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, 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

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.2.0.tar.gz (59.3 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.2.0-py3-none-any.whl (48.4 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for pymotogp-0.2.0.tar.gz
Algorithm Hash digest
SHA256 231d8bf47d0045b5dd76259d57ed0938a326564eed2cc2a67e11e0297a2e91e8
MD5 ca973166bb9a73cc1b94017e704aee13
BLAKE2b-256 556f6dabcab9a3bf02db1866d22a6121d81769cb9f67803f745d8ce1bebb15f5

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymotogp-0.2.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.2.0-py3-none-any.whl.

File metadata

  • Download URL: pymotogp-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 48.4 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.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f9509dcab7d8b70fa5842ca6d1d71813864924e26b3b1b66768471adcf043702
MD5 0e665878f0a3e6988a1d1a037c582b9b
BLAKE2b-256 fd6e6a74a679c2807d240ddc07f17e3834d89ceef3af0979cf698287a8f00ee8

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymotogp-0.2.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