Skip to main content

No project description provided

Project description

skysnoop

A Python SDK and CLI for querying aircraft data from adsb.lol, a community-driven ADS-B aggregation service.

Python 3.10+ License: MIT

Features

  • 🛩️ Comprehensive API Coverage: Query aircraft by location, identifier, type, and more
  • 🎯 Type-Safe: Full type hints and Pydantic models for all data structures
  • Async-First: Built on httpx for high-performance async operations
  • 🖥️ Beautiful CLI: Rich terminal output with tables and JSON formatting
  • 🔍 Flexible Filtering: Filter by altitude, type, callsign, squawk, and more
  • 🧪 Well-Tested: >90% code coverage with comprehensive test suite
  • 🔌 Dual Backend Support: Works with both OpenAPI (public) and RE-API (feeder) backends

Documentation

Complete documentation is available at docs/README.md

For Contributors:

About adsb.lol

adsb.lol is a community-driven ADS-B aggregation service that collects aircraft position data from volunteers worldwide.

API Backends:

  • RE-API - Feeder-only API with full features
  • OpenAPI - Public API (some features simulated)

The SkySnoop unified client automatically handles both backends, selecting the appropriate one and normalizing responses.

Resources:

Installation

From PyPI

pip install skysnoop

From Source

git clone https://github.com/tedivm/skysnoop.git
cd skysnoop
pip install -e .

Quick Start

High-Level Client (Recommended)

The SkySnoop unified client provides a single, consistent interface that works with both API backends. It automatically selects the appropriate backend, normalizes responses, and handles differences transparently.

Key Benefits:

  • Single interface - no need to learn two different APIs
  • Automatic backend selection - uses RE-API by default, OpenAPI as fallback
  • Normalized responses - consistent data structure regardless of backend
  • Future-proof - ready for API key authentication when available
  • Flexible - can explicitly choose backend when needed

Python Usage

import asyncio
from skysnoop import SkySnoop

async def main():
    # Auto-select backend (recommended - prefers RE-API, falls back to OpenAPI)
    async with SkySnoop() as client:
        # Query by ICAO hex
        result = await client.get_by_hex("4CA87C")
        print(f"Found {result.result_count} aircraft")

        # Query aircraft in a circle (50nm radius)
        result = await client.get_in_circle(
            lat=37.7749,
            lon=-122.4194,
            radius=50
        )

        # Get closest aircraft within 100nm
        result = await client.get_closest(
            lat=37.7749,
            lon=-122.4194,
            radius=100
        )

        # Access aircraft data
        for aircraft in result.aircraft:
            print(f"{aircraft.hex}: {aircraft.flight} at {aircraft.alt_baro}ft")

asyncio.run(main())

With filters:

from skysnoop import SkySnoop
from skysnoop.query.filters import QueryFilters

async def main():
    async with SkySnoop() as client:
        # Create filters
        filters = QueryFilters(
            above_alt_baro=30000,
            type_code="A321",
            military=True
        )

        # Query with filters
        result = await client.get_in_circle(
            lat=37.7749,
            lon=-122.4194,
            radius=200,
            filters=filters
        )

        print(f"Found {result.result_count} military A321s above 30,000ft")

asyncio.run(main())

Explicit backend selection:

async def main():
    # Force RE-API backend (feeder access required)
    async with SkySnoop(backend="reapi") as client:
        result = await client.get_in_box(
            lat_min=37.0,
            lat_max=38.0,
            lon_min=-123.0,
            lon_max=-122.0
        )

    # Force OpenAPI backend (public access)
    async with SkySnoop(backend="openapi") as client:
        result = await client.get_by_hex("4CA87C")

asyncio.run(main())

Backend Comparison

Access Requirements:

  • RE-API: Only accessible from the same IP address as active adsb.lol feeders. This is a stable, mature API that requires you to be contributing data to the network. If you have access to this you should use it.
  • OpenAPI: Publicly accessible without restrictions currently. However, this is a newer API that is subject to change and will likely introduce rate limiting and API key requirements in the future.
Feature RE-API OpenAPI
Access Feeder-only Public
Status Stable Subject to Change
get_by_hex()
get_by_callsign()
get_by_registration()
get_by_type()
get_in_circle()
get_closest()
get_in_box() ✅ Simulated*
get_all_with_pos()
Filters ✅ Full support ⚠️ Limited**
API Key Not required Future

* OpenAPI simulates get_in_box() by fetching bounding circle and filtering client-side ** OpenAPI supports only military filter via separate endpoint


Low-Level Clients (Advanced Use)

For advanced use cases requiring direct backend access, you can use the low-level client implementations:

OpenAPI Client

Direct access to the public OpenAPI backend with versioned endpoints and type-safe generated models.

import asyncio
from skysnoop.client import OpenAPIClient

async def main():
    async with OpenAPIClient() as client:
        # Query by ICAO hex
        response = await client.v2.get_by_hex(icao_hex="4CA87C")

        # Query military aircraft
        response = await client.v2.get_mil()

        # Query by location
        response = await client.v2.get_by_point(
            lat=37.7749,
            lon=-122.4194,
            radius=50
        )

        # Access aircraft data (note: uses .ac instead of .aircraft)
        for aircraft in response.ac:
            print(f"{aircraft.hex}: {aircraft.flight} at {aircraft.alt_baro}ft")

asyncio.run(main())

See OpenAPI Client Documentation for full details.

RE-API Client

Direct access to the feeder-only RE-API backend with native support for all geographic queries.

import asyncio
from skysnoop.client.api import ReAPIClient

async def main():
    async with ReAPIClient() as client:
        # Query aircraft in a circular area
        response = await client.circle(
            lat=37.7749,
            lon=-122.4194,
            radius=50  # nautical miles
        )

        # Iterate through results (note: uses .resultCount instead of .result_count)
        print(f"Found {response.resultCount} aircraft")
        for aircraft in response:
            print(f"{aircraft.hex}: {aircraft.flight} at {aircraft.alt_baro}ft")

asyncio.run(main())

With filters:

from skysnoop.query.filters import QueryFilters

async def main():
    async with ReAPIClient() as client:
        # Create filters
        filters = QueryFilters(
            above_alt_baro=30000,
            type_code="A321"
        )

        # Query with filters
        response = await client.circle(
            lat=37.7749,
            lon=-122.4194,
            radius=200,
            filters=filters
        )

        for aircraft in response:
            print(f"{aircraft.hex}: {aircraft.type} at {aircraft.alt_baro}ft")

asyncio.run(main())

Note: The low-level clients have different APIs and response formats. Consider using the SkySnoop unified client for a consistent interface.

CLI Usage

The skysnoop CLI provides a convenient command-line interface for querying aircraft data.

Backend Selection

All commands support the --backend option to choose which API backend to use:

# Auto-select backend (default - prefers RE-API, falls back to OpenAPI)
skysnoop circle --backend auto -- 37.7749 -122.4194 50

# Force RE-API backend (feeder access required)
skysnoop circle --backend reapi -- 37.7749 -122.4194 50

# Force OpenAPI backend (public access)
skysnoop find-hex --backend openapi 4CA87C

Note: The default backend is auto, which automatically selects RE-API (feeder) or OpenAPI (public) based on availability.

Geographic Queries

Query aircraft by location:

# Query aircraft within a radius of a point
skysnoop circle -- <lat> <lon> <radius_nm>
skysnoop circle -- 37.7749 -122.4194 50

# Find the closest aircraft to a point
skysnoop closest -- <lat> <lon> <max_radius_nm>
skysnoop closest -- 37.7749 -122.4194 100

# Query aircraft within a bounding box
skysnoop box -- <lat_south> <lat_north> <lon_west> <lon_east>
skysnoop box -- 37.0 38.0 -123.0 -122.0

Identifier Queries

Query aircraft by identifier:

# Find aircraft by ICAO hex code
skysnoop find-hex <hex_code>
skysnoop find-hex 4CA87C

# Find aircraft by callsign
skysnoop find-callsign <callsign>
skysnoop find-callsign UAL123

# Find aircraft by registration
skysnoop find-reg <registration>
skysnoop find-reg N12345

# Find all aircraft of a specific type
skysnoop find-type <type_code>
skysnoop find-type A321

Bulk Queries

Query all aircraft:

# Query all aircraft with position data
skysnoop all-aircraft

# Include aircraft without position data
skysnoop all-aircraft --include-no-position

Filtering Options

All commands support filtering options:

  • --backend <auto|reapi|openapi> - Choose API backend
  • --json - Output as JSON instead of table
  • --callsign <callsign> - Filter by exact callsign
  • --callsign-prefix <prefix> - Filter by callsign prefix
  • --type <type> - Filter by aircraft type (e.g., A321, B738)
  • --squawk <squawk> - Filter by squawk code
  • --above-alt <feet> - Filter for aircraft above altitude
  • --below-alt <feet> - Filter for aircraft below altitude
  • --military - Filter for military aircraft

Examples:

# Military aircraft above 30,000ft with auto backend selection
skysnoop circle --backend auto --military --above-alt 30000 -- 37.7749 -122.4194 200

# A321 aircraft with JSON output
skysnoop find-type --json A321

# Aircraft with callsign prefix using OpenAPI backend
skysnoop circle --backend openapi --callsign-prefix UAL -- 37.7749 -122.4194 100

Low-Level CLI Commands

For direct access to backend-specific features:

# OpenAPI v2 endpoints
skysnoop openapi v2 mil              # Query military aircraft
skysnoop openapi v2 hex 4CA87C       # Find by hex
skysnoop openapi v2 point 37.7749 -- -122.4194 50  # Query by location
skysnoop openapi v2 closest 37.7749 -- -122.4194 100  # Closest aircraft

Note: When using negative coordinates (e.g., longitude), use -- before the coordinates to prevent them from being interpreted as options.

Error Handling

The library defines custom exceptions in skysnoop.exceptions:

from skysnoop import SkySnoop
from skysnoop.exceptions import (
    SkySnoopError,
    APIError,
    TimeoutError,
    ValidationError,
    UnsupportedOperationError
)

try:
    async with SkySnoop() as client:
        result = await client.get_in_circle(lat=37.7749, lon=-122.4194, radius=50)
        for aircraft in result.aircraft:
            print(f"{aircraft.hex}: {aircraft.callsign}")
except UnsupportedOperationError as e:
    print(f"Operation not supported by backend: {e}")
except TimeoutError as e:
    print(f"Request timed out: {e}")
except APIError as e:
    print(f"API error: {e}")
except ValidationError as e:
    print(f"Invalid parameters: {e}")
except SkySnoopError as e:
    print(f"General error: {e}")

Exception Hierarchy

  • SkySnoopError - Base exception for all library errors
    • APIError - HTTP/API errors (4xx, 5xx responses)
    • ValidationError - Invalid parameters or data
    • TimeoutError - Request timeout
    • UnsupportedOperationError - Operation not supported by the selected backend

You can catch all library errors with the base exception:

from skysnoop import SkySnoop
from skysnoop.exceptions import SkySnoopError

try:
    async with SkySnoop() as client:
        result = await client.get_in_circle(lat=37.7749, lon=-122.4194, radius=50)
        for aircraft in result.aircraft:
            print(f"{aircraft.hex}: {aircraft.callsign}")
except SkySnoopError as e:
    # Catches all library-specific errors
    print(f"SkySnoop error: {e}")

Configuration

Configure the library via environment variables or settings:

from skysnoop.settings import settings

# Settings can be overridden
settings.adsb_api_base_url = "https://re-api.adsb.lol/"
settings.adsb_api_timeout = 30.0
settings.cli_output_format = "table"  # or "json"

Or via environment variables:

export ADSB_API_BASE_URL="https://re-api.adsb.lol/"
export ADSB_API_TIMEOUT="30.0"
export CLI_OUTPUT_FORMAT="table"

Development

Setup Development Environment

git clone https://github.com/tedivm/skysnoop.git
cd skysnoop
make install

This will create a virtual environment at .venv and install all development dependencies.

Run Tests

# Run all tests with coverage
make pytest

# Run specific test with verbose output
make pytest_loud

# Run live API tests (requires API access from adsb.lol)
make pytest_live

Note: Live API tests require API access. For the OpenAPI access this is currently open to all with no API key, but may be restricted in the future. For the RE-API you must be feeding data to adsb.lol to have API access. See the About adsb.lol section above.

Code Quality

# Run all quality checks (tests + linting + type checking)
make tests

# Format code
make black_fixes

# Lint code
make ruff_check

# Type checking
make mypy_check

# Auto-fix linting issues
make ruff_fixes

# Run all formatting fixes
make chores

Data Format Notes

Important conventions:

  • Altitude: Can be an integer (in feet) or the string "ground" for aircraft on the ground
  • Distances: All distances are in nautical miles
  • Altitudes: All altitudes are in feet
  • Speeds: All speeds are in knots
  • Coordinates: Latitude/longitude in decimal degrees

Documentation

For complete documentation, see:

License

This project is licensed under the MIT License - see the LICENSE file for details.

Data from adsb.lol is available under the ODbl license.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

skysnoop-0.3.2.tar.gz (761.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

skysnoop-0.3.2-py3-none-any.whl (863.4 kB view details)

Uploaded Python 3

File details

Details for the file skysnoop-0.3.2.tar.gz.

File metadata

  • Download URL: skysnoop-0.3.2.tar.gz
  • Upload date:
  • Size: 761.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for skysnoop-0.3.2.tar.gz
Algorithm Hash digest
SHA256 ababd43182a928128c6fbb3d35bf312343b0472c1aa7f4d8a0220e36ac978994
MD5 6a640e1231c00dd05b522ef5af896478
BLAKE2b-256 53edaa50f883ed61b89715af9d6260f62325fc3be4017166635da54b2fd4a399

See more details on using hashes here.

Provenance

The following attestation bundles were made for skysnoop-0.3.2.tar.gz:

Publisher: pypi.yaml on tedivm/skysnoop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file skysnoop-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: skysnoop-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 863.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for skysnoop-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 788ba10e25bffeb5c7fc44c4735596d0136b44230084ffa04136d99499441cec
MD5 69d82c5f293329c79d0b4d55061e5de7
BLAKE2b-256 d80fce9a634212bb06b69c606c02cb4ee1b0e774474507b0af7e8bc6c846a9d5

See more details on using hashes here.

Provenance

The following attestation bundles were made for skysnoop-0.3.2-py3-none-any.whl:

Publisher: pypi.yaml on tedivm/skysnoop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page