The Problem

Clients that need granular Formula 1 telemetry must stitch together disparate web‑scrapers, live‑timing feeds, and ad‑hoc CSV dumps. Maintaining that pipeline is time‑consuming, error‑prone, and repeats the same parsing logic across projects.

What This Does

openf1 supplies a single Python‑based service that ingests both historic F1 data (scraped from the official site) and live‑timing streams, normalises them, stores the results in MongoDB, and exposes a Flask API for downstream consumption.

  • Historical scraping lives in src/openf1/services/f1_scraping/ – functions such as download_page (src/openf1/services/f1_scraping/util.py) fetch HTML, while session_result.py and starting_grid.py parse it into Mongo documents.
  • Live‑timing ingestion resides under src/openf1/services/ingestor_livetiming/. The real‑time entry point is src/openf1/services/ingestor_livetiming/real_time/app.py (Flask app that consumes MQTT topics). A historical back‑fill mode is provided by src/openf1/services/ingestor_livetiming/historical/main.py.
  • Query access is implemented by src/openf1/services/query_api/app.py, a Flask server that reads from Mongo and returns JSON/CSV.

All code is pure Python; Docker support (Dockerfile, docker‑compose.yml) enables containerised deployment.

How It Is Wired

Execution begins at the Flask entry points:

  • Real‑time ingest – app.py creates the MQTT client, registers callbacks, then calls process_message. The call chain is: process_message → to_datetime (7×), process_message → _update_lap (5×), process_message → _update_stint (4×). Each update eventually calls upsert_data_sync in src/openf1/util/db.py, which writes to Mongo (collection.bulk_write).
  • Historical ingest – src/openf1/services/ingestor_livetiming/historical/main.py runs ingest_season. It fetches schedule data via get_schedule (network call in src/openf1/services/f1_scraping/schedule.py), then iterates topics, calling _get_topic_content → requests.get → insert_data_async.
  • API server – src/openf1/services/query_api/app.py defines endpoint. A request hits _process_request, which parses the path (_parse_path), builds a Mongo query (query_params.py), and finally calls get_documents in src/openf1/util/db.py. The response may also request a favicon via _get_favicon (outbound HTTP).

Core utilities that see the most traffic are src/openf1/util/db.py (15 functions, DB I/O) and src/openf1/util/misc.py (helpers like join_url). The import graph shows no circular dependencies, so changes to a hub file (e.g., objects.py with 16 functions) have a predictable blast radius.

How To Use It

# Clone the repo
git clone https://github.com/moses-y/openf1.git
cd openf1

# Install in editable mode
pip install -e .

# Set Mongo connection (required for all services)
export MONGO_CONNECTION_STRING="mongodb://localhost:27017"

# Run the historical scraper (example for 2023 season)
python -m src.openf1.services.ingestor_livetiming.historical.main ingest_season 2023

# Start the live‑timing ingest
python -m src.openf1.services.ingestor_livetiming.real_time.app

# Launch the query API
python -m src.openf1.services.query_api.app

Docker alternative (build and start all three services) is defined in docker-compose.yml; the Dockerfile builds the Python environment.

Real‑World Use

A data‑science team can schedule ingest_season nightly to back‑fill a MongoDB cache, run the live‑timing ingest continuously during race weekends, and point their analytics notebooks at http://localhost:8000/v1/laps?... to retrieve clean lap‑by‑lap telemetry without writing custom scrapers.

Code Health & Issues

  • High – GitHub Actions pinned to tags only (JamesIves/github-pages-deploy-action@v4). Replace with commit SHA.
  • High – No test suite; repository contains 47 source files but zero tests. Add unit/integration tests for each public entry point.
  • High – No lockfile for Python dependencies (pyproject.toml only). Generate and commit poetry.lock or requirements.txt with hashes.
  • High – Flask CORS is configured with allow_origins=["*"] together with credentials. Restrict to explicit origins.
  • Medium – GitHub token permissions are not declared; add permissions: block.
  • Medium – Dependabot/Renovate not configured; add .github/dependabot.yml.
  • Medium – Docker base image python:3.10-slim is unpinned; use digest.
  • Medium – No CI step for dependency vulnerability scanning; add dependency-review-action or osv-scanner.
  • Medium – Outbound requests.get calls lack timeouts; pass timeout=.
  • Medium – actions/checkout keeps credentials; set persist-credentials: false.

Additional observations: the repository includes a CI workflow (.github/workflows/tests.yml) but no test files, and the license file is present. No secrets are committed.

The Bottom Line

openf1 delivers a functional end‑to‑end pipeline for F1 data ingestion and API delivery, with clear separation between scraping, live‑timing, and query layers. However, the lack of tests, an unlocked dependency set, and several security‑related CI misconfigurations make it unsuitable for production without remediation. It is a solid starting point for internal tooling or research prototypes, provided the team adds test coverage and tightens the CI/CD hygiene.