Library Quickstart
Async¤
Most code is developed using asynchronous programming, enabling parallel execution of multiple queries and achieve high performance.
Here is a quick example in case you are not familiar with this code style:
import asyncio
from fr24.json import find
import httpx
async def main() -> None: # (1)!
async with httpx.AsyncClient() as client:
list_ = await find(client, "Toulouse") # (2)!
print(list_)
if __name__ == "__main__":
asyncio.run(main()) # (3)!
- Wrap your code in an
async
function find()
is an asynchronous function - we need toawait
itmain()
returns a coroutine object which does not run immediately - we need to run it on the event loop
from fr24.json import find
import httpx
async def main() -> None: # (1)!
async with httpx.AsyncClient() as client:
list_ = await find(client, "Toulouse") # (2)!
print(list_)
await main()
- Wrap your code in an
async
function find()
is an asynchronous function - we need toawait
it
{
"results": [
{
"id": "TLS",
"label": "Toulouse Blagnac Airport (TLS / LFBO)",
"detail": {"lat": 43.628101, "lon": 1.367263, "size": 33922},
"type": "airport",
"match": "begins",
},
# ...
]
}
The FR24
Class¤
Most of the core code are developed in many tiny functions for flexibility.
However, the FR24 class provides a convenient wrapper around them. It:
- Manages a shared HTTP client and authentication state
- Has three services:
- Live Feed: snapshot of all aircraft state vectors
- Flight List: all historical flights for a given aircraft registration or flight number
- Playback: historical trajectory for one flight.
Each service has its own async .fetch()
method to retrieve raw data from the API. .to_arrow()
can then be used to transform to an Apache Arrow table, and used to perform caching and downstream pandas
operations.
Here is an example for using the Live Feed service:
Initialisation¤
from fr24.core import FR24
async def my_feed() -> None:
async with FR24() as fr24:
response = await fr24.live_feed.fetch()
datac = response.to_arrow()
datac.save()
await my_feed()
LiveFeedAPIResp(
ctx={
"timestamp": 1711911907,
"source": "live",
"duration": None,
"hfreq": None,
"base_dir": PosixPath("/home/user/.cache/fr24"),
},
data=[
{
"timestamp": 1711911905,
"flightid": 882151247,
"latitude": -12.432657241821289,
"longitude": -172.14825439453125,
"track": 203,
"altitude": 34000,
"ground_speed": 515,
"vertical_speed": 0,
"on_ground": False,
"callsign": "QFA7552",
"source": 0,
"registration": "N409MC",
"origin": "HNL",
"destination": "AKL",
"typecode": "B744",
"eta": 0,
}
# ... 15109 more items
],
)
timestamp flightid latitude longitude track altitude ground_speed on_ground callsign source registration origin destination typecode eta vertical_speed
0 1711911905 882151247 -12.432657 -172.148254 203 34000 515 False QFA7552 0 N409MC HNL AKL B744 0 0
1 1711911902 882203620 -16.504490 -178.940308 249 36000 460 False VOZ76 0 VH-YIL APW BNE B738 0 0
2 1711911904 882212062 -13.240505 -176.195602 292 0 0 True RLY0100 0 F-OCQZ WLS DHC6 0 0
3 1711911897 882145424 10.347591 -167.007263 56 37000 516 False QFA15 5 VH-EBQ BNE LAX A332 0 0
4 1711911905 882199081 18.591330 -165.391083 247 32000 416 False UAL132 3 N77296 HNL MAJ B738 0 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
15095 1711911897 882140495 53.746582 174.467392 258 38000 435 False GTI8650 5 N710GT LAX HKG B77L 0 0
15096 1711911887 882155955 54.291172 175.606842 57 31000 515 False UPS81 5 N628UP PVG ANC B748 0 0
15097 1711911673 882165589 57.548691 179.460800 246 36000 438 False KAL258 4 HL8285 ANC ICN B77L 0 0
15098 1711911901 882187089 56.527115 176.298798 242 38000 442 False CKS936 4 N701CK ANC HFE B744 0 0
15099 1711911905 882137160 59.153641 179.730972 229 38000 471 False KAL8286 5 HL8043 YYZ ICN B77L 0 0
[15100 rows x 16 columns]
When FR24()
is first initialised, it creates an unauthenticated HTTPX client under the hood.
How to authenticate?
from fr24.core import FR24
async def main() -> None:
async with FR24() as fr24:
# anonymous now
await fr24.login() # reads from environment or configuration file, or,
await fr24.login(creds={"username": "...", "password": "..."}) # or,
await fr24.login(creds={"subscriptionKey": "...", "token": "..."})
See authentication for more details.
How to pass in my own HTTPX client?
To share clients across code, pass it into the fr24.core.FR24 constructor.
import httpx
from fr24.core import FR24
client = httpx.AsyncClient(http1=False, http2=True, transport=httpx.AsyncHTTPTransport(retries=5))
async def main() -> None:
async with FR24(client) as fr24:
...
The async with
statement ensures that it is properly authenticated by calling the login endpoint (if necessary).
Fetching from API¤
from fr24.core import FR24
async def my_feed() -> None:
async with FR24() as fr24:
response = await fr24.live_feed.fetch()
datac = response.to_arrow()
datac.save()
await my_feed()
LiveFeedAPIResp(
ctx={
"timestamp": 1711911907,
"source": "live",
"duration": None,
"hfreq": None,
"base_dir": PosixPath("/home/user/.cache/fr24"),
},
data=[
{
"timestamp": 1711911905,
"flightid": 882151247,
"latitude": -12.432657241821289,
"longitude": -172.14825439453125,
"track": 203,
"altitude": 34000,
"ground_speed": 515,
"vertical_speed": 0,
"on_ground": False,
"callsign": "QFA7552",
"source": 0,
"registration": "N409MC",
"origin": "HNL",
"destination": "AKL",
"typecode": "B744",
"eta": 0,
}
# ... 15109 more items
],
)
timestamp flightid latitude longitude track altitude ground_speed on_ground callsign source registration origin destination typecode eta vertical_speed
0 1711911905 882151247 -12.432657 -172.148254 203 34000 515 False QFA7552 0 N409MC HNL AKL B744 0 0
1 1711911902 882203620 -16.504490 -178.940308 249 36000 460 False VOZ76 0 VH-YIL APW BNE B738 0 0
2 1711911904 882212062 -13.240505 -176.195602 292 0 0 True RLY0100 0 F-OCQZ WLS DHC6 0 0
3 1711911897 882145424 10.347591 -167.007263 56 37000 516 False QFA15 5 VH-EBQ BNE LAX A332 0 0
4 1711911905 882199081 18.591330 -165.391083 247 32000 416 False UAL132 3 N77296 HNL MAJ B738 0 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
15095 1711911897 882140495 53.746582 174.467392 258 38000 435 False GTI8650 5 N710GT LAX HKG B77L 0 0
15096 1711911887 882155955 54.291172 175.606842 57 31000 515 False UPS81 5 N628UP PVG ANC B748 0 0
15097 1711911673 882165589 57.548691 179.460800 246 36000 438 False KAL258 4 HL8285 ANC ICN B77L 0 0
15098 1711911901 882187089 56.527115 176.298798 242 38000 442 False CKS936 4 N701CK ANC HFE B744 0 0
15099 1711911905 882137160 59.153641 179.730972 229 38000 471 False KAL8286 5 HL8043 YYZ ICN B77L 0 0
[15100 rows x 16 columns]
fr24.live_feed
returns a LiveFeedService, with the following methods:
Method | Return type |
---|---|
.fetch - asynchronously query the the API for fresh data |
fr24.core.LiveFeedAPIResp |
.load - load a previously cached snapshot from the disk |
fr24.core.LiveFeedArrow |
You can retrieve:
- the context related to the request with
response.ctx
; - the raw JSON response as a list of typed dictionaries with
response.data
.
Transformation to Arrow¤
In practice, you could directly pipe response.data
into pd.DataFrame.from_records()
, but pandas
uses 64-bit integers by default and can be storage-inefficient.
from fr24.core import FR24
async def my_feed() -> None:
async with FR24() as fr24:
response = await fr24.live_feed.fetch()
datac = response.to_arrow()
datac.save()
await my_feed()
LiveFeedAPIResp(
ctx={
"timestamp": 1711911907,
"source": "live",
"duration": None,
"hfreq": None,
"base_dir": PosixPath("/home/user/.cache/fr24"),
},
data=[
{
"timestamp": 1711911905,
"flightid": 882151247,
"latitude": -12.432657241821289,
"longitude": -172.14825439453125,
"track": 203,
"altitude": 34000,
"ground_speed": 515,
"vertical_speed": 0,
"on_ground": False,
"callsign": "QFA7552",
"source": 0,
"registration": "N409MC",
"origin": "HNL",
"destination": "AKL",
"typecode": "B744",
"eta": 0,
}
# ... 15109 more items
],
)
timestamp flightid latitude longitude track altitude ground_speed on_ground callsign source registration origin destination typecode eta vertical_speed
0 1711911905 882151247 -12.432657 -172.148254 203 34000 515 False QFA7552 0 N409MC HNL AKL B744 0 0
1 1711911902 882203620 -16.504490 -178.940308 249 36000 460 False VOZ76 0 VH-YIL APW BNE B738 0 0
2 1711911904 882212062 -13.240505 -176.195602 292 0 0 True RLY0100 0 F-OCQZ WLS DHC6 0 0
3 1711911897 882145424 10.347591 -167.007263 56 37000 516 False QFA15 5 VH-EBQ BNE LAX A332 0 0
4 1711911905 882199081 18.591330 -165.391083 247 32000 416 False UAL132 3 N77296 HNL MAJ B738 0 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
15095 1711911897 882140495 53.746582 174.467392 258 38000 435 False GTI8650 5 N710GT LAX HKG B77L 0 0
15096 1711911887 882155955 54.291172 175.606842 57 31000 515 False UPS81 5 N628UP PVG ANC B748 0 0
15097 1711911673 882165589 57.548691 179.460800 246 36000 438 False KAL258 4 HL8285 ANC ICN B77L 0 0
15098 1711911901 882187089 56.527115 176.298798 242 38000 442 False CKS936 4 N701CK ANC HFE B744 0 0
15099 1711911905 882137160 59.153641 179.730972 229 38000 471 False KAL8286 5 HL8043 YYZ ICN B77L 0 0
[15100 rows x 16 columns]
Instead, you can call .to_arrow()
, which creates a new
strongly typed Apache Arrow table from it.
Arrow is a columnar data storage format
with excellent interoperability
with pd.DataFrame
. You can retrieve:
- the context related to the request with
datac.ctx
; - the underlying Arrow table with
datac.data
; - get the pandas representation with
datac.df
.
Saving to Disk¤
from fr24.core import FR24
async def my_feed() -> None:
async with FR24() as fr24:
response = await fr24.live_feed.fetch()
datac = response.to_arrow()
datac.save()
await my_feed()
LiveFeedAPIResp(
ctx={
"timestamp": 1711911907,
"source": "live",
"duration": None,
"hfreq": None,
"base_dir": PosixPath("/home/user/.cache/fr24"),
},
data=[
{
"timestamp": 1711911905,
"flightid": 882151247,
"latitude": -12.432657241821289,
"longitude": -172.14825439453125,
"track": 203,
"altitude": 34000,
"ground_speed": 515,
"vertical_speed": 0,
"on_ground": False,
"callsign": "QFA7552",
"source": 0,
"registration": "N409MC",
"origin": "HNL",
"destination": "AKL",
"typecode": "B744",
"eta": 0,
}
# ... 15109 more items
],
)
timestamp flightid latitude longitude track altitude ground_speed on_ground callsign source registration origin destination typecode eta vertical_speed
0 1711911905 882151247 -12.432657 -172.148254 203 34000 515 False QFA7552 0 N409MC HNL AKL B744 0 0
1 1711911902 882203620 -16.504490 -178.940308 249 36000 460 False VOZ76 0 VH-YIL APW BNE B738 0 0
2 1711911904 882212062 -13.240505 -176.195602 292 0 0 True RLY0100 0 F-OCQZ WLS DHC6 0 0
3 1711911897 882145424 10.347591 -167.007263 56 37000 516 False QFA15 5 VH-EBQ BNE LAX A332 0 0
4 1711911905 882199081 18.591330 -165.391083 247 32000 416 False UAL132 3 N77296 HNL MAJ B738 0 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
15095 1711911897 882140495 53.746582 174.467392 258 38000 435 False GTI8650 5 N710GT LAX HKG B77L 0 0
15096 1711911887 882155955 54.291172 175.606842 57 31000 515 False UPS81 5 N628UP PVG ANC B748 0 0
15097 1711911673 882165589 57.548691 179.460800 246 36000 438 False KAL258 4 HL8285 ANC ICN B77L 0 0
15098 1711911901 882187089 56.527115 176.298798 242 38000 442 False CKS936 4 N701CK ANC HFE B744 0 0
15099 1711911905 882137160 59.153641 179.730972 229 38000 471 False KAL8286 5 HL8043 YYZ ICN B77L 0 0
[15100 rows x 16 columns]
.save()
writes the table to the default cache directory1 using the Parquet
data format.
You can always check its exact location using datac.fp
. In general, where it gets saved is as follows:
Storage Location¤
- Live feed
feed/{timestamp}.parquet
- Flight list
flight_list/reg/{reg.upper()}.parquet
, orflight_list/flight/{iata_flight_num.upper()}.parquet
- Playback
playback/{fr24_hex_id.lower()}.parquet
It should resemble the following on Linux:
$ tree $HOME/.cache/fr24/feed
/home/user/.cache/fr24
├── feed
│ ├── 1711911907.parquet
├── flight_list
│ ├── flight
│ │ └── CX8747.parquet
│ └── reg
│ └── B-HUJ.parquet
└── playback
└── 2d81a27.parquet
datac.save()
is called.
Reading from disk¤
from fr24.core import FR24
async def my_feed() -> None:
async with FR24() as fr24:
datac = fr24.live_feed.load(1711911907)
await my_feed()
timestamp flightid latitude longitude track altitude ground_speed on_ground callsign source registration origin destination typecode eta vertical_speed
0 1711911905 882151247 -12.432657 -172.148254 203 34000 515 False QFA7552 0 N409MC HNL AKL B744 0 0
1 1711911902 882203620 -16.504490 -178.940308 249 36000 460 False VOZ76 0 VH-YIL APW BNE B738 0 0
2 1711911904 882212062 -13.240505 -176.195602 292 0 0 True RLY0100 0 F-OCQZ WLS DHC6 0 0
3 1711911897 882145424 10.347591 -167.007263 56 37000 516 False QFA15 5 VH-EBQ BNE LAX A332 0 0
4 1711911905 882199081 18.591330 -165.391083 247 32000 416 False UAL132 3 N77296 HNL MAJ B738 0 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
15095 1711911897 882140495 53.746582 174.467392 258 38000 435 False GTI8650 5 N710GT LAX HKG B77L 0 0
15096 1711911887 882155955 54.291172 175.606842 57 31000 515 False UPS81 5 N628UP PVG ANC B748 0 0
15097 1711911673 882165589 57.548691 179.460800 246 36000 438 False KAL258 4 HL8285 ANC ICN B77L 0 0
15098 1711911901 882187089 56.527115 176.298798 242 38000 442 False CKS936 4 N701CK ANC HFE B744 0 0
15099 1711911905 882137160 59.153641 179.730972 229 38000 471 False KAL8286 5 HL8043 YYZ ICN B77L 0 0
[15100 rows x 16 columns]
To retrieve saved data, first pass in the unique identifier (timestamp in this case) to the .load()
.
Notes¤
Each service inherits from the fr24.base.ServiceBase and have similar APIs demonstrated above.
See the examples gallery to learn more.
Tip
Pyarrow unfortunately do not provide type hints. You can however, generate the stubs to your site-packages
directory with:
$ stubgen -p pyarrow -o $(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
Intersphinx for this project could be found here.
-
You can check its location by running
fr24 dirs
in the shell. ↩