Skip to main content

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

pip install aiotransperth

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

Metadata

Release files for aiotransperth 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aiotransperth 0.2.0
File Size Uploaded Provenance
aiotransperth-0.2.0.tar.gz 28.0 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for aiotransperth 0.2.0
File Interpreter ABI Platform
aiotransperth-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 46.2 kB

Release files / aiotransperth-0.2.0.tar.gz

Download URL aiotransperth-0.2.0.tar.gz
Size 28.0 kB
Tags Source
SHA-256 checksum
How to use checksums
9adfbef8090b3c3cd670e5f9588ded502688c82d47e7c84f88c7bb23e330b2d1
BLAKE2b-256 checksum
How to use checksums
bf3864f22183d3fcb78087d9b9a58cf04688476d16c81a8744ede5d6ae58bc1e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.

Transparency log

Release files / aiotransperth-0.2.0-py3-none-any.whl

Download URL aiotransperth-0.2.0-py3-none-any.whl
Size 18.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
745780941df6e663b8b4b895b8ca3dae7f19098c9ef2fc89e91ec9535d1a9b2d
BLAKE2b-256 checksum
How to use checksums
c832c38cadf7a905fec2c2e35c6a1380f0a0efa967f7ba714053b425cdb92bb0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.

Transparency log

Security for aiotransperth 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for aiotransperth-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page