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:
fromopenf1_clientimportOpenF1Client# Create a clientclient=OpenF1Client()
# Get lap data for a specific driverlaps=client.laps.list(
session_key=9161,
driver_number=63,
)
forlapinlaps:
print(f"Lap {lap.lap_number}: {lap.lap_duration}s")
# Don't forget to close the client when doneclient.close()fromopenf1_clientimportOpenF1ClientwithOpenF1Client() asclient:
# Get driver informationdrivers=client.drivers.list(session_key=9158)
fordriverindrivers:
print(f"{driver.name_acronym}: {driver.full_name} - {driver.team_name}")For real-time data and higher rate limits, authenticate with your OpenF1 credentials:
fromopenf1_clientimportOpenF1Clientclient=OpenF1Client(
username="your_email@example.com",
password="your_password",
)
# Access real-time datalatest_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 valueslaps=client.laps.list(
session_key=9161,
driver_number=63,
lap_number=8,
)Use dictionaries with operator keys for comparisons:
# Speed >= 315 km/hfast_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 rangelocation_data=client.location.list(
session_key=9161,
driver_number=81,
date={
">": "2023-09-16T13:03:35.200",
"<": "2023-09-16T13:03:35.800",
},
)
# Lap rangestint_laps=client.laps.list(
session_key=9161,
driver_number=1,
lap_number={">=": 10, "<=": 20},
)For more complex filters, use the FilterBuilder helper:
fromopenf1_clientimportOpenF1Client, FilterBuilderwithOpenF1Client() asclient:
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 recordslaps=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 parsingraw_data=client.laps.list_raw(session_key=9161)
# Get CSV formatcsv_data=client.laps.list_csv(session_key=9161)
# Count matching recordscount=client.laps.count(session_key=9161, driver_number=1)Many endpoints also provide convenience methods:
# 🔄 Lapsfastest_lap=client.laps.get_fastest_lap(session_key=9161)
flying_laps=client.laps.get_flying_laps(session_key=9161, driver_number=1)
# 📅 Sessionsraces=client.sessions.get_races(year=2023)
latest=client.sessions.get_latest()
# 🔧 Stintsstrategy=client.stints.get_tyre_strategy(session_key=9161, driver_number=1)
# 🌤️ Weatherrain=client.weather.get_rain_periods(session_key=9161)fromopenf1_clientimportOpenF1Clientclient=OpenF1Client(
# 🔐 Authenticationusername="user@example.com",
password="secret",
# Or use a token directly# access_token="your_token",# 🌐 Connection settingstimeout=60.0, # Request timeout in seconds# timeout=(5.0, 30.0), # (connect, read) timeoutsmax_retries=5, # Retry failed requests# 📄 Response formatdefault_format="json", # "json" or "csv"# 🔒 SSL/TLSverify_ssl=True,
)The client provides a comprehensive exception hierarchy:
fromopenf1_clientimport (
OpenF1Client,
OpenF1Error, # Base exceptionOpenF1ConfigError, # Invalid configurationOpenF1TransportError, # Network errorsOpenF1APIError, # API errors (non-2xx)OpenF1AuthError, # 401/403 errorsOpenF1RateLimitError, # 429 errorsOpenF1NotFoundError, # 404 errorsOpenF1ServerError, # 5xx errorsOpenF1TimeoutError, # Request timeoutOpenF1ValidationError, # Data validation
)
try:
withOpenF1Client() asclient:
laps=client.laps.list(session_key=99999)
exceptOpenF1NotFoundErrorase:
print(f"❌ Session not found: {e}")
exceptOpenF1RateLimitErrorase:
print(f"⏳ Rate limited. Retry after: {e.retry_after}s")
exceptOpenF1APIErrorase:
print(f"⚠️ API error {e.status_code}: {e.message}")
exceptOpenF1Errorase:
print(f"💥 Client error: {e}")Enable debug logging to see HTTP requests and responses:
fromopenf1_clientimportsetup_loggingimportlogging# Enable debug loggingsetup_logging(logging.DEBUG)
# Or configure manuallylogging.basicConfig(level=logging.DEBUG)
logger=logging.getLogger("openf1_client")
logger.setLevel(logging.DEBUG)All responses are parsed into Pydantic models with full type annotations:
fromopenf1_clientimportOpenF1Client, Lap, DriverwithOpenF1Client() asclient:
lap: Lap=client.laps.first(session_key=9161, driver_number=1, lap_number=1)
iflap:
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")fromopenf1_clientimportOpenF1ClientwithOpenF1Client() asclient:
# Get session infosession=client.sessions.first(session_key=9161)
print(f"🏁 Session: {session.session_name} - {session.country_name}")
# Get all driversdrivers=client.drivers.list(session_key=9161)
fordriverindrivers:
# Get their fastest lapfastest=client.laps.get_fastest_lap(
session_key=9161,
driver_number=driver.driver_number,
)
# Get pit stopspit_count=client.pit.count_pit_stops(
session_key=9161,
driver_number=driver.driver_number,
)
# Get tyre strategystrategy=client.stints.get_tyre_strategy(
session_key=9161,
driver_number=driver.driver_number,
)
print(f"🏎️ {driver.name_acronym}: "f"Fastest: {fastest.lap_durationiffastestelse'N/A'}s, "f"Stops: {pit_count}, "f"Tyres: {' → '.join(strategy)}")fromopenf1_clientimportOpenF1ClientwithOpenF1Client() asclient:
weather_data=client.weather.list(session_key=9161)
forwinweather_data:
rain_emoji="🌧️"ifw.rainfallelse"☀️"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}%")fromopenf1_clientimportOpenF1ClientfromcollectionsimportCounterwithOpenF1Client() asclient:
overtakes=client.overtakes.list(session_key=9161)
print(f"🔀 Total overtakes: {len(overtakes)}")
# Get drivers with most overtakesovertake_counts=Counter(o.driver_numberforoinovertakes)
print("\n🏆 Top overtakers:")
fordriver_num, countinovertake_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)fromopenf1_clientimportAsyncOpenF1ClientasyncwithAsyncOpenF1Client() asclient:
laps=awaitclient.laps.list(session_key=9161)# Future streaming support (design ready)# Via MQTT or WebSockets for live dataasyncfortelemetryinclient.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