Metadata-Version: 2.4
Name: speedkit
Version: 0.2.0
Summary: Python SDK for network speed testing — bandwidth, latency, and connection details in a single call.
Author: nessshon
Maintainer: nessshon
License-Expression: MIT
Project-URL: Homepage, https://github.com/nessshon/speedkit/
Project-URL: Examples, https://github.com/nessshon/speedkit/tree/main/examples/
Keywords: CLI,SDK,bandwidth,benchmark,internet speed,librespeed,network,ookla,speedtest
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Networking :: Monitoring
Requires-Python: <3.15,>=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: mypy>=1.19.0; extra == "dev"
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: ruff>=0.8.0; extra == "dev"
Dynamic: license-file

# 📦 Speedkit

![Python Versions](https://img.shields.io/badge/Python-3.9%20--%203.14-black?color=FFE873&labelColor=3776AB)
[![PyPI](https://img.shields.io/pypi/v/speedkit.svg?color=FFE873&labelColor=3776AB)](https://pypi.org/project/speedkit/)
[![License](https://img.shields.io/github/license/nessshon/speedkit)](https://github.com/nessshon/speedkit/blob/main/LICENSE)

### Python SDK for network speed testing

Measures download, upload, and latency with the official [Ookla Speedtest CLI](https://www.speedtest.net/apps/cli)
and falls back to [LibreSpeed](https://github.com/librespeed/speedtest-cli) when Speedtest is blocked or unavailable.

**Features**

- **Zero setup** — Ookla Speedtest first, LibreSpeed when it is blocked or fails.
- **Automatic binaries** — downloaded and cached for your OS on first run.
- **Nearby servers** — the lowest-latency server is picked automatically.
- **Self-recovery** — failed attempts are retried, broken servers excluded.
- **Uniform results** — same fields and units whichever provider ran; client info from geo-IP.

## Installation

```bash
pip install speedkit
```

## Usage

```python
from speedkit import Speedkit

kit = Speedkit()
result = kit.run()

print(f"download: {result.download / 1_000_000:.1f} Mbit/s")
print(f"upload:   {result.upload / 1_000_000:.1f} Mbit/s")
print(f"ping:     {result.ping:.1f} ms")

data = result.to_dict()  # plain JSON-serializable dict
```

Or from the command line:

```bash
speedkit                  # auto: Ookla, then LibreSpeed
speedkit -p librespeed    # pin one provider
speedkit -t 180           # time budget per provider in seconds
speedkit -a 1             # disable retries
speedkit --no-geoip       # no third-party geo-IP request
```

`Speedkit()` needs no configuration; everything it accepts:

```python
kit = Speedkit(
    provider="auto",   # "auto" | "ookla" | "librespeed" — a pinned provider never falls back
    timeout=120,       # time budget per provider in seconds, covering all attempts
    attempts=2,        # measurement attempts within the budget; 1 disables retries
    lookup_geoip=True, # resolve client info from a geo-IP service; False makes no such request
    cache_dir=None,    # where CLI binaries are cached; None = user cache directory
)
```

## Result format

`SpeedtestResult.to_dict()` returns `download`/`upload` in bits per second and `ping` in milliseconds:

```json
{
  "client": {
    "ip": "203.0.113.7",
    "isp": "Example ISP",
    "country": "ZZ",
    "city": "Springfield"
  },
  "server": {
    "url": "",
    "name": "Springfield",
    "country": "Freedonia",
    "sponsor": "Example Sponsor",
    "id": "1234",
    "host": "speedtest.example.net:8080",
    "latency": 0.681
  },
  "provider": "ookla",
  "download": 293111520.0,
  "upload": 292265640.0,
  "ping": 0.681,
  "timestamp": "2026-07-19T15:22:48Z",
  "bytes_sent": 418465064,
  "bytes_received": 426981996
}
```

- `client` — from a geo-IP service, identical whichever provider measured.
- `server` — the test server that provider picked; unreported fields stay at `""` / `0.0`.
- `provider` — which measurer produced the result.

## Geo-IP lookup

Client details come from the first service that answers: `ipinfo.io` → `ipwho.is`.

- The lookup takes a fraction of a second.
- If every service is unreachable, the measurement is still returned —
  with whatever client info the CLI reported.

> This sends your IP address to the geo-IP service that answers.

Pass `lookup_geoip=False` (or `--no-geoip`) to skip it entirely. Nothing is then sent to a third
party, and `client` keeps what the measuring CLI itself reported — `ip` and `isp` from
Ookla, plus `country` from LibreSpeed. Fields neither reports, `city` above all, stay at
`""`, so `client` is no longer identical across providers.

## Binaries

| Provider            | Version | Platforms                    |
| ------------------- | ------- | ---------------------------- |
| Ookla Speedtest CLI | 1.2.0   | Linux x86_64/aarch64, macOS  |
| librespeed-cli      | 1.0.13  | Linux x86_64/aarch64, macOS  |

Binaries are downloaded on first use and cached per platform:

- Linux — `~/.cache/speedkit`
- macOS — `~/Library/Caches/speedkit`

Every archive is checked against a SHA-256 digest pinned in `speedkit/binaries.py` before
anything is extracted or made executable. A release asset that changed after it was pinned
raises `BinaryDownloadError` instead of running. All network access is HTTPS with certificate
verification; plain HTTP, and any redirect leaving TLS, is refused.

Set `SPEEDKIT_OOKLA_BINARY` / `SPEEDKIT_LIBRESPEED_BINARY` to a path of a preinstalled
binary to skip downloading entirely — useful for offline machines and locked-down networks.
The path is executed as given and bypasses the checksum, so it is on you to trust it.

To pre-download the binaries at image build time (Docker, CI):

```bash
python -c "from speedkit.binaries import ookla_binary, librespeed_binary; ookla_binary(); librespeed_binary()"
```

> Running the Ookla provider passes `--accept-license --accept-gdpr`, which implies acceptance
> of the [Ookla EULA](https://www.speedtest.net/about/eula) and privacy terms.

## Errors

Every error derives from `SpeedkitError` and carries an actionable hint:

```python
from speedkit import Speedkit, SpeedkitError

try:
    result = Speedkit().run()
except SpeedkitError as error:
    print(error)  # cause, and a hint on how to fix it
```

`UnsupportedPlatformError` — no prebuilt binary for this OS/arch; `BinaryDownloadError` —
the binary could not be fetched; `SpeedtestError` — the measurement itself failed.

## License

This repository is distributed under the [MIT License](LICENSE).
