---
title: "Data Types & Type Hints"
icon: "code"
description: "Type definitions, schemas, and type hints for tif1"
keywords:
  - "tif1"
  - "Formula 1"
  - "F1 data"
  - "documentation"
---

`tif1` uses Python's typing system extensively for better IDE support and type safety. All public APIs include type hints.

## Type Aliases

### `BackendType`

Specifies which DataFrame lib to use.

```python
BackendType = Literal["pandas", "polars"]
```python

**Usage:**
```python
import tif1
from tif1.types import BackendType

lib: BackendType = "pandas"
session = tif1.get_session(2021, "Belgian Grand Prix", "Race", lib=lib)
```

---

### `SessionType`

Valid session names.

```python
SessionType = Literal[
    "Practice 1",
    "Practice 2",
    "Practice 3",
    "Qualifying",
    "Sprint",
    "Sprint Qualifying",
    "Sprint Shootout",
    "Race"
]
```

**Usage:**
```python
from tif1.types import SessionType

session_name: SessionType = "Qualifying"
session = tif1.get_session(2021, "Belgian Grand Prix", session_name)
```

---

### `CompoundType`

Tire compound names.

```python
CompoundType = Literal[
    "SOFT",
    "MEDIUM",
    "HARD",
    "INTERMEDIATE",
    "WET",
    "UNKNOWN",
    "TEST_UNKNOWN"
]
```

**Usage:**
```python
from tif1.types import CompoundType

compound: CompoundType = "SOFT"
soft_laps = laps[laps["Compound"] == compound]
```

---

### `TrackStatusType`

Track status codes during a session.

```python
TrackStatusType = Literal[
    "1",  # Green Flag
    "2",  # Yellow Flag
    "4",  # Safety Car
    "5",  # Red Flag
    "6",  # VSC (Virtual Safety Car)
    "7"   # VSC Ending
]
```python

**Usage:**
```python
from tif1.types import TrackStatusType

# Filter laps under green flag
green_flag: TrackStatusType = "1"
clean_laps = laps[laps["TrackStatus"] == green_flag]
```

---

## TypedDict Schemas

These define the structure of DataFrame row data. All fields except core identity fields (Driver, DriverNumber, Team, Time) are optional and may be None.

### `LapDataDict`

Structure of lap timing data in DataFrames. Uses `NotRequired` for optional fields.

```python
class LapDataDict(TypedDict):
    # Core identity (required)
    Driver: str
    DriverNumber: str
    Team: str
    Time: datetime.timedelta  # Session time from start

    # Lap timing (all timedelta, optional)
    LapTime: NotRequired[datetime.timedelta | None]
    LapNumber: NotRequired[float | None]
    Stint: NotRequired[float | None]
    PitOutTime: NotRequired[datetime.timedelta | None]
    PitInTime: NotRequired[datetime.timedelta | None]
    Sector1Time: NotRequired[datetime.timedelta | None]
    Sector2Time: NotRequired[datetime.timedelta | None]
    Sector3Time: NotRequired[datetime.timedelta | None]
    Sector1SessionTime: NotRequired[datetime.timedelta | None]
    Sector2SessionTime: NotRequired[datetime.timedelta | None]
    Sector3SessionTime: NotRequired[datetime.timedelta | None]
    LapStartTime: NotRequired[datetime.timedelta | None]
    LapStartDate: NotRequired[datetime.datetime | None]

    # Speed traps (km/h, optional)
    SpeedI1: NotRequired[float | None]
    SpeedI2: NotRequired[float | None]
    SpeedFL: NotRequired[float | None]
    SpeedST: NotRequired[float | None]

    # Tire info (optional)
    Compound: NotRequired[str | None]
    TyreLife: NotRequired[float | None]
    FreshTyre: NotRequired[bool]

    # Session/lap metadata (optional)
    TrackStatus: NotRequired[str | None]
    Position: NotRequired[float | None]
    IsPersonalBest: NotRequired[bool]
    Deleted: NotRequired[bool | None]
    DeletedReason: NotRequired[str | None]
    FastF1Generated: NotRequired[bool]
    IsAccurate: NotRequired[bool]

    # Derived fields (optional)
    LapTimeSeconds: NotRequired[float | None]

    # Per-lap weather data (optional)
    WeatherTime: NotRequired[datetime.timedelta | None]
    AirTemp: NotRequired[float | None]
    Humidity: NotRequired[float | None]
    Pressure: NotRequired[float | None]
    Rainfall: NotRequired[bool]
    TrackTemp: NotRequired[float | None]
    WindDirection: NotRequired[int | None]
    WindSpeed: NotRequired[float | None]
```

**Usage:**
```python
import tif1

session = tif1.get_session(2021, "Belgian Grand Prix", "Race")
laps = session.laps

# Access lap data
for _, lap in laps.iterrows():
    driver: str = lap["Driver"]
    lap_time = lap.get("LapTime")  # May be None
    if lap_time:
        print(f"{driver}: {lap_time}")
```

---

### `TelemetryDataDict`

Structure of high-frequency telemetry data. All fields except Time are optional.

```python
class TelemetryDataDict(TypedDict):
    Time: float                                    # Time from lap start (seconds)
    RPM: NotRequired[int | None]                   # Engine RPM
    Speed: NotRequired[float | None]               # Speed (km/h)
    nGear: NotRequired[int | None]                 # Gear number (1-8)
    Throttle: NotRequired[float | None]            # Throttle position (0-100%)
    Brake: NotRequired[int | None]                 # Brake status (0=off, 1=on)
    DRS: NotRequired[int | None]                   # DRS status (0=off, 10+=on)
    Distance: NotRequired[float | None]            # Distance from lap start (meters)
    RelativeDistance: NotRequired[float | None]    # Relative distance
    DriverAhead: NotRequired[str | None]           # Driver code ahead
    DistanceToDriverAhead: NotRequired[float | None]  # Gap to driver ahead
    X: NotRequired[float | None]                   # X coordinate (meters)
    Y: NotRequired[float | None]                   # Y coordinate (meters)
    Z: NotRequired[float | None]                   # Z coordinate (meters)
    AccelerationX: NotRequired[float | None]       # X acceleration
    AccelerationY: NotRequired[float | None]       # Y acceleration
    AccelerationZ: NotRequired[float | None]       # Z acceleration
    DataKey: NotRequired[str | None]               # Data key identifier
```

**Usage:**
```python
import tif1

session = tif1.get_session(2021, "Belgian Grand Prix", "Race")
lap = session.laps.iloc[0]
tel = lap.telemetry

# Access telemetry data
for _, sample in tel.iterrows():
    time: float = sample["Time"]
    speed = sample.get("Speed")  # May be None
    if speed:
        print(f"Time: {time:.2f}s, Speed: {speed:.1f} km/h")
```

---

### `WeatherDataDict`

Structure of weather data returned by `session.weather`.

```python
class WeatherDataDict(TypedDict):
    Time: datetime.timedelta                       # Session time from start
    AirTemp: NotRequired[float | None]             # Air temperature (°C)
    Humidity: NotRequired[float | None]            # Relative humidity (%)
    Pressure: NotRequired[float | None]            # Atmospheric pressure (mbar)
    Rainfall: NotRequired[bool | None]             # Rainfall indicator
    TrackTemp: NotRequired[float | None]           # Track temperature (°C)
    WindDirection: NotRequired[int | None]         # Wind direction (degrees)
    WindSpeed: NotRequired[float | None]           # Wind speed (km/h)
```

**Usage:**
```python
import tif1

session = tif1.get_session(2021, "Belgian Grand Prix", "Race")
weather = session.weather

# Access weather data
for _, sample in weather.iterrows():
    time = sample["Time"]
    air_temp = sample.get("AirTemp")
    rainfall = sample.get("Rainfall", False)
    print(f"Time: {time}, Temp: {air_temp}°C, Rain: {rainfall}")
```

---

### `RaceControlDataDict`

Structure of race control messages.

```python
class RaceControlDataDict(TypedDict):
    Time: float                                    # Session time (seconds)
    Category: NotRequired[str | None]              # Message category
    Message: NotRequired[str | None]               # Message text
    Status: NotRequired[str | None]                # Track status
    Flag: NotRequired[str | None]                  # Flag type
    Scope: NotRequired[str | None]                 # Message scope
    Sector: NotRequired[int | str | None]          # Affected sector
    RacingNumber: NotRequired[str | None]          # Affected driver number
    Lap: NotRequired[int | None]                   # Lap number
```

**Usage:**
```python
import tif1

session = tif1.get_session(2021, "Belgian Grand Prix", "Race")
messages = session.race_control_messages

# Access race control messages
for _, msg in messages.iterrows():
    time = msg["Time"]
    category = msg.get("Category", "")
    message = msg.get("Message", "")
    print(f"{time}: [{category}] {message}")
```

---

### `DriverInfoDict`

Structure of driver information.

```python
class DriverInfoDict(TypedDict):
    driver: str                    # 3-letter code (e.g., "VER")
    team: str                      # Team name
    dn: str                        # Driver number (car number)
    fn: str                        # First name
    ln: str                        # Last name
    tc: str                        # Team color (hex code)
    url: str                       # Headshot URL
```

**Usage:**
```python
import tif1

session = tif1.get_session(2021, "Belgian Grand Prix", "Race")
drivers_df = session.drivers_df

# Access driver information
for _, driver in drivers_df.iterrows():
    code = driver["Driver"]
    name = f"{driver['FirstName']} {driver['LastName']}"
    team = driver["Team"]
    print(f"{code}: {name} ({team})")
```

---

## DataFrame type hints

For better IDE support, use type hints with DataFrames:

### Pandas DataFrames

```python
import pandas as pd
import tif1

session = tif1.get_session(2021, "Belgian Grand Prix", "Race")

# Type hint for laps DataFrame
laps: pd.DataFrame = session.laps

# Type hint for telemetry
tel: pd.DataFrame = session.get_fastest_lap_tel()

# Type hint for weather
weather: pd.DataFrame = session.weather
```

### Polars DataFrames

```python
import polars as pl
import tif1

config = tif1.get_config()
config.set("lib", "polars")

session = tif1.get_session(2021, "Belgian Grand Prix", "Race")

# Type hint for laps DataFrame
laps: pl.DataFrame = session.laps

# Type hint for telemetry
tel: pl.DataFrame = session.get_fastest_lap_tel()
```

### Union types for lib-agnostic code

```python
from typing import Union
import pandas as pd
import polars as pl
import tif1

DataFrame = Union[pd.DataFrame, pl.DataFrame]

def analyze_laps(laps: DataFrame) -> dict:
    """Works with both pandas and polars."""
    if isinstance(laps, pd.DataFrame):
        return {"count": len(laps), "fastest": laps["LapTime"].min()}
    else:
        return {"count": len(laps), "fastest": laps["LapTime"].min()}
```

---

## Object type hints

### Core Objects

```python
from tif1 import Session, Driver
from tif1.core import Lap, Laps, Telemetry

# Session
session: Session = tif1.get_session(2021, "Belgian Grand Prix", "Race")

# Driver
driver: Driver = session.get_driver("VER")

# Lap
lap: Lap = driver.get_lap(19)

# Laps collection
laps: Laps = session.laps

# Telemetry
telemetry: Telemetry = lap.telemetry
```

### Model Objects

```python
from tif1.core import SessionResults, DriverResult, CircuitInfo

# Session results
results: SessionResults = session.results

# Circuit info
circuit: CircuitInfo = session.get_circuit_info()
```

---

## Generic type hints

### Async Functions

```python
from typing import Awaitable
import pandas as pd
import tif1

async def load_session_async() -> pd.DataFrame:
    session = tif1.get_session(2021, "Belgian Grand Prix", "Race")
    laps: pd.DataFrame = await session.laps_async()
    return laps

# Type hint for async function
load_func: Awaitable[pd.DataFrame] = load_session_async()
```

### Dictionary Returns

```python
from typing import Dict
import pandas as pd
import tif1

session = tif1.get_session(2021, "Belgian Grand Prix", "Race")

# Type hint for telemetry dictionary
tels: Dict[str, pd.DataFrame] = session.get_fastest_laps_tels(by_driver=True)

for driver, tel in tels.items():
    driver_code: str = driver
    telemetry: pd.DataFrame = tel
    print(f"{driver_code}: {len(telemetry)} samples")
```

---

## Complete type-annotated example

```python
from typing import Dict, List, Tuple, Optional
import pandas as pd
import tif1
from tif1 import Session, Driver
from tif1.core import Lap
from tif1.types import BackendType, SessionType, CompoundType

def analyze_qualifying(
    year: int,
    gp: str,
    lib: BackendType = "pandas"
) -> Dict[str, float]:
    """
    Analyze qualifying session and return fastest lap times.

    Args:
        year: Season year
        gp: Grand Prix name
        lib: DataFrame library to use

    Returns:
        Dictionary mapping driver codes to fastest lap times
    """
    session_name: SessionType = "Qualifying"
    session: Session = tif1.get_session(year, gp, session_name, lib=lib)

    fastest_laps: pd.DataFrame = session.get_fastest_laps(by_driver=True)

    results: Dict[str, float] = {}
    for _, row in fastest_laps.iterrows():
        driver: str = row["Driver"]
        lap_time: float = row["LapTime"]
        results[driver] = lap_time

    return results

def get_compound_usage(
    session: Session,
    driver_code: str
) -> Dict[CompoundType, int]:
    """
    Count laps per compound for a driver.

    Args:
        session: Session object
        driver_code: 3-letter driver code

    Returns:
        Dictionary mapping compounds to lap counts
    """
    driver: Driver = session.get_driver(driver_code)
    laps: pd.DataFrame = driver.laps

    compound_counts: Dict[CompoundType, int] = {}
    for compound in ["SOFT", "MEDIUM", "HARD"]:
        count: int = len(laps[laps["Compound"] == compound])
        if count > 0:
            compound_counts[compound] = count  # type: ignore

    return compound_counts

def find_fastest_sector(
    lap: Lap
) -> Tuple[int, float]:
    """
    Find the fastest sector in a lap.

    Args:
        lap: Lap object

    Returns:
        Tuple of (sector_number, sector_time)
    """
    # Access lap data (Lap is a Series subclass)
    s1: float = lap["Sector1Time"]
    s2: float = lap["Sector2Time"]
    s3: float = lap["Sector3Time"]

    sectors: List[Tuple[int, float]] = [
        (1, s1),
        (2, s2),
        (3, s3)
    ]

    fastest: Tuple[int, float] = min(sectors, key=lambda x: x[1])
    return fastest

# Usage with type checking
if __name__ == "__main__":
    results: Dict[str, float] = analyze_qualifying(2021, "Belgian Grand Prix")

    for driver, time in results.items():
        print(f"{driver}: {time:.3f}s")
```

---

## Type Checking

### Using mypy

```bash
# Install mypy
pip install mypy

# Check types
mypy your_script.py
```

### Using pyright

```bash
# Install pyright
npm install -g pyright

# Check types
pyright your_script.py
```

### IDE Support

Modern IDEs like VS Code, PyCharm, and others provide:
- Autocomplete based on type hints
- Inline type checking
- Parameter hints
- Return type hints

**Example in VS Code:**
```python
import tif1

session = tif1.get_session(2021, "Belgian Grand Prix", "Race")
# Hovering over 'session' shows: session: Session

laps = session.laps
# Hovering over 'laps' shows: laps: DataFrame

driver = session.get_driver("VER")
# Hovering over 'driver' shows: driver: Driver
```

---

## Best Practices

1. **Always use type hints in function signatures**: Helps catch errors early.

2. **Use Union types for lib-agnostic code**: Supports both pandas and polars.

3. **Import types from tif1.types**: Centralized type definitions.

4. **Use TypedDict for JSON data**: Better than plain dicts.

5. **Enable strict type checking**: Use mypy or pyright in CI/CD.

6. **Document complex types**: Add docstrings explaining type parameters.

7. **Use Optional for nullable values**: Makes None handling explicit.

```python
from typing import Optional
import tif1

def get_driver_safely(
    session: tif1.Session,
    driver_code: str
) -> Optional[tif1.Driver]:
    """Get driver or None if not found."""
    try:
        return session.get_driver(driver_code)
    except tif1.DriverNotFoundError:
        return None
```
