Skip to content

Commit ea27efd

Browse files
authored
flight categories (#39)
1 parent 8c6d809 commit ea27efd

5 files changed

Lines changed: 82 additions & 8 deletions

File tree

examples/basic_usage.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from datetime import datetime, timezone
1414

1515
from fr24sdk.client import Client
16+
from fr24sdk.models.flight_category import FlightCategory
1617
from fr24sdk.models.geographic import AltitudeRange, Boundary
1718

1819
# Configure basic logging to see SDK informational messages
@@ -31,6 +32,7 @@ def main() -> None:
3132
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))
3233
if flight_summary.data:
3334
print(flight_summary.data[0].fr24_id)
35+
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]))
3436
print(client.live.flight_positions.get_light(flights=["SK2752"]))
3537
print(client.live.flight_positions.get_light(bounds=Boundary(north=55.6, south=55.5, west=12.5, east=12.6)))
3638
print(client.live.flight_positions.get_full(bounds="55.6,55.5,12.5,12.6"))

src/fr24sdk/models/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# SPDX-License-Identifier: MIT
44
"""Exposes data models for the Flightradar24 SDK."""
55

6-
from .flight_category import FlightCategory
6+
from .flight_category import FlightCategory, FlightCategoryCode
77
from .airline import AirlineLight
88
from .airport import AirportFull, AirportLight, Country, Timezone
99
from .flight import (
@@ -29,6 +29,7 @@
2929
__all__ = [
3030
"AirlineLight",
3131
"FlightCategory",
32+
"FlightCategoryCode",
3233
"AirportFull",
3334
"AirportLight",
3435
"Country",

src/fr24sdk/models/flight_category.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,44 @@
44
"""Flight category enumeration for the Flightradar24 SDK."""
55

66
from enum import Enum
7+
from typing import Literal
8+
9+
FlightCategoryCode = Literal[
10+
"P",
11+
"C",
12+
"M",
13+
"J",
14+
"T",
15+
"H",
16+
"B",
17+
"G",
18+
"D",
19+
"V",
20+
"O",
21+
"N",
22+
]
723

824

925
class FlightCategory(str, Enum):
10-
"""Enumeration of FlightRadar24 flight categories.
11-
12-
Maps character literals used by the FR24 API to identify different
13-
types of aircraft and flight operations.
26+
"""Aircraft / vehicle category codes used in API (e.g. flight summary).
27+
28+
- **P** — **PASSENGER** — Commercial aircraft that carry passengers as their primary purpose.
29+
- **C** — **CARGO** — Aircraft that carry only cargo.
30+
- **M** — **MILITARY_AND_GOVERNMENT** — Aircraft operated by military or a governmental agency.
31+
- **J** — **BUSINESS_JETS** — Larger private aircraft, such as Gulfstream, Bombardier, and Pilatus.
32+
- **T** — **GENERAL_AVIATION** — Non-commercial transport flights, including private, ambulance,
33+
aerial survey, flight training and instrument calibration aircraft.
34+
- **H** — **HELICOPTERS** — Rotary wing aircraft.
35+
- **B** — **LIGHTER_THAN_AIR** — Lighter-than-air aircraft include gas-filled airships of all kinds.
36+
- **G** — **GLIDERS** — Unpowered aircraft.
37+
- **D** — **DRONES** — Uncrewed aircraft, ranging from small consumer drones to larger UAVs.
38+
- **V** — **GROUND_VEHICLES** — Transponder equipped vehicles, such as push-back tugs, fire trucks,
39+
and operations vehicles.
40+
- **O** — **OTHER** — Aircraft appearing on Flightradar24 not classified elsewhere
41+
(International Space Station, UFOs, Santa, etc).
42+
- **N** — **NON_CATEGORIZED** — Aircraft not yet placed into a category in the Flightradar24 database.
1443
"""
15-
44+
1645
PASSENGER = "P"
1746
CARGO = "C"
1847
MILITARY_AND_GOVERNMENT = "M"
@@ -28,3 +57,6 @@ class FlightCategory(str, Enum):
2857

2958
def __str__(self) -> str:
3059
return self.value
60+
61+
62+
__all__ = ["FlightCategory", "FlightCategoryCode"]

src/fr24sdk/resources/flight_summary.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
"""Resource class for flight summary data."""
55

66
import warnings
7-
from typing import Optional, Any, Annotated
7+
from typing import Optional, Any, Annotated, Union
88
from datetime import datetime
99
from pydantic import BaseModel, model_serializer, StringConstraints, Field
1010

1111
from ..transport import HttpTransport
12+
from ..models.flight_category import FlightCategory
1213
from ..models.flight import (
1314
FlightSummaryLightResponse,
1415
FlightSummaryFullResponse,
@@ -21,6 +22,7 @@
2122
AIRLINE_ICAO_PATTERN,
2223
AIRPORT_PARAM_PATTERN,
2324
ROUTE_PATTERN,
25+
SERVICE_TYPES_PATTERN,
2426
SORT_PATTERN,
2527
)
2628

@@ -53,6 +55,9 @@ class _FlightSummaryParams(BaseModel):
5355
Field(default=None, max_length=15)
5456
)
5557
aircraft: Optional[list[str]] = Field(default=None, max_length=15)
58+
categories: Optional[
59+
list[Union[FlightCategory, Annotated[str, StringConstraints(pattern=SERVICE_TYPES_PATTERN)]]]
60+
] = Field(default=None, max_length=15)
5661
sort: Optional[Annotated[str, StringConstraints(pattern=SORT_PATTERN)]] = None
5762
limit: Optional[Annotated[int, Field(ge=1, le=20000)]] = None
5863

@@ -97,6 +102,7 @@ def get_light(
97102
airports: Optional[list[str]] = None,
98103
routes: Optional[list[str]] = None,
99104
aircraft: Optional[list[str]] = None,
105+
categories: Optional[list[str]] = None,
100106
sort: Optional[str] = None,
101107
limit: Optional[int] = None,
102108
) -> FlightSummaryLightResponse:
@@ -118,6 +124,7 @@ def get_light(
118124
airports=airports,
119125
routes=routes,
120126
aircraft=aircraft,
127+
categories=categories,
121128
sort=sort,
122129
limit=limit,
123130
).model_dump(exclude_none=True)
@@ -140,6 +147,7 @@ def get_full(
140147
airports: Optional[list[str]] = None,
141148
routes: Optional[list[str]] = None,
142149
aircraft: Optional[list[str]] = None,
150+
categories: Optional[list[str]] = None,
143151
sort: Optional[str] = None,
144152
limit: Optional[int] = None,
145153
) -> FlightSummaryFullResponse:
@@ -159,6 +167,7 @@ def get_full(
159167
airports=airports,
160168
routes=routes,
161169
aircraft=aircraft,
170+
categories=categories,
162171
sort=sort,
163172
limit=limit,
164173
).model_dump(exclude_none=True)
@@ -181,6 +190,7 @@ def get_count(
181190
airports: Optional[list[str]] = None,
182191
routes: Optional[list[str]] = None,
183192
aircraft: Optional[list[str]] = None,
193+
categories: Optional[list[str]] = None,
184194
) -> CountResponse:
185195
"""Return the number of flight-summary records that match the filters.
186196
@@ -199,6 +209,7 @@ def get_count(
199209
airports=airports,
200210
routes=routes,
201211
aircraft=aircraft,
212+
categories=categories,
202213
).model_dump(exclude_none=True)
203214
response = self._transport.request(
204215
"GET", f"{self.BASE_PATH}/count", params=params
@@ -219,6 +230,7 @@ def count(
219230
airports: Optional[list[str]] = None,
220231
routes: Optional[list[str]] = None,
221232
aircraft: Optional[list[str]] = None,
233+
categories: Optional[list[str]] = None,
222234
) -> CountResponse:
223235
"""Deprecated alias for :meth:`get_count`."""
224236
warnings.warn(
@@ -239,4 +251,5 @@ def count(
239251
airports=airports,
240252
routes=routes,
241253
aircraft=aircraft,
254+
categories=categories,
242255
)

tests/unit/test_flight_summary.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,31 @@
99

1010
from fr24sdk.resources.flight_summary import FlightSummaryResource, _FlightSummaryParams
1111
from fr24sdk.models.flight import (
12+
FlightSummaryFull,
1213
FlightSummaryLightResponse,
1314
FlightSummaryFullResponse,
1415
CountResponse,
1516
)
17+
from fr24sdk.models.flight_category import FlightCategory
1618
from fr24sdk.transport import HttpTransport
1719

1820

21+
class TestFlightSummaryFullCategory:
22+
"""Parsing of ``category`` on :class:`FlightSummaryFull`."""
23+
24+
def test_known_code_coerces_to_enum(self) -> None:
25+
row = FlightSummaryFull.model_validate({"fr24_id": "abc", "category": "M"})
26+
assert row.category == FlightCategory.MILITARY_AND_GOVERNMENT
27+
28+
def test_unknown_code_left_as_str(self) -> None:
29+
row = FlightSummaryFull.model_validate({"fr24_id": "abc", "category": "X"})
30+
assert row.category == "X"
31+
32+
def test_category_omitted_is_none(self) -> None:
33+
row = FlightSummaryFull.model_validate({"fr24_id": "abc"})
34+
assert row.category is None
35+
36+
1937
class TestFlightSummaryParams:
2038
"""Test the _FlightSummaryParams class."""
2139

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

61+
params_with_cat = _FlightSummaryParams(
62+
flight_datetime_from=test_datetime,
63+
flight_datetime_to=test_datetime,
64+
categories=[FlightCategory.PASSENGER, "C"],
65+
)
66+
assert params_with_cat._to_query_dict()["categories"] == "P,C"
67+
4368
# Check that None values are not included
4469
assert "registrations" not in serialized
4570

@@ -162,6 +187,7 @@ def test_get_full_success(self, flight_summary, mock_transport):
162187
"flight_time": 3600,
163188
"actual_distance": 250.5,
164189
"circle_distance": 230.0,
190+
"category": "P",
165191
"hex": "40123A",
166192
"first_seen": "2023-01-01T09:45:00Z",
167193
"last_seen": "2023-01-01T11:15:00Z",
@@ -179,13 +205,13 @@ def test_get_full_success(self, flight_summary, mock_transport):
179205
result = flight_summary.get_full(flight_ids=["35f2ffd9"])
180206

181207
# Verify results
182-
print(result)
183208
assert isinstance(result, FlightSummaryFullResponse)
184209
assert len(result.data) == 1
185210
assert result.data[0].fr24_id == "35f2ffd9"
186211
assert result.data[0].flight == "BA1234"
187212
assert result.data[0].runway_takeoff == "27L"
188213
assert result.data[0].flight_time == 3600
214+
assert result.data[0].category == FlightCategory.PASSENGER
189215

190216
# Verify mock was called correctly
191217
mock_transport.request.assert_called_once()

0 commit comments

Comments
 (0)