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" # exact match COMPARISON = "comparison" # >, <, >=, <= class DataType(Enum): STRING = "string" INTEGER = "integer" DATETIME = "datetime" BINARY = "binary" # true/false filters @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 # For equality filters with restricted values 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} 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]] = {} # endpoint -> filter names self.filters: Dict[str, FilterSpec] = {} # global filter definitions 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""" # Validate all filters exist 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.") # Store endpoint -> filters mapping 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()) # Create registry with base URL f1_api = APIEndpointRegistry("https://api.openf1.org/v1/") # Define filters with their specifications 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).") # Register API endpoints with their supported filters 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")