A production-grade Python SDK for the OpenF1 API, providing easy access to real-time and historical Formula 1 data.
- Features
- Installation
- Quick Start
- Filtering Data
- Available Endpoints
- Endpoint Methods
- Configuration
- Error Handling
- Logging
- Data Models
- Examples
- Future Enhancements
- Contributing
- License
- Acknowledgements
- 🔌 Full API Coverage — Access all 16 OpenF1 endpoints including telemetry, lap times, positions, weather, and more
- 🔒 Type Safety — Fully typed with Pydantic v2 models and comprehensive type hints
- 🎯 Pythonic Filtering — Use dictionaries with comparison operators for flexible queries
- 🔐 Authentication Support — OAuth2 password flow for real-time data access
⚠️ Robust Error Handling — Comprehensive exception hierarchy with detailed error information- 🚀 Production Ready — Automatic retries, configurable timeouts, and logging support
- 📊 Multiple Formats — JSON and CSV response support
pip install OpenF1-python-clientOr install from source:
git clone https://github.com/rhtnr/OpenF1-python-client.git
cd OpenF1-python-client
pip install -e .For development:
pip install -e ".[dev]"Historical data is available without authentication:
from openf1_client import OpenF1Client
# Create a client
client = OpenF1Client()
# Get lap data for a specific driver
laps = client.laps.list(
session_key=9161,
driver_number=63,
)
for lap in laps:
print(f"Lap {lap.lap_number}: {lap.lap_duration}s")
# Don't forget to close the client when done
client.close()from openf1_client import OpenF1Client
with OpenF1Client() as client:
# Get driver information
drivers = client.drivers.list(session_key=9158)
for driver in drivers:
print(f"{driver.name_acronym}: {driver.full_name} - {driver.team_name}")For real-time data and higher rate limits, authenticate with your OpenF1 credentials:
from openf1_client import OpenF1Client
client = OpenF1Client(
username="your_email@example.com",
password="your_password",
)
# Access real-time data
latest_session = client.sessions.first(session_key="latest")
print(f"Current session: {latest_session.session_name}")Or use a pre-existing access token:
client = OpenF1Client(access_token="your_access_token")OpenF1 supports rich filtering with comparison operators. The client provides a Pythonic interface:
# Filter by exact values
laps = client.laps.list(
session_key=9161,
driver_number=63,
lap_number=8,
)Use dictionaries with operator keys for comparisons:
# Speed >= 315 km/h
fast_telemetry = client.car_data.list(
session_key=9159,
driver_number=55,
speed={">=": 315},
)
# Close intervals (< 0.5 seconds)
close_battles = client.intervals.list(
session_key=9161,
interval={"<": 0.5},
)# Date range
location_data = client.location.list(
session_key=9161,
driver_number=81,
date={
">": "2023-09-16T13:03:35.200",
"<": "2023-09-16T13:03:35.800",
},
)
# Lap range
stint_laps = client.laps.list(
session_key=9161,
driver_number=1,
lap_number={">=": 10, "<=": 20},
)For more complex filters, use the FilterBuilder helper:
from openf1_client import OpenF1Client, FilterBuilder
with OpenF1Client() as client:
filters = (
FilterBuilder()
.eq("session_key", 9161)
.eq("driver_number", 1)
.gte("speed", 300)
.lt("lap_number", 10)
.build()
)
car_data = client.car_data.list(**filters)| Endpoint | Description | Example |
|---|---|---|
🏎️ car_data |
Car telemetry (~3.7 Hz) | client.car_data.list(...) |
👤 drivers |
Driver information | client.drivers.list(...) |
⏱️ intervals |
Gap data (~4s updates) | client.intervals.list(...) |
🔄 laps |
Lap timing data | client.laps.list(...) |
📍 location |
Car positions (~3.7 Hz) | client.location.list(...) |
🏁 meetings |
Grand Prix metadata | client.meetings.list(...) |
🔀 overtakes |
Passing events (beta) | client.overtakes.list(...) |
🛞 pit |
Pit stop activity | client.pit.list(...) |
📊 position |
Track positions | client.position.list(...) |
🚩 race_control |
Flags, incidents | client.race_control.list(...) |
📅 sessions |
Session data | client.sessions.list(...) |
🏆 session_result |
Final results (beta) | client.session_result.list(...) |
🚦 starting_grid |
Grid positions (beta) | client.starting_grid.list(...) |
🔧 stints |
Stint/tyre data | client.stints.list(...) |
📻 team_radio |
Radio communications | client.team_radio.list(...) |
🌤️ weather |
Weather data (~1 min) | client.weather.list(...) |
Each endpoint provides several methods:
# List all matching records
laps = client.laps.list(session_key=9161, driver_number=1)
# Get first matching record (or None)
lap = client.laps.first(session_key=9161, driver_number=1, lap_number=1)
# Get raw data (dict) without model parsing
raw_data = client.laps.list_raw(session_key=9161)
# Get CSV format
csv_data = client.laps.list_csv(session_key=9161)
# Count matching records
count = client.laps.count(session_key=9161, driver_number=1)Many endpoints also provide convenience methods:
# 🔄 Laps
fastest_lap = client.laps.get_fastest_lap(session_key=9161)
flying_laps = client.laps.get_flying_laps(session_key=9161, driver_number=1)
# 📅 Sessions
races = client.sessions.get_races(year=2023)
latest = client.sessions.get_latest()
# 🔧 Stints
strategy = client.stints.get_tyre_strategy(session_key=9161, driver_number=1)
# 🌤️ Weather
rain = client.weather.get_rain_periods(session_key=9161)from openf1_client import OpenF1Client
client = OpenF1Client(
# 🔐 Authentication
username="user@example.com",
password="secret",
# Or use a token directly
# access_token="your_token",
# 🌐 Connection settings
timeout=60.0, # Request timeout in seconds
# timeout=(5.0, 30.0), # (connect, read) timeouts
max_retries=5, # Retry failed requests
# 📄 Response format
default_format="json", # "json" or "csv"
# 🔒 SSL/TLS
verify_ssl=True,
)The client provides a comprehensive exception hierarchy:
from openf1_client import (
OpenF1Client,
OpenF1Error, # Base exception
OpenF1ConfigError, # Invalid configuration
OpenF1TransportError, # Network errors
OpenF1APIError, # API errors (non-2xx)
OpenF1AuthError, # 401/403 errors
OpenF1RateLimitError, # 429 errors
OpenF1NotFoundError, # 404 errors
OpenF1ServerError, # 5xx errors
OpenF1TimeoutError, # Request timeout
OpenF1ValidationError, # Data validation
)
try:
with OpenF1Client() as client:
laps = client.laps.list(session_key=99999)
except OpenF1NotFoundError as e:
print(f"❌ Session not found: {e}")
except OpenF1RateLimitError as e:
print(f"⏳ Rate limited. Retry after: {e.retry_after}s")
except OpenF1APIError as e:
print(f"⚠️ API error {e.status_code}: {e.message}")
except OpenF1Error as e:
print(f"💥 Client error: {e}")Enable debug logging to see HTTP requests and responses:
from openf1_client import setup_logging
import logging
# Enable debug logging
setup_logging(logging.DEBUG)
# Or configure manually
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("openf1_client")
logger.setLevel(logging.DEBUG)All responses are parsed into Pydantic models with full type annotations:
from openf1_client import OpenF1Client, Lap, Driver
with OpenF1Client() as client:
lap: Lap = client.laps.first(session_key=9161, driver_number=1, lap_number=1)
if lap:
print(f"Lap duration: {lap.lap_duration}")
print(f"Sector 1: {lap.duration_sector_1}")
print(f"Sector 2: {lap.duration_sector_2}")
print(f"Sector 3: {lap.duration_sector_3}")
print(f"Speed trap: {lap.st_speed} km/h")from openf1_client import OpenF1Client
with OpenF1Client() as client:
# Get session info
session = client.sessions.first(session_key=9161)
print(f"🏁 Session: {session.session_name} - {session.country_name}")
# Get all drivers
drivers = client.drivers.list(session_key=9161)
for driver in drivers:
# Get their fastest lap
fastest = client.laps.get_fastest_lap(
session_key=9161,
driver_number=driver.driver_number,
)
# Get pit stops
pit_count = client.pit.count_pit_stops(
session_key=9161,
driver_number=driver.driver_number,
)
# Get tyre strategy
strategy = client.stints.get_tyre_strategy(
session_key=9161,
driver_number=driver.driver_number,
)
print(f"🏎️ {driver.name_acronym}: "
f"Fastest: {fastest.lap_duration if fastest else 'N/A'}s, "
f"Stops: {pit_count}, "
f"Tyres: {' → '.join(strategy)}")from openf1_client import OpenF1Client
with OpenF1Client() as client:
weather_data = client.weather.list(session_key=9161)
for w in weather_data:
rain_emoji = "🌧️" if w.rainfall else "☀️"
print(f"{rain_emoji} {w.date}")
print(f" 🌡️ Air: {w.air_temperature}°C")
print(f" 🛣️ Track: {w.track_temperature}°C")
print(f" 💧 Humidity: {w.humidity}%")from openf1_client import OpenF1Client
from collections import Counter
with OpenF1Client() as client:
overtakes = client.overtakes.list(session_key=9161)
print(f"🔀 Total overtakes: {len(overtakes)}")
# Get drivers with most overtakes
overtake_counts = Counter(o.driver_number for o in overtakes)
print("\n🏆 Top overtakers:")
for driver_num, count in overtake_counts.most_common(5):
driver = client.drivers.first(
session_key=9161,
driver_number=driver_num,
)
print(f" {driver.name_acronym}: {count} overtakes")The client is designed to be easily extended. Planned additions:
# Future async client (design ready, not yet implemented)
from openf1_client import AsyncOpenF1Client
async with AsyncOpenF1Client() as client:
laps = await client.laps.list(session_key=9161)# Future streaming support (design ready)
# Via MQTT or WebSockets for live data
async for telemetry in client.car_data.stream(driver_number=1):
print(f"🏎️ Speed: {telemetry.speed} km/h")Contributions are welcome! Please read our Contributing Guide for details.
# Install development dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Run type checking
mypy src/openf1_client
# Format code
black src tests
# Lint code
ruff check src testsThis project is licensed under the MIT License - see the LICENSE file for details.
- 🏎️ OpenF1 for providing the excellent Formula 1 data API
- ❤️ The Formula 1 community for their passion and support
If you find this project useful, consider supporting its development:
Made with ❤️ for the F1 community