Skip to content

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)!
  1. Wrap your code in an async function
  2. find() is an asynchronous function - we need to await it
  3. main() 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()
  1. Wrap your code in an async function
  2. find() is an asynchronous function - we need to await 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:

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:

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, or
    • flight_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
These directories are created automatically whenever 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.


  1. You can check its location by running fr24 dirs in the shell