Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/basic_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from datetime import datetime, timezone

from fr24sdk.client import Client
from fr24sdk.models.flight_category import FlightCategory
from fr24sdk.models.geographic import AltitudeRange, Boundary

# Configure basic logging to see SDK informational messages
Expand All @@ -31,6 +32,7 @@ def main() -> None:
flight_summary = client.flight_summary.get_light(flights=["KL1316"], flight_datetime_from=datetime(2025, 5, 13, 12, 0, 0), flight_datetime_to=datetime(2025, 5, 14, 17, 10, 0))
if flight_summary.data:
print(flight_summary.data[0].fr24_id)
print(client.flight_summary.get_full(operating_as=["FDX"],flight_datetime_from=datetime(2026, 1, 15, 15, 0, 0),flight_datetime_to=datetime(2026, 1, 15, 15, 5, 0),categories=[FlightCategory.CARGO]))
print(client.live.flight_positions.get_light(flights=["SK2752"]))
print(client.live.flight_positions.get_light(bounds=Boundary(north=55.6, south=55.5, west=12.5, east=12.6)))
print(client.live.flight_positions.get_full(bounds="55.6,55.5,12.5,12.6"))
Expand Down
3 changes: 2 additions & 1 deletion src/fr24sdk/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# SPDX-License-Identifier: MIT
"""Exposes data models for the Flightradar24 SDK."""

from .flight_category import FlightCategory
from .flight_category import FlightCategory, FlightCategoryCode
from .airline import AirlineLight
from .airport import AirportFull, AirportLight, Country, Timezone
from .flight import (
Expand All @@ -29,6 +29,7 @@
__all__ = [
"AirlineLight",
"FlightCategory",
"FlightCategoryCode",
"AirportFull",
"AirportLight",
"Country",
Expand Down
42 changes: 37 additions & 5 deletions src/fr24sdk/models/flight_category.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,44 @@
"""Flight category enumeration for the Flightradar24 SDK."""

from enum import Enum
from typing import Literal

FlightCategoryCode = Literal[
"P",
"C",
"M",
"J",
"T",
"H",
"B",
"G",
"D",
"V",
"O",
"N",
]


class FlightCategory(str, Enum):
"""Enumeration of FlightRadar24 flight categories.

Maps character literals used by the FR24 API to identify different
types of aircraft and flight operations.
"""Aircraft / vehicle category codes used in API (e.g. flight summary).

- **P** — **PASSENGER** — Commercial aircraft that carry passengers as their primary purpose.
- **C** — **CARGO** — Aircraft that carry only cargo.
- **M** — **MILITARY_AND_GOVERNMENT** — Aircraft operated by military or a governmental agency.
- **J** — **BUSINESS_JETS** — Larger private aircraft, such as Gulfstream, Bombardier, and Pilatus.
- **T** — **GENERAL_AVIATION** — Non-commercial transport flights, including private, ambulance,
aerial survey, flight training and instrument calibration aircraft.
- **H** — **HELICOPTERS** — Rotary wing aircraft.
- **B** — **LIGHTER_THAN_AIR** — Lighter-than-air aircraft include gas-filled airships of all kinds.
- **G** — **GLIDERS** — Unpowered aircraft.
- **D** — **DRONES** — Uncrewed aircraft, ranging from small consumer drones to larger UAVs.
- **V** — **GROUND_VEHICLES** — Transponder equipped vehicles, such as push-back tugs, fire trucks,
and operations vehicles.
- **O** — **OTHER** — Aircraft appearing on Flightradar24 not classified elsewhere
(International Space Station, UFOs, Santa, etc).
- **N** — **NON_CATEGORIZED** — Aircraft not yet placed into a category in the Flightradar24 database.
"""

PASSENGER = "P"
CARGO = "C"
MILITARY_AND_GOVERNMENT = "M"
Expand All @@ -28,3 +57,6 @@ class FlightCategory(str, Enum):

def __str__(self) -> str:
return self.value


__all__ = ["FlightCategory", "FlightCategoryCode"]
15 changes: 14 additions & 1 deletion src/fr24sdk/resources/flight_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
"""Resource class for flight summary data."""

import warnings
from typing import Optional, Any, Annotated
from typing import Optional, Any, Annotated, Union
from datetime import datetime
from pydantic import BaseModel, model_serializer, StringConstraints, Field

from ..transport import HttpTransport
from ..models.flight_category import FlightCategory
from ..models.flight import (
FlightSummaryLightResponse,
FlightSummaryFullResponse,
Expand All @@ -21,6 +22,7 @@
AIRLINE_ICAO_PATTERN,
AIRPORT_PARAM_PATTERN,
ROUTE_PATTERN,
SERVICE_TYPES_PATTERN,
SORT_PATTERN,
)

Expand Down Expand Up @@ -53,6 +55,9 @@ class _FlightSummaryParams(BaseModel):
Field(default=None, max_length=15)
)
aircraft: Optional[list[str]] = Field(default=None, max_length=15)
categories: Optional[
list[Union[FlightCategory, Annotated[str, StringConstraints(pattern=SERVICE_TYPES_PATTERN)]]]
] = Field(default=None, max_length=15)
sort: Optional[Annotated[str, StringConstraints(pattern=SORT_PATTERN)]] = None
limit: Optional[Annotated[int, Field(ge=1, le=20000)]] = None

Expand Down Expand Up @@ -97,6 +102,7 @@ def get_light(
airports: Optional[list[str]] = None,
routes: Optional[list[str]] = None,
aircraft: Optional[list[str]] = None,
categories: Optional[list[str]] = None,
sort: Optional[str] = None,
limit: Optional[int] = None,
) -> FlightSummaryLightResponse:
Expand All @@ -118,6 +124,7 @@ def get_light(
airports=airports,
routes=routes,
aircraft=aircraft,
categories=categories,
sort=sort,
limit=limit,
).model_dump(exclude_none=True)
Expand All @@ -140,6 +147,7 @@ def get_full(
airports: Optional[list[str]] = None,
routes: Optional[list[str]] = None,
aircraft: Optional[list[str]] = None,
categories: Optional[list[str]] = None,
sort: Optional[str] = None,
limit: Optional[int] = None,
) -> FlightSummaryFullResponse:
Expand All @@ -159,6 +167,7 @@ def get_full(
airports=airports,
routes=routes,
aircraft=aircraft,
categories=categories,
sort=sort,
limit=limit,
).model_dump(exclude_none=True)
Expand All @@ -181,6 +190,7 @@ def get_count(
airports: Optional[list[str]] = None,
routes: Optional[list[str]] = None,
aircraft: Optional[list[str]] = None,
categories: Optional[list[str]] = None,
) -> CountResponse:
"""Return the number of flight-summary records that match the filters.

Expand All @@ -199,6 +209,7 @@ def get_count(
airports=airports,
routes=routes,
aircraft=aircraft,
categories=categories,
).model_dump(exclude_none=True)
response = self._transport.request(
"GET", f"{self.BASE_PATH}/count", params=params
Expand All @@ -219,6 +230,7 @@ def count(
airports: Optional[list[str]] = None,
routes: Optional[list[str]] = None,
aircraft: Optional[list[str]] = None,
categories: Optional[list[str]] = None,
) -> CountResponse:
"""Deprecated alias for :meth:`get_count`."""
warnings.warn(
Expand All @@ -239,4 +251,5 @@ def count(
airports=airports,
routes=routes,
aircraft=aircraft,
categories=categories,
)
28 changes: 27 additions & 1 deletion tests/unit/test_flight_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,31 @@

from fr24sdk.resources.flight_summary import FlightSummaryResource, _FlightSummaryParams
from fr24sdk.models.flight import (
FlightSummaryFull,
FlightSummaryLightResponse,
FlightSummaryFullResponse,
CountResponse,
)
from fr24sdk.models.flight_category import FlightCategory
from fr24sdk.transport import HttpTransport


class TestFlightSummaryFullCategory:
"""Parsing of ``category`` on :class:`FlightSummaryFull`."""

def test_known_code_coerces_to_enum(self) -> None:
row = FlightSummaryFull.model_validate({"fr24_id": "abc", "category": "M"})
assert row.category == FlightCategory.MILITARY_AND_GOVERNMENT

def test_unknown_code_left_as_str(self) -> None:
row = FlightSummaryFull.model_validate({"fr24_id": "abc", "category": "X"})
assert row.category == "X"

def test_category_omitted_is_none(self) -> None:
row = FlightSummaryFull.model_validate({"fr24_id": "abc"})
assert row.category is None


class TestFlightSummaryParams:
"""Test the _FlightSummaryParams class."""

Expand All @@ -40,6 +58,13 @@ def test_serialize_params(self):
assert serialized["callsigns"] == "BAW123"
assert serialized["aircraft"] == "B738,A320"

params_with_cat = _FlightSummaryParams(
flight_datetime_from=test_datetime,
flight_datetime_to=test_datetime,
categories=[FlightCategory.PASSENGER, "C"],
)
assert params_with_cat._to_query_dict()["categories"] == "P,C"

# Check that None values are not included
assert "registrations" not in serialized

Expand Down Expand Up @@ -162,6 +187,7 @@ def test_get_full_success(self, flight_summary, mock_transport):
"flight_time": 3600,
"actual_distance": 250.5,
"circle_distance": 230.0,
"category": "P",
"hex": "40123A",
"first_seen": "2023-01-01T09:45:00Z",
"last_seen": "2023-01-01T11:15:00Z",
Expand All @@ -179,13 +205,13 @@ def test_get_full_success(self, flight_summary, mock_transport):
result = flight_summary.get_full(flight_ids=["35f2ffd9"])

# Verify results
print(result)
assert isinstance(result, FlightSummaryFullResponse)
assert len(result.data) == 1
assert result.data[0].fr24_id == "35f2ffd9"
assert result.data[0].flight == "BA1234"
assert result.data[0].runway_takeoff == "27L"
assert result.data[0].flight_time == 3600
assert result.data[0].category == FlightCategory.PASSENGER

# Verify mock was called correctly
mock_transport.request.assert_called_once()
Expand Down
Loading