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
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 raisesInvalidStopError. -
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 livecontract 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aiotransperth-0.2.0.tar.gz.
File metadata
- Download URL: aiotransperth-0.2.0.tar.gz
- Upload date:
- Size: 28.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9adfbef8090b3c3cd670e5f9588ded502688c82d47e7c84f88c7bb23e330b2d1
|
|
| MD5 |
766038181f9e3c77b7c006c9f201b3ee
|
|
| BLAKE2b-256 |
bf3864f22183d3fcb78087d9b9a58cf04688476d16c81a8744ede5d6ae58bc1e
|
Provenance
The following attestation bundles were made for aiotransperth-0.2.0.tar.gz:
Publisher:
release.yml on Chris112/aiotransperth
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aiotransperth-0.2.0.tar.gz -
Subject digest:
9adfbef8090b3c3cd670e5f9588ded502688c82d47e7c84f88c7bb23e330b2d1 - Sigstore transparency entry: 2248911228
- Sigstore integration time:
-
Permalink:
Chris112/aiotransperth@7e42354d6fff8aaddc6ad9c95c79e2b19b93c532 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Chris112
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7e42354d6fff8aaddc6ad9c95c79e2b19b93c532 -
Trigger Event:
release
-
Statement type:
File details
Details for the file aiotransperth-0.2.0-py3-none-any.whl.
File metadata
- Download URL: aiotransperth-0.2.0-py3-none-any.whl
- Upload date:
- Size: 18.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
745780941df6e663b8b4b895b8ca3dae7f19098c9ef2fc89e91ec9535d1a9b2d
|
|
| MD5 |
e01fb3473028a2411edcc354d46b61d1
|
|
| BLAKE2b-256 |
c832c38cadf7a905fec2c2e35c6a1380f0a0efa967f7ba714053b425cdb92bb0
|
Provenance
The following attestation bundles were made for aiotransperth-0.2.0-py3-none-any.whl:
Publisher:
release.yml on Chris112/aiotransperth
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aiotransperth-0.2.0-py3-none-any.whl -
Subject digest:
745780941df6e663b8b4b895b8ca3dae7f19098c9ef2fc89e91ec9535d1a9b2d - Sigstore transparency entry: 2248911537
- Sigstore integration time:
-
Permalink:
Chris112/aiotransperth@7e42354d6fff8aaddc6ad9c95c79e2b19b93c532 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Chris112
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7e42354d6fff8aaddc6ad9c95c79e2b19b93c532 -
Trigger Event:
release
-
Statement type: