Metadata-Version: 2.5
Name: blacktop-data
Version: 0.1.2
Summary: Python client for the Blacktop Data API, with a FastF1-style interface for Formula 1
Project-URL: Homepage, https://ocblacktop.com
Project-URL: Documentation, https://api.ocblacktop.com/api/docs
Author: Blacktop Data
License-Expression: MIT
License-File: LICENSE
Keywords: api,f1,fastf1,formula1,motorsport,telemetry
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.10
Requires-Dist: httpx>=0.24
Requires-Dist: pandas>=2.0
Provides-Extra: test
Requires-Dist: pytest>=7; extra == 'test'
Description-Content-Type: text/markdown

# blacktop-data

Python client for the [Blacktop Data API](https://api.ocblacktop.com/api/docs). The `blacktop.f1` module follows FastF1's interface, so most FastF1 scripts run after changing the import.

## Install

```bash
pip install blacktop-data
```

Python 3.10 or later. Depends on `httpx` and `pandas`.

## API key

Create a key in the API Keys section of your profile at [ocblacktop.com](https://ocblacktop.com), then set it in the environment:

```bash
export BLACKTOP_API_KEY=your-key
```

or pass it in code:

```python
import blacktop

blacktop.set_client(blacktop.Client(api_key="your-key"))
```

## Quickstart

FastF1's fastest-lap speed trace, with one import changed:

```python
from matplotlib import pyplot as plt
import blacktop.f1 as fastf1

fastf1.Cache.enable_cache("cache")

session = fastf1.get_session(2019, "Monza", "R")
session.load()
fast_leclerc = session.laps.pick_drivers("LEC").pick_fastest()
lec_car_data = fast_leclerc.get_car_data()
t = lec_car_data["Time"].dt.total_seconds()
vCar = lec_car_data["Speed"]

fig, ax = plt.subplots()
ax.plot(t, vCar, label="Fast")
ax.set_xlabel("Time [s]")
ax.set_ylabel("Speed [km/h]")
ax.set_title("Leclerc is")
ax.legend()
plt.show()
```

`matplotlib` is not a dependency; install it to plot. Use `.add_distance()` on car data for speed against distance.

Laps cover every F1 session from 2018. Car data covers every race and sprint since 2018 and every session from 2026. Lap and car data are served once a session has finished. The routes behind `laps`, `car_data`, `pos_data` and `team_radio` need a paid plan; on the free plan they raise `PlanRequiredError` with the API's message.

## Coming from FastF1

| FastF1 | `blacktop.f1` |
| --- | --- |
| `Cache.enable_cache(path)` | Same. The cache is also on by default; see Caching. |
| `get_event_schedule(year)` | Same columns where the API has them. `RoundNumber` is counted in date order; testing is round 0. |
| `get_event`, `get_session`, `get_testing_session` | Same. `gp` is a round number or a name matched against event name, location and country. Identifiers: `R`, `Q`, `S`, `SQ`, `SS`, `FP1`–`FP3`, full names, or 1–5. |
| `session.load()` | Accepted. Data loads on first access. |
| `session.results`, `get_driver()`, `drivers` | Same. `ClassifiedPosition` and `Status` use the API's values (`DNF`, `DSQ`, `OK`). |
| `session.laps` | `Driver`, `DriverNumber`, `Team`, `LapNumber`, `LapTime`, `Sector1Time`–`Sector3Time`, `Compound`, `Position`, `Stint`, plus `InPit` (in-lap), `PitOut` (out-lap) and `StintLap` (laps into the stint, from 1). `Stint` starts a new stint on each out-lap, as FastF1 does. |
| `pick_drivers`, `pick_laps`, `pick_fastest`, `pick_quicklaps`, `pick_compounds`, `pick_tyre`, `pick_teams`, `pick_wo_box`, `pick_box_laps`, `iterlaps` | Same. `pick_fastest` picks by `LapTime`; the API does not flag deleted laps. |
| `Lap.get_car_data()`, `Laps.get_car_data()`, `session.car_data[...]` | `Date`, `SessionTime`, `Time`, `RPM`, `Speed`, `nGear`, `Throttle`, `Brake`, `DRS`, `Source`, plus `LapNumber`. About 4 Hz. |
| `get_telemetry()` | Car data with `Distance` and `RelativeDistance`. No `X`/`Y`/`Z`. |
| `add_distance()`, `add_relative_distance()`, `slice_by_lap()`, `slice_by_time()` | Same. |
| `session.pos_data[...]` | `Time`, `TrackFraction` (0 at the line), `LapNumber`. No `X`/`Y`/`Z`; `Time` is the positions route's own clock. |
| `session.weather_data` | Same values, timestamped by `Date` (UTC) instead of `Time`. |
| `session.race_control_messages` | Same, without `Status`. |
| `fastf1.plotting` | Not included. |

Times are tz-naive UTC timestamps and `Timedelta`s, as in FastF1. A channel the source recorded wrong is withheld and comes back as missing values; `car.quality`, `car.quality_note` and `car.withheld_channels` say which.

These raise `NotImplementedError` (`blacktop.NotServedError`, also a `KeyError`) with a one-line reason:

- Lap columns: `Time`, `LapStartTime`, `LapStartDate`, `PitInTime`, `PitOutTime` (use `InPit`/`PitOut`), `Sector1SessionTime`–`Sector3SessionTime`, `SpeedI1`, `SpeedI2`, `SpeedFL`, `SpeedST`, `IsPersonalBest`, `TyreLife` (use `StintLap`), `FreshTyre`, `TrackStatus`, `Deleted`, `DeletedReason`, `IsAccurate`, `FastF1Generated`.
- Laps methods: `pick_not_deleted`, `pick_accurate`, `pick_track_status`, `split_qualifying_sessions`, `get_pos_data`, `get_weather_data`.
- Session: `track_status`, `session_status`, `total_laps`, `session_start_time`, `session_info`, `get_circuit_info()`.
- Car data: `X`, `Y`, `Z`, `Status`, `DriverAhead`, `DistanceToDriverAhead`, `add_driver_ahead()`, `merge_channels()`, `add_track_status()`.
- Results: `BroadcastName`, `HeadshotUrl`, `CountryCode`.
- Schedule: local session times (`Session1Date`…, `get_session_date(utc=False)`), `F1ApiSupport`.

Not in FastF1: `session.mini_sectors(driver)` (per-lap mini-sector speeds, pedal shares and gap to the car ahead), `session.team_radio`, and `session.t0_date`.

## Caching

The cache is on by default and stores responses in:

- macOS: `~/Library/Caches/blacktop-data`
- Linux: `$XDG_CACHE_HOME/blacktop-data`, or `~/.cache/blacktop-data`
- Windows: `%LOCALAPPDATA%\blacktop-data`

To use another directory, set `BLACKTOP_CACHE_DIR` or call `Cache.enable_cache(path)`. `Cache.disable_cache()` turns caching off and `Cache.clear_cache()` empties the directory.

Only session routes (`/events/{id}/sessions/{id}/...`) are cached. Telemetry responses are kept indefinitely, since telemetry does not change once a session is published. Other session routes are refetched after 24 hours. Schedules and `/live/` routes (weather, race control) are not cached. A cached response makes no request, so it does not count against your monthly quota.

`session.laps` is one request for every driver. `Lap.get_car_data()` fetches that lap only, unless the driver's whole session is already in memory or in the cache. `Laps.get_car_data()` over several laps and `session.car_data[...]` fetch the driver's whole session once. With `Cache.offline_mode(True)`, a session route missing from the cache raises `CacheMissError` instead of making a request.

## Low-level client

```python
import blacktop

client = blacktop.Client()                         # reads BLACKTOP_API_KEY
standings = client.get("/v1/formula1/standings/drivers", year=2024)
events = client.get_all("/v1/formula1/events", year=2024)   # follows every page
```

Errors: `AuthenticationError` (401), `PlanRequiredError` (402, with `required_tier`, `current_tier`, `upgrade_url`), `NotFoundError` (404, with the API's message; withheld sessions say why), `RateLimitError` (429). A 429 with a short `Retry-After` is retried once.

## License

MIT
