Skip to main content

Async Python client for Transperth (Perth, WA) bus and train departures, with realtime data

Project description

aiotransperth

Live Perth bus and train departures in Python — including realtime delays.

Transperth (Perth, Western Australia's public transport operator) publishes no official realtime API. Live delay data exists in exactly one place: the Transperth website itself. This library talks to the same internal endpoints the website uses and hands you the results as plain, typed Python objects — the only way to get Perth realtime data into your own code.

  • Buses — upcoming departures at any stop, live delays for buses already on the road, and stop-by-stop trip details.
  • Trains — live departures for any station: destination, platform, delay status, car count.

Async (aiohttp is the only dependency), fully typed, Python 3.12+, MIT.

Getting started

Not on PyPI yet — install from a checkout:

git clone https://github.com/Chris112/aiotransperth
cd aiotransperth
pip install .

Then:

import asyncio

from aiotransperth import TransperthClient


async def main() -> None:
    async with TransperthClient() as client:
        # ---- Buses: next departures at stop 12627 ----
        timetable = await client.get_stop_timetable("12627")
        print(f"Stop {timetable.stop.code}: {timetable.stop.name}")
        for dep in timetable.departures[:3]:
            when = dep.estimated or dep.scheduled  # live estimate if available
            live = f"live, {dep.live.description}" if dep.live.is_live else "scheduled"
            print(f"  {dep.route:>4} to {dep.headsign:<20} {when:%H:%M} ({live})")

        # ---- Trains: live status at Maylands on the Midland line ----
        trains = await client.get_train_departures("Midland Line", "Maylands Stn")
        print("Maylands Stn (Midland Line):")
        for train in trains[:3]:
            when = train.estimated or train.scheduled
            print(
                f"  to {train.destination:<8} plat {train.platform} "
                f"{when:%H:%M} ({train.live.description}, {train.cars} cars)"
            )


asyncio.run(main())

Real output (Monday afternoon, Perth):

Stop 12627: Main St After Royal St
   404 to Tuart Hill           15:20 (scheduled)
   402 to Perth Busport        15:22 (scheduled)
   414 to Glendalough Stn      15:33 (scheduled)
Maylands Stn (Midland Line):
  to Midland  plat 2 14:50 (On Time, 4 cars)
  to Perth    plat 1 14:57 (On Time, 4 cars)
  to Midland  plat 2 15:01 (On Time, 4 cars)

When a bus is live-tracked (typically the imminent departure), dep.live.is_live is true, dep.estimated carries the actual expected time, and dep.delay_minutes the difference — e.g. 3 for a bus running three minutes late. Trains are always live-tracked while services run.

Finding your inputs

  • Bus stop code — the 5-digit number printed on the physical stop sign, also shown when you click a stop in the Transperth journey planner. Verify one with await client.validate_stop("12627"), which returns the stop's name or raises InvalidStopError.

  • Train line and station names — display names exactly as the Transperth site spells them: lines like "Midland Line", stations like "Maylands Stn". Don't guess; fetch the catalog:

    lines = await client.get_train_lines()        # ("Airport Line", ..., "Yanchep Line")
    stations = await client.get_train_stations()  # (TrainStation(id="130", name="Maylands Stn"), ...)
    

API

All methods are coroutines on TransperthClient. Construct it bare (it manages its own aiohttp session as an async context manager) or pass your own session, which will not be closed for you.

Method Returns Notes
get_stop_timetable(stop_code, *, when=None, max_trips=100) StopTimetable (stop + BusDeparture tuple) Realtime always requested. when defaults to now (Perth).
validate_stop(stop_code) Stop Raises InvalidStopError for unknown codes.
get_route_trips(route, *, when=None, max_options=4) tuple[RouteTrip, ...] Upcoming trips for a route, e.g. "414".
get_trip_stops(trip) tuple[TripStop, ...] Every stop on a trip, with times, boarding flags, GPS.
get_train_departures(line, station) tuple[TrainDeparture, ...] Live status; raises InvalidStopError for unknown stations.
get_train_lines() tuple[str, ...] The 8 line names. Fetched once, cached per client.
get_train_stations() tuple[TrainStation, ...] All ~80 stations. Fetched once, cached per client.

Departure models share a core: scheduled and estimated (aware datetimes; estimated is None when not live), live (a LiveStatus with is_live, raw status_code, and a human description), and a delay_minutes property. Buses add route, headsign, origin, destination, trip_uid; trains add line, destination, platform, cars, pattern, trip_id.

Errors

Everything raises a subclass of TransperthError:

Exception Meaning What to do
RateLimitError HTTP 429 — Transperth's cooldown is sticky (>60 s) and shared with their public website Back off for minutes, not seconds
AuthError A CSRF token couldn't be obtained or was rejected (bus endpoints only) Usually transient; the client already retried once
NetworkError DNS/timeout/connection failure Retry with your own policy
InvalidStopError Unknown stop code or station name Fix the input

The client itself never sleeps and never retries beyond one automatic token refresh on HTTP 401 — back-off policy belongs to the caller.

Data caveats

  • This is an unofficial API; Transperth can change or break it without notice. A -m live contract test suite pins the shapes this library reads.
  • Individual departures the API garbles are skipped, not raised — a response with one malformed entry still returns the rest.
  • Bus realtime only lights up for vehicles already on the road — usually the next imminent departure. Everything else is scheduled times.
  • The bus timetable endpoint self-caps its response window (~2 hours of departures) regardless of max_trips.
  • Be respectful: one or two requests per query, no tight polling loops.

Development

pip install -e ".[dev]"
pytest                # offline tests only (fixtures, no network)
pytest -m live        # contract tests against the real API — run sparingly
ruff check src tests && mypy src

The package version lives in src/aiotransperth/__init__.py (__version__); pyproject.toml reads it from there, so bump it in one place only.

License

MIT

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

aiotransperth-0.1.0.tar.gz (19.1 kB view details)

Uploaded Source

Built Distribution

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

aiotransperth-0.1.0-py3-none-any.whl (14.5 kB view details)

Uploaded Python 3

File details

Details for the file aiotransperth-0.1.0.tar.gz.

File metadata

  • Download URL: aiotransperth-0.1.0.tar.gz
  • Upload date:
  • Size: 19.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aiotransperth-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9c9df50886357a72f94683b005fd720dbfc1d00d91283d0f75ab24c0ef3897bc
MD5 2670e3196f4b1fb35520c2fac676c0bd
BLAKE2b-256 64d7dab9657a29f24e005d0f748c4bb06ab890b51f5f32889be298e77fcc5121

See more details on using hashes here.

Provenance

The following attestation bundles were made for aiotransperth-0.1.0.tar.gz:

Publisher: release.yml on Chris112/aiotransperth

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

File details

Details for the file aiotransperth-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: aiotransperth-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aiotransperth-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 980431d3a12f24e89da97d5e3681c24429f6ecd79573e8b085d074d7efd6127b
MD5 a3434cf8c6ce9ce405d1bd23e02241d4
BLAKE2b-256 4d919c7c8b8e641df6d76fd1abca6a2e7018f344d0a19dcee6798a3186325306

See more details on using hashes here.

Provenance

The following attestation bundles were made for aiotransperth-0.1.0-py3-none-any.whl:

Publisher: release.yml on Chris112/aiotransperth

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