|
from enum import Enum |
|
from dataclasses import dataclass |
|
from typing import Dict, Set, List, Optional |
|
|
|
api_endpoints = { |
|
"sessions": "sessions?", |
|
"weather": "weather?", |
|
"locations": "locations?", |
|
"drivers": "drivers?", |
|
"intervals": "intervals?", |
|
"laps": "laps?", |
|
"car_data": "car_data?", |
|
"pit": "pit?", |
|
"position": "position?", |
|
"race_control": "race_control?", |
|
"stints": "stints?", |
|
"team_radio": "team_radio?", |
|
} |
|
|
|
class FilterType(Enum): |
|
EQUALITY = "equality" |
|
COMPARISON = "comparison" |
|
|
|
class DataType(Enum): |
|
STRING = "string" |
|
INTEGER = "integer" |
|
DATETIME = "datetime" |
|
BINARY = "binary" |
|
|
|
@dataclass |
|
class FilterSpec: |
|
"""Specification for an API filter parameter""" |
|
name: str |
|
filter_type: FilterType |
|
data_type: DataType |
|
description: str = "" |
|
allowed_values: Optional[List[str]] = None |
|
|
|
def get_query_examples(self) -> List[str]: |
|
"""Generate example query parameters for this filter""" |
|
examples = [] |
|
|
|
if self.data_type == DataType.BINARY: |
|
examples = [f"{self.name}=true", f"{self.name}=false"] |
|
|
|
elif self.filter_type == FilterType.EQUALITY: |
|
if self.allowed_values: |
|
examples = [f"{self.name}={val}" for val in self.allowed_values[:2]] |
|
elif self.data_type == DataType.STRING: |
|
examples = [f"{self.name}=example_value"] |
|
elif self.data_type == DataType.INTEGER: |
|
examples = [f"{self.name}=42"] |
|
elif self.data_type == DataType.DATETIME: |
|
examples = [f"{self.name}=2024-01-01T00:00:00Z", f"{self.name}=2024-01-01T10:30:00Z"] |
|
|
|
elif self.filter_type == FilterType.COMPARISON: |
|
if self.data_type == DataType.INTEGER: |
|
examples = [f"{self.name}>=10", f"{self.name}<100"] |
|
elif self.data_type == DataType.DATETIME: |
|
examples = [f"{self.name}>=2024-01-01T00:00:00Z", f"{self.name}<2024-12-31T00:00:00Z"] |
|
elif self.data_type == DataType.STRING: |
|
examples = [f"{self.name}>M", f"{self.name}<Z"] |
|
|
|
return examples |
|
|
|
def help_text(self) -> str: |
|
"""Generate help text for this filter""" |
|
text = f"Filter: {self.name}\n" |
|
text += f" Type: {self.filter_type.value} ({self.data_type.value})\n" |
|
|
|
if self.description: |
|
text += f" Description: {self.description}\n" |
|
|
|
if self.allowed_values: |
|
text += f" Allowed values: {', '.join(self.allowed_values)}\n" |
|
|
|
examples = self.get_query_examples() |
|
if examples: |
|
text += f" Examples: {', '.join(examples)}" |
|
|
|
return text |
|
|
|
|
|
|
|
class APIEndpointRegistry: |
|
"""Registry for API endpoints and their supported filters""" |
|
|
|
def __init__(self, base_url: str = ""): |
|
self.base_url = base_url |
|
self.endpoints: Dict[str, Set[str]] = {} |
|
self.filters: Dict[str, FilterSpec] = {} |
|
|
|
def define_filter( |
|
self, |
|
name: str, |
|
filter_type: FilterType, |
|
data_type: DataType, |
|
description: str = "", |
|
allowed_values: Optional[List[str]] = None |
|
) -> 'APIEndpointRegistry': |
|
"""Define a filter that can be used by endpoints""" |
|
filter_spec = FilterSpec(name, filter_type, data_type, description, allowed_values) |
|
self.filters[name] = filter_spec |
|
return self |
|
|
|
def register_endpoint(self, endpoint: str, *filter_names: str) -> 'APIEndpointRegistry': |
|
"""Register an API endpoint with its supported filters""" |
|
|
|
|
|
for filter_name in filter_names: |
|
if filter_name not in self.filters: |
|
raise ValueError(f"Filter '{filter_name}' not defined. Use define_filter() first.") |
|
|
|
|
|
self.endpoints[endpoint] = set(filter_names) |
|
|
|
return self |
|
|
|
|
|
|
|
|
|
|
|
def get_endpoint_filters(self, endpoint: str) -> Dict[str, FilterSpec]: |
|
"""Get all filters supported by an endpoint""" |
|
filter_names = self.endpoints.get(endpoint, set()) |
|
return {name: self.filters[name] for name in filter_names} |
|
|
|
def get_filter_help(self, filter_name: str) -> str: |
|
"""Get help text for a specific filter""" |
|
|
|
if filter_name in self.filters: |
|
return self.filters[filter_name].help_text() |
|
return f"Filter '{filter_name}' not found." |
|
|
|
def get_endpoint_help(self, endpoint: str) -> str: |
|
"""Get help text for all filters supported by an endpoint""" |
|
|
|
filters = self.get_endpoint_filters(endpoint) |
|
if not filters: |
|
return f"Endpoint '{endpoint}' has no registered filters." |
|
|
|
help_text = f"API Endpoint: {self.base_url}{endpoint}\n" |
|
help_text += f"Supported filters ({len(filters)}):\n\n" |
|
|
|
for name, spec in sorted(filters.items()): |
|
help_text += spec.help_text() + "\n\n" |
|
|
|
return help_text.strip() |
|
|
|
def list_all_endpoints(self) -> List[str]: |
|
"""Get list of all registered endpoints""" |
|
return sorted(self.endpoints.keys()) |
|
|
|
def list_all_filters(self) -> List[str]: |
|
"""Get list of all defined filters""" |
|
return sorted(self.filters.keys()) |
|
|
|
|
|
|
|
f1_api = APIEndpointRegistry("https://api.openf1.org/v1/") |
|
|
|
|
|
f1_api.define_filter("date", FilterType.COMPARISON, DataType.DATETIME, "The UTC date and time, in ISO 8601 format.") |
|
f1_api.define_filter("driver_number", FilterType.EQUALITY, DataType.INTEGER, "The unique number assigned to an F1 driver") |
|
f1_api.define_filter("meeting_key", FilterType.EQUALITY, DataType.STRING, "The unique identifier for the meeting. Use 'latest' to identify the latest or current meeting.") |
|
f1_api.define_filter("session_key", FilterType.EQUALITY, DataType.STRING, "The unique identifier for the session. Use 'latest' to identify the latest or current session.") |
|
f1_api.define_filter("speed", FilterType.COMPARISON, DataType.INTEGER, "Velocity of the car in km/h.") |
|
f1_api.define_filter("country_code", FilterType.EQUALITY, DataType.STRING, "A code that uniquely identifies the country.") |
|
f1_api.define_filter("first_name", FilterType.EQUALITY, DataType.STRING, "The first name of the driver.") |
|
f1_api.define_filter("last_name", FilterType.EQUALITY, DataType.STRING, "The last name of the driver.") |
|
f1_api.define_filter("full_name", FilterType.EQUALITY, DataType.STRING, "The full name of the driver.") |
|
f1_api.define_filter("name_acronym", FilterType.EQUALITY, DataType.STRING, "Three-letter acronym of the driver's name.") |
|
f1_api.define_filter("team_name", FilterType.EQUALITY, DataType.STRING, "The name of the driver's team.") |
|
f1_api.define_filter("gap_to_leader", FilterType.COMPARISON, DataType.INTEGER, "The time gap to the race leader in seconds, +1 LAP if lapped, or null for the race leader.") |
|
f1_api.define_filter("interval", FilterType.COMPARISON, DataType.INTEGER, "The time gap to the car ahead in seconds, +1 LAP if lapped, or null for the race leader.") |
|
f1_api.define_filter("date_start", FilterType.COMPARISON, DataType.DATETIME, "The UTC starting date and time, in ISO 8601 format.") |
|
f1_api.define_filter("date_end", FilterType.COMPARISON, DataType.DATETIME, "The UTC ending date and time, in ISO 8601 format.") |
|
f1_api.define_filter("is_pit_out_lap", FilterType.EQUALITY, DataType.BINARY, "A boolean value indicating whether the lap is an out lap from the pit (true if it is, false otherwise).") |
|
f1_api.define_filter("lap_duration", FilterType.COMPARISON, DataType.INTEGER, "The total time taken, in seconds, to complete the entire lap.") |
|
f1_api.define_filter("lap_number", FilterType.EQUALITY, DataType.INTEGER, "The sequential number of the lap within the session (starts at 1).") |
|
f1_api.define_filter("circuit_key", FilterType.EQUALITY, DataType.STRING, "The unique identifier for the circuit where the event takes place.") |
|
f1_api.define_filter("circuit_short_name", FilterType.EQUALITY, DataType.STRING, "The short or common name of the circuit where the event takes place.") |
|
f1_api.define_filter("country_key", FilterType.EQUALITY, DataType.STRING, "The unique identifier for the country where the event takes place.") |
|
f1_api.define_filter("country_name", FilterType.EQUALITY, DataType.STRING, "The name of the country where the event takes place.") |
|
f1_api.define_filter("location", FilterType.EQUALITY, DataType.STRING, "The city or geographical location where the event takes place.") |
|
f1_api.define_filter("meeting_name", FilterType.EQUALITY, DataType.STRING, "The name of the meeting.") |
|
f1_api.define_filter("meeting_official_name", FilterType.EQUALITY, DataType.STRING, "The official name of the meeting.") |
|
f1_api.define_filter("year", FilterType.EQUALITY, DataType.INTEGER, "The year of the event.") |
|
f1_api.define_filter("pit_duration", FilterType.COMPARISON, DataType.INTEGER, "The time spent in the pit, from entering to leaving the pit lane, in seconds.") |
|
f1_api.define_filter("position", FilterType.EQUALITY, DataType.INTEGER, "Position of the driver (starts at 1).", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) |
|
f1_api.define_filter("category", FilterType.EQUALITY, DataType.STRING, "The category of the event (CarEvent, Drs, Flag, SafetyCar)", ["CarEvent", "Drs", "Flag", "SafetyCar"]) |
|
f1_api.define_filter("flag", FilterType.EQUALITY, DataType.STRING, "The flag displayed to the drivers.", ["Green", "Yellow", "Red", "Black", "White", "Blue", "Checkered", "White"]) |
|
f1_api.define_filter("message", FilterType.EQUALITY, DataType.STRING, "Description of the event or action.") |
|
f1_api.define_filter("session_name", FilterType.EQUALITY, DataType.STRING, "The name of the session (Practice 1, Qualifying, Race, ...).") |
|
f1_api.define_filter("session_type", FilterType.EQUALITY, DataType.STRING, "The type of the session (Practice, Qualifying, Race, ...).") |
|
f1_api.define_filter("compound", FilterType.EQUALITY, DataType.STRING, "The specific compound of tyre used during the stint (SOFT, MEDIUM, HARD, ...).", ["SOFT", "MEDIUM", "HARD", "INTERMEDIATE", "WET"]) |
|
f1_api.define_filter("lap_end", FilterType.COMPARISON, DataType.INTEGER, "Number of the last completed lap in this stint.") |
|
f1_api.define_filter("lap_start", FilterType.COMPARISON, DataType.INTEGER, "Number of the initial lap in this stint (starts at 1).") |
|
f1_api.define_filter("stint_number", FilterType.EQUALITY, DataType.INTEGER, "The sequential number of the stint within the session (starts at 1).") |
|
f1_api.define_filter("tyre_age_at_start", FilterType.COMPARISON, DataType.INTEGER, "The age of the tyres at the start of the stint, in laps completed.") |
|
f1_api.define_filter("air_temperature", FilterType.COMPARISON, DataType.INTEGER, "Air temperature (°C).") |
|
f1_api.define_filter("humidity", FilterType.COMPARISON, DataType.INTEGER, "Humidity percentage.") |
|
f1_api.define_filter("pressure", FilterType.COMPARISON, DataType.INTEGER, "Air pressure (mbar).") |
|
f1_api.define_filter("rainfall", FilterType.COMPARISON, DataType.INTEGER, "Whether there is rainfall.") |
|
f1_api.define_filter("track_temperature", FilterType.COMPARISON, DataType.INTEGER, "Track temperature (°C).") |
|
f1_api.define_filter("wind_direction", FilterType.COMPARISON, DataType.INTEGER, "Wind direction (°), from 0° to 359°.") |
|
f1_api.define_filter("wind_speed", FilterType.COMPARISON, DataType.INTEGER, "Wind speed (m/s).") |
|
|
|
|
|
|
|
f1_api.register_endpoint("car_data", "date", "driver_number", "meeting_key", "session_key", "speed") |
|
f1_api.register_endpoint("drivers", "session_key", "meeting_key", "country_code", "driver_number", "first_name", "last_name", "full_name", "name_acronym", "team_name") |
|
f1_api.register_endpoint("intervals", "date", "driver_number", "meeting_key", "session_key", "gap_to_leader", "interval") |
|
f1_api.register_endpoint("laps", "date_start", "driver_number", "meeting_key", "session_key", "lap_duration", "lap_number", "is_pit_out_lap") |
|
f1_api.register_endpoint("location", "date", "driver_number", "meeting_key", "session_key") |
|
f1_api.register_endpoint("meetings", "circuit_key", "circuit_short_name", "country_code", "country_key", "country_name", "date_start", "location", "meeting_key", "meeting_name", "meeting_official_name", "year") |
|
f1_api.register_endpoint("pit", "date", "driver_number", "lap_number", "meeting_key", "session_key", "pit_duration") |
|
f1_api.register_endpoint("position", "date", "driver_number", "meeting_key", "session_key", "position") |
|
f1_api.register_endpoint("race_control", "category", "date", "driver_number", "meeting_key", "session_key", "flag", "message", "lap_number") |
|
f1_api.register_endpoint("sessions", "circuit_key", "circuit_short_name", "country_code", "country_key", "country_name", "date_start", "date_end", "location", "session_name", "session_type", "session_key", "meeting_key", "year") |
|
f1_api.register_endpoint("stints", "compound", "driver_number", "lap_end", "lap_start", "meeting_key", "session_key", "stint_number", "tyre_age_at_start") |
|
f1_api.register_endpoint("team_radio", "date", "driver_number", "meeting_key", "session_key") |
|
f1_api.register_endpoint("weather", "air_temperature", "date", "humidity", "pressure", "rainfall", "track_temperature", "wind_direction", "wind_speed", "meeting_key", "session_key") |