Skip to main content
tif1 uses a structured exception hierarchy to help you handle errors gracefully. All exceptions inherit from TIF1Error and include contextual information.

Exception Hierarchy

TIF1Error (base)
├── DataNotFoundError
│   ├── DriverNotFoundError
│   └── LapNotFoundError
├── NetworkError
├── InvalidDataError
├── CacheError
└── SessionNotLoadedError
```python

---

## Base Exception

### `TIF1Error`

Base exception for all tif1 errors. All exceptions accept `**context` kwargs for structured error information.

```python
class TIF1Error(Exception):
    def __init__(self, message: str, **context: Any) -> None
```python

**Attributes:**
- `message`: Error message string
- `context`: Dictionary of contextual information

**Example:**
```python
try:
    session = tif1.get_session(2025, "Invalid GP", "Race")
except tif1.TIF1Error as e:
    print(f"Error: {e.message}")
    print(f"Context: {e.context}")
```python

---

## Data Errors

### `DataNotFoundError`

Raised when requested data is not available in the CDN.

```python
class DataNotFoundError(TIF1Error):
    def __init__(
        self,
        year: int | None = None,
        event: str | None = None,
        session: str | None = None,
        **context: Any
    ) -> None
```python

**Context:**
- `year`: The requested year
- `event`: The requested event name
- `session`: The requested session name

**Example:**
```python
try:
    session = tif1.get_session(2030, "Future GP", "Race")
except tif1.DataNotFoundError as e:
    print(f"Data not found: {e.context}")
    # {'year': 2030, 'event': 'Future GP', 'session': 'Race'}
```python

### `DriverNotFoundError`

Raised when a driver code is not found in the session.

```python
class DriverNotFoundError(DataNotFoundError):
    def __init__(self, driver: str, **context: Any) -> None
```python

**Context:**
- `driver`: The requested driver code

**Example:**
```python
try:
    driver = session.get_driver("XXX")
except tif1.DriverNotFoundError as e:
    print(f"Driver '{e.context['driver']}' not found")
    print(f"Available: {session.drivers}")
```text ### `LapNotFoundError`

Raised when a lap number is not found for a driver.

```python
class LapNotFoundError(DataNotFoundError):
    def __init__(
        self,
        lap_number: int | None = None,
        driver: str | None = None,
        **context: Any
    ) -> None
```python

**Context:**
- `lap_number`: The requested lap number
- `driver`: The driver code

**Example:**
```python
try:
    lap = driver.get_lap(999)
except tif1.LapNotFoundError as e:
    print(f"Lap {e.context['lap_number']} not found for {e.context['driver']}")
```python

---

## Network Errors

### `NetworkError`

Raised when network requests fail after all retries and CDN fallbacks are exhausted.

```python
class NetworkError(TIF1Error):
    def __init__(
        self,
        url: str | None = None,
        status_code: int | None = None,
        **context: Any
    ) -> None
```python

**Context:**
- `url`: The URL that failed
- `status_code`: HTTP status code (if available)

**Example:**
```python
try:
    session = tif1.get_session(2025, "Monaco", "Race")
    laps = session.laps
except tif1.NetworkError as e:
    print(f"Network error: {e.message}")
    print(f"URL: {e.context.get('url')}")
    print(f"Status: {e.context.get('status_code')}")
```python

---

## Data validation errors

### `InvalidDataError`

Raised when fetched data is invalid, corrupted, or fails validation.

```python
class InvalidDataError(TIF1Error):
    def __init__(self, reason: str | None = None, **context: Any) -> None
```python

**Context:**
- `reason`: Description of why the data is invalid

**Example:**
```python
try:
    session = tif1.get_session(2025, "Monaco", "Race")
    laps = session.laps
except tif1.InvalidDataError as e:
    print(f"Invalid data: {e.context.get('reason')}")
```python

---

## Cache Errors

### `CacheError`

Raised when cache operations fail (e.g., SQLite errors, disk full).

```python
class CacheError(TIF1Error)
```python

**Example:**
```python
try:
    cache = tif1.get_cache()
    cache.clear()
except tif1.CacheError as e:
    print(f"Cache error: {e.message}")
```python ---

## Session state errors

### `SessionNotLoadedError`

Raised when accessing session data before it has been loaded.

```python
class SessionNotLoadedError(TIF1Error):
    def __init__(self, attribute: str | None = None) -> None
```python

**Context:**
- `attribute`: The attribute that was accessed

**Example:**
```python
try:
    session = tif1.get_session(2025, "Monaco", "Race")
    # Accessing data without loading first (if lazy loading is disabled)
    drivers = session._drivers  # Internal attribute
except tif1.SessionNotLoadedError as e:
    print(f"Session not loaded. Call session.load() first.")
```python

---

## Error handling patterns

### Basic Try-Catch

```python
import tif1

try:
    session = tif1.get_session(2025, "Monaco Grand Prix", "Race")
    laps = session.laps
    fastest = session.get_fastest_laps()
except tif1.DataNotFoundError as e:
    print(f"Data not found: {e.message}")
except tif1.NetworkError as e:
    print(f"Network error: {e.message}")
except tif1.TIF1Error as e:
    print(f"General error: {e.message}")
```python

### Specific error handling

```python
import tif1

session = tif1.get_session(2025, "Monaco", "Race")

# Handle driver not found
try:
    driver = session.get_driver("XXX")
except tif1.DriverNotFoundError:
    print(f"Driver not found. Available: {session.drivers}")
    driver = session.get_driver(session.drivers[0])

# Handle lap not found
try:
    lap = driver.get_lap(999)
except tif1.LapNotFoundError:
    print("Lap not found. Using fastest lap instead.")
    lap_df = driver.get_fastest_lap()
```python

### Retry with Fallback

```python
import tif1
import time

def load_session_with_retry(year, gp, session_name, max_retries=3):
    for attempt in range(max_retries):
        try:
            session = tif1.get_session(year, gp, session_name)
            laps = session.laps
            return session
        except tif1.NetworkError as e:
            if attempt < max_retries - 1:
                print(f"Network error, retrying... ({attempt + 1}/{max_retries})")
                time.sleep(2 ** attempt)  # Exponential backoff
            else:
                print("Max retries exceeded")
                raise
        except tif1.DataNotFoundError:
            print("Data not found, cannot retry")
            raise

session = load_session_with_retry(2025, "Monaco", "Race")
```python

### Context-aware error handling

```python
import tif1

try:
    session = tif1.get_session(2025, "Monaco", "Race")
    driver = session.get_driver("VER")
    lap = driver.get_lap(19)
    tel = lap.telemetry
except tif1.TIF1Error as e:
    # Access structured context
    print(f"Error: {e.message}")

    if 'year' in e.context:
        print(f"Year: {e.context['year']}")
    if 'driver' in e.context:
        print(f"Driver: {e.context['driver']}")
    if 'lap_number' in e.context:
        print(f"Lap: {e.context['lap_number']}")

    # Log full context for debugging
    print(f"Full context: {e.context}")
```python

### Graceful Degradation

```python
import tif1

session = tif1.get_session(2025, "Monaco", "Race")

# Try to get telemetry, fall back to lap data only
drivers_data = {}

for driver_code in session.drivers:
    try:
        driver = session.get_driver(driver_code)
        fastest_tel = driver.get_fastest_lap_tel()
        drivers_data[driver_code] = {
            'has_telemetry': True,
            'telemetry': fastest_tel
        }
    except tif1.TIF1Error as e:
        # Fall back to lap data only
        fastest_lap = driver.get_fastest_lap()
        drivers_data[driver_code] = {
            'has_telemetry': False,
            'lap_data': fastest_lap
        }
        print(f"Warning: No telemetry for {driver_code}: {e.message}")
```yaml

---

## Best Practices

1. **Catch specific exceptions first**: Handle `DriverNotFoundError` before `DataNotFoundError`, and `DataNotFoundError` before `TIF1Error`.

2. **Use context information**: All exceptions include structured context via the `context` attribute.

3. **Don't swallow errors silently**: Always log or handle errors appropriately.

4. **Implement retries for network errors**: Network errors are often transient.

5. **Validate user input early**: Check driver codes and lap numbers before making expensive API calls.

6. **Use try-finally for cleanup**: Ensure resources are cleaned up even if errors occur.

```python
cache = tif1.get_cache()
try:
    # ... operations ...
    pass
finally:
    cache.close()
```python 7. **Log context for debugging**: The `context` dict contains valuable debugging information.

```python
import logging

try:
    session = tif1.get_session(2025, "Monaco", "Race")
except tif1.TIF1Error as e:
    logging.error(f"Error: {e.message}", extra=e.context)
```python