PyTransportNSWv2 0.9.2__tar.gz → 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/PKG-INFO +58 -46
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/PyTransportNSWv2.egg-info/PKG-INFO +58 -46
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/README.md +57 -45
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/TransportNSWv2/TransportNSWv2.py +80 -76
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/setup.py +1 -1
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/LICENSE +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/PyTransportNSWv2.egg-info/SOURCES.txt +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/PyTransportNSWv2.egg-info/TransportNSWv2.py +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/PyTransportNSWv2.egg-info/dependency_links.txt +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/PyTransportNSWv2.egg-info/requires.txt +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/PyTransportNSWv2.egg-info/top_level.txt +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/TransportNSWv2/__init__.py +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/setup.cfg +0 -0
@@ -1,32 +1,31 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: PyTransportNSWv2
|
3
|
-
Version: 0.
|
3
|
+
Version: 1.0.1
|
4
4
|
Summary: Get detailed per-trip transport information from TransportNSW
|
5
5
|
Home-page: https://github.com/andystewart999/TransportNSW
|
6
6
|
Author: andystewart999
|
7
7
|
Author-email: andy.stewart@live.com
|
8
8
|
License: UNKNOWN
|
9
9
|
Description: # TransportNSWv2
|
10
|
-
Python lib to access Transport NSW information.
|
10
|
+
Python lib to access Transport NSW stop and journey information.
|
11
11
|
|
12
12
|
## How to Use
|
13
13
|
|
14
14
|
### Get an API Key
|
15
|
-
An OpenData account and API key is required to request the data. More information on how to create the free account can be found here
|
16
|
-
https://opendata.transport.nsw.gov.au/user-guide. You need to register an application that needs both the Trip Planner and Realtime Vehicle Positions APIs
|
15
|
+
An OpenData account and API key is required to request the data. More information on how to create the free account can be found [here](https://opendata.transport.nsw.gov.au/user-guide). You need to register an application that needs both the Trip Planner and Realtime Vehicle Positions APIs.
|
17
16
|
|
18
17
|
### Get the stop IDs
|
19
|
-
The
|
18
|
+
The only mandatory parameters are the API key and the from/to stop IDs - the easiest way to get the stop ID is via https://transportnsw.info/stops#/ - that page provides the option to search for either a location or a specific platform, bus stop or ferry wharf. Regardless of if you specify a general location for the origin or destination, the return information shows the stop ID for the actual arrival and destination platform, bus stop or ferry wharf.
|
20
19
|
|
21
20
|
If it's available, the general occupancy level and the latitude and longitude of the selected journey's vehicle (train, bus, etc) will be returned, unless you specifically set ```include_realtime_location``` to ```False```.
|
22
21
|
|
23
22
|
### API Documentation
|
24
|
-
The source API details can be found here
|
23
|
+
The source Transport NSW API details can be found [here](https://opendata.transport.nsw.gov.au/sites/default/files/2023-08/Trip%20Planner%20API%20manual-opendataproduction%20v3.2.pdf).
|
25
24
|
|
26
25
|
### Exposed functions
|
27
26
|
Two functions are available:
|
28
27
|
```get_trip()``` returns trip information between two stop IDs
|
29
|
-
```check_stops()``` lets you confirm that the two stop IDs are valid, plus it returns all the stop ID metadata. Note that ```get_trip()``` calls this function internally and fails
|
28
|
+
```check_stops()``` lets you confirm that the two stop IDs are valid, plus it returns all the stop ID metadata. Note that ```get_trip()``` calls this function internally (unless you tell it not to) and fails with a ```StopError``` Exception if either of the stop IDs are invalid, so there's no specific need to call ```check_stops()``` unless you want the stop ID metadata, or know you'll be calling the same journey multiple times and want to reduce your daily API calls by pre-checking once.
|
30
29
|
|
31
30
|
### check_stops() parameters
|
32
31
|
All parameters are mandatory. Note that ```stop_list``` can be a single string or a list of strings:
|
@@ -63,8 +62,33 @@ Description: # TransportNSWv2
|
|
63
62
|
]
|
64
63
|
}
|
65
64
|
```
|
66
|
-
Most of the top-level properties are pretty self-explanatory. If all you want to do is get a general yes/no then ```all_stops_valid``` is the quickest check
|
65
|
+
Most of the top-level properties are pretty self-explanatory. If all you want to do is get a general yes/no then ```all_stops_valid``` is the quickest check, although with the latest version raising a StopError exception if a stop ID check fails that's become a little bit academic.
|
66
|
+
If the API call was successful then ```stop_detail``` will contain everything that the API sent back for the closest match it found.
|
67
67
|
|
68
|
+
### Sample Code - catching an invalid stop
|
69
|
+
|
70
|
+
The following example checks two stops to see if they're valid, and it turns out that one of them isn't.
|
71
|
+
|
72
|
+
**Code:**
|
73
|
+
```python
|
74
|
+
from TransportNSWv2 import TransportNSWv2, StopError
|
75
|
+
|
76
|
+
tnsw = TransportNSWv2()
|
77
|
+
try:
|
78
|
+
_data = tnsw.check_stops(<your API key>, ['20006012345', '229310'])
|
79
|
+
print (_data['all_stops_valid'])
|
80
|
+
|
81
|
+
except StopError as ex:
|
82
|
+
print (f"Stop error - {ex}")
|
83
|
+
|
84
|
+
except Exception as ex:
|
85
|
+
print (f"Misc error - {ex}")
|
86
|
+
```
|
87
|
+
|
88
|
+
**Result:**
|
89
|
+
```python
|
90
|
+
Stop error - Error 'stop invalid' calling stop finder API for stop ID 20006012345
|
91
|
+
```
|
68
92
|
|
69
93
|
### get_trip() parameters
|
70
94
|
Only the first three parameters are mandatory, the rest are optional. All parameters and their defaults are as follows:
|
@@ -72,6 +96,11 @@ Description: # TransportNSWv2
|
|
72
96
|
.get_trip(origin_stop_id, destination_stop_id, api_key, trip_wait_time = 0, transport_type = 0, strict_transport_type = False, raw_output = False, journeys_to_return = 1, route_filter = '', include_realtime_location = True, include_alerts = 'none', alert_type = 'all', check_stop_ids = True, forced_gtfs_uri = [])
|
73
97
|
```
|
74
98
|
|
99
|
+
```trip_wait_time``` is how many minutes from now the departure should be
|
100
|
+
If you specify a ```transport_type``` then only journeys with at least **one** leg of the journey including that transport type are included, unless ```strict_transport_type``` is ```True```, in which case the **first** leg must be of the requested type to be returned.
|
101
|
+
If ```route_filter``` has a value then only journeys with that value in either the ```origin_line_name``` or ```origin_line_name_short``` fields are included - it's a caseless wildcard search so ```north``` would include ```T1 North Shore & Western Line``` journeys
|
102
|
+
```raw_output``` means that function returns whatever came back from the API call as-is
|
103
|
+
|
75
104
|
Transport types:
|
76
105
|
```
|
77
106
|
1: Train
|
@@ -105,27 +134,7 @@ Description: # TransportNSWv2
|
|
105
134
|
bannerInfo: Alerts potentially relating to network-wide impacts
|
106
135
|
```
|
107
136
|
|
108
|
-
TransportNSW's trip planner can work better if you use the general location IDs (eg Central Station) rather than a specific
|
109
|
-
|
110
|
-
### Sample Code - bus journey, no alerts included
|
111
|
-
|
112
|
-
The following example returns the next trip that starts from a bus stop in St. Ives (207537) at least five minutes from now, to Central Station's general stop ID (200060):
|
113
|
-
|
114
|
-
**Code:**
|
115
|
-
```python
|
116
|
-
from TransportNSWv2 import TransportNSWv2
|
117
|
-
tnsw = TransportNSWv2()
|
118
|
-
journey = tnsw.get_trip('207537', '200060', 'YOUR_API_KEY', journey_wait_time = 5, transport_type = 5)
|
119
|
-
print(journey)
|
120
|
-
```
|
121
|
-
**Result:**
|
122
|
-
```python
|
123
|
-
{"journeys_to_return": 1, "journeys_with_data": 1, "journeys": [
|
124
|
-
{"due": 22, "origin_stop_id": "207537", "origin_name": "Mona Vale Rd at Shinfield Ave, St Ives", "departure_time": "2024-09-10T06:34:24Z", "destination_stop_id": "207235", "destination_name": "Gordon Station, Stand C, Gordon", "arrival_time": "2024-09-
|
125
|
-
10T06:40:36Z", "origin_transport_type": "Bus", "origin_transport_name": "Sydney Buses Network", "origin_line_name": "195", "origin_line_name_short": "195", "changes": 0, "occupancy": "FEW_SEATS", "real_time_trip_id": "2197645", "latitude": -33.728271484375,
|
126
|
-
"longitude": 151.1637420654297, "alerts": []
|
127
|
-
}]}
|
128
|
-
```
|
137
|
+
TransportNSW's trip planner can work better if you use the general location IDs (eg Central Station) rather than a specific stop ID (eg Central Station, Platform 19) for the destination, depending on the transport type. Forcing a specific end destination sometimes results in much more complicated trips. Also note that the API expects (and returns) the stop IDs as strings, although so far they all appear to be numeric.
|
129
138
|
|
130
139
|
### Sample Code - train journey, all stop-related alerts normal priority or higher included
|
131
140
|
|
@@ -141,23 +150,23 @@ Description: # TransportNSWv2
|
|
141
150
|
**Result:**
|
142
151
|
```python
|
143
152
|
{"journeys_to_return": 2, "journeys_with_data": 2, "journeys":[
|
144
|
-
{"due": 8, "origin_stop_id": "207262", "origin_name": "Gordon Station, Platform 2, Gordon", "departure_time": "2024-09-10T05:18:00Z", "destination_stop_id": "2000338", "destination_name": "Central Station, Platform 18, Sydney", "arrival_time": "2024-09-
|
145
|
-
10T05:54:00Z", "origin_transport_type": "Train", "origin_transport_name": "Sydney Trains Network", "origin_line_name": "T1 North Shore & Western Line", "origin_line_name_short": "T1", "changes": 0, "occupancy": "unknown", "real_time_trip_id":
|
153
|
+
{"due": 8, "origin_stop_id": "207262", "origin_name": "Gordon Station, Platform 2, Gordon", "departure_time": "2024-09-10T05:18:00Z", "destination_stop_id": "2000338", "destination_name": "Central Station, Platform 18, Sydney", "arrival_time": "2024-09-
|
154
|
+
10T05:54:00Z", "origin_transport_type": "Train", "origin_transport_name": "Sydney Trains Network", "origin_line_name": "T1 North Shore & Western Line", "origin_line_name_short": "T1", "changes": 0, "occupancy": "unknown", "real_time_trip_id":
|
146
155
|
"171L.1915.100.8.A.8.83064399", "latitude": -33.755828857421875, "longitude": 151.1542205810547, "alerts": [
|
147
|
-
{"priority": "normal", "id": "ems-39380", "version": 3, "type": "stopInfo", "infoLinks": [{"urlText": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available", "url": "https://transportnsw.info/alerts/details#/ems-39380", "content":
|
148
|
-
"At Central Station Lift 20 between Central Walk and Platform 20/21 is temporarily out of service.\n\nIf you need help, ask staff or phone 02 9379 1777.", "subtitle": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available",
|
149
|
-
"smsText": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available", "speechText": "At Central Station Lift 20 between Central Walk and Platform 20/21 is temporarily out of service.\n\nIf you need help, ask staff or phone 02 9379
|
156
|
+
{"priority": "normal", "id": "ems-39380", "version": 3, "type": "stopInfo", "infoLinks": [{"urlText": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available", "url": "https://transportnsw.info/alerts/details#/ems-39380", "content":
|
157
|
+
"At Central Station Lift 20 between Central Walk and Platform 20/21 is temporarily out of service.\n\nIf you need help, ask staff or phone 02 9379 1777.", "subtitle": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available",
|
158
|
+
"smsText": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available", "speechText": "At Central Station Lift 20 between Central Walk and Platform 20/21 is temporarily out of service.\n\nIf you need help, ask staff or phone 02 9379
|
150
159
|
1777.", "properties": {"publisher": "ems.comm.addinfo", "infoType": "stopInfo", "appliesTo": "departingArriving", "stopIDglobalID": "200060:2000340,2000341"}}
|
151
160
|
]}
|
152
161
|
]},
|
153
|
-
{"due": 11, "origin_stop_id": "207261", "origin_name": "Gordon Station, Platform 1, Gordon", "departure_time": "2024-09-10T05:21:00Z", "destination_stop_id": "2067141", "destination_name": "Chatswood Station, Platform 1, Chatswood", "arrival_time": "2024-09-
|
154
|
-
10T05:30:00Z", "origin_transport_type": "Train", "origin_transport_name": "Sydney Trains Network", "origin_line_name": "T1 North Shore & Western Line", "origin_line_name_short": "T1", "changes": 0, "occupancy": "unknown", "real_time_trip_id":
|
162
|
+
{"due": 11, "origin_stop_id": "207261", "origin_name": "Gordon Station, Platform 1, Gordon", "departure_time": "2024-09-10T05:21:00Z", "destination_stop_id": "2067141", "destination_name": "Chatswood Station, Platform 1, Chatswood", "arrival_time": "2024-09-
|
163
|
+
10T05:30:00Z", "origin_transport_type": "Train", "origin_transport_name": "Sydney Trains Network", "origin_line_name": "T1 North Shore & Western Line", "origin_line_name_short": "T1", "changes": 0, "occupancy": "unknown", "real_time_trip_id":
|
155
164
|
"281G.1915.100.12.H.8.83062682", "latitude": -33.709938049316406, "longitude": 151.10427856445312, "alerts": [
|
156
|
-
{"priority": "normal", "id": "ems-38565", "version": 145217, "type": "lineInfo", "infoLinks": [{"urlText": "Metro services temporarily end by 10.30pmMonday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip",
|
157
|
-
"url": "https://transportnsw.info/alerts/details#/ems-38565", "content": "<div>\n<div>For the first four weeks after opening, there are reduced operating hours from Monday to Thursday evenings in the City section between Chatswood and Sydenham to support
|
158
|
-
essential engineering and maintenance works during the early phases of operations.</div>\n<div> </div>\n<div>This is temporary and only affects services between Chatswood and Sydenham. Following the first four weeks, metro services will operate
|
159
|
-
between Tallawong and Sydenham on the normal timetable.</div>\n</div>", "subtitle": "Metro services temporarily end by 10.30pm Monday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip", "smsText": "Metro
|
160
|
-
services temporarily end by 10.30pm Monday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip", "speechText": "There are reduced operating hours from Monday to Thursday evenings in the City section between
|
165
|
+
{"priority": "normal", "id": "ems-38565", "version": 145217, "type": "lineInfo", "infoLinks": [{"urlText": "Metro services temporarily end by 10.30pmMonday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip",
|
166
|
+
"url": "https://transportnsw.info/alerts/details#/ems-38565", "content": "<div>\n<div>For the first four weeks after opening, there are reduced operating hours from Monday to Thursday evenings in the City section between Chatswood and Sydenham to support
|
167
|
+
essential engineering and maintenance works during the early phases of operations.</div>\n<div> </div>\n<div>This is temporary and only affects services between Chatswood and Sydenham. Following the first four weeks, metro services will operate
|
168
|
+
between Tallawong and Sydenham on the normal timetable.</div>\n</div>", "subtitle": "Metro services temporarily end by 10.30pm Monday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip", "smsText": "Metro
|
169
|
+
services temporarily end by 10.30pm Monday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip", "speechText": "There are reduced operating hours from Monday to Thursday evenings in the City section between
|
161
170
|
Chatswood and Sydenham to support essential engineering and maintenance works during the early phases of operations.", "properties": {"publisher": "ems.comm.addinfo", "infoType": "lineInfo"}}
|
162
171
|
]}
|
163
172
|
]}
|
@@ -185,13 +194,16 @@ Description: # TransportNSWv2
|
|
185
194
|
### Notes ###
|
186
195
|
Requesting multiple journeys to be returned doesn't always return that exact number of journeys. The API only ever returns five or six, and if you have any filters applied then that might further reduce the number of 'valid' journeys.
|
187
196
|
|
188
|
-
|
189
|
-
|
190
|
-
Note also that the origin and destination details are just that - information about the first and last stops on the journey at the time the request was made. We don't return any intermediate steps, transport change types etc other than the total number of changes - the assumption is that you'll know the details of your specified trip, you just want to know when the next departure is. If you need much more detailed information then I recommend that you use the full Transport NSW trip planner website or application.
|
197
|
+
Note that the origin and destination details are just that - information about the first and last stops on the journey at the time the request was made. The output doesn't include any intermediate steps, transport change types etc. other than the total number of changes - the assumption is that you'll know the details of your specified trip, you just want to know when the next departure is. If you need much more detailed information then I recommend that you use the full Transport NSW trip planner website or application, or parse the raw output by adding ```raw_output = True``` to your call.
|
191
198
|
|
192
|
-
|
199
|
+
## Exceptions ##
|
200
|
+
The latest release of TransportNSWv2 now uses custom Exceptions when things go wrong, instead of returning None - I think that's probably more 'Pythonic'. The Exceptions that can be imported are as follows:
|
201
|
+
* InvalidAPIKey - API key-related issues
|
202
|
+
* APIRateLimitExceeded - API rate-limit issues
|
203
|
+
* StopError - stop ID issues, usually when checking that a stop ID is valid
|
204
|
+
* TripError - trip-related issues, including no journeys being returned when calling ```.get_trip()```
|
193
205
|
|
194
|
-
|
206
|
+
## Rate limits ##
|
195
207
|
By default the TransportNSW API allows each API key to make 60,000 calls in a day and up to 5 calls per second. When requesting real-time location information some services required me to brute-force up to 12 (!) URIs until I found the right one which sometimes resulted in an API rate limt breach. From version 0.8.7 I found a TransportNSW-maintained CSV that contains mappings of bus agency IDs to URIs so I'm using that, plus adding in a 0.75 second delay between API calls. Alternatively, if you're confident that the origin and destination IDs are correct you can reduce your API calls by adding ```check_trip_ids = False``` in the parameters. Additionally there's a final option ```forced_gtfs_uri``` which, if you're super-confident you know what the GTFS URI is for your particular journey, will again reduce the API calls per trip query... although I'd use this one with caution! ```forced_gtfs_uri``` needs to be a single-item list, here's an example:
|
196
208
|
|
197
209
|
```forced_gtfs_uri = ["/lightrail/innerwest"]```
|
@@ -1,32 +1,31 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: PyTransportNSWv2
|
3
|
-
Version: 0.
|
3
|
+
Version: 1.0.1
|
4
4
|
Summary: Get detailed per-trip transport information from TransportNSW
|
5
5
|
Home-page: https://github.com/andystewart999/TransportNSW
|
6
6
|
Author: andystewart999
|
7
7
|
Author-email: andy.stewart@live.com
|
8
8
|
License: UNKNOWN
|
9
9
|
Description: # TransportNSWv2
|
10
|
-
Python lib to access Transport NSW information.
|
10
|
+
Python lib to access Transport NSW stop and journey information.
|
11
11
|
|
12
12
|
## How to Use
|
13
13
|
|
14
14
|
### Get an API Key
|
15
|
-
An OpenData account and API key is required to request the data. More information on how to create the free account can be found here
|
16
|
-
https://opendata.transport.nsw.gov.au/user-guide. You need to register an application that needs both the Trip Planner and Realtime Vehicle Positions APIs
|
15
|
+
An OpenData account and API key is required to request the data. More information on how to create the free account can be found [here](https://opendata.transport.nsw.gov.au/user-guide). You need to register an application that needs both the Trip Planner and Realtime Vehicle Positions APIs.
|
17
16
|
|
18
17
|
### Get the stop IDs
|
19
|
-
The
|
18
|
+
The only mandatory parameters are the API key and the from/to stop IDs - the easiest way to get the stop ID is via https://transportnsw.info/stops#/ - that page provides the option to search for either a location or a specific platform, bus stop or ferry wharf. Regardless of if you specify a general location for the origin or destination, the return information shows the stop ID for the actual arrival and destination platform, bus stop or ferry wharf.
|
20
19
|
|
21
20
|
If it's available, the general occupancy level and the latitude and longitude of the selected journey's vehicle (train, bus, etc) will be returned, unless you specifically set ```include_realtime_location``` to ```False```.
|
22
21
|
|
23
22
|
### API Documentation
|
24
|
-
The source API details can be found here
|
23
|
+
The source Transport NSW API details can be found [here](https://opendata.transport.nsw.gov.au/sites/default/files/2023-08/Trip%20Planner%20API%20manual-opendataproduction%20v3.2.pdf).
|
25
24
|
|
26
25
|
### Exposed functions
|
27
26
|
Two functions are available:
|
28
27
|
```get_trip()``` returns trip information between two stop IDs
|
29
|
-
```check_stops()``` lets you confirm that the two stop IDs are valid, plus it returns all the stop ID metadata. Note that ```get_trip()``` calls this function internally and fails
|
28
|
+
```check_stops()``` lets you confirm that the two stop IDs are valid, plus it returns all the stop ID metadata. Note that ```get_trip()``` calls this function internally (unless you tell it not to) and fails with a ```StopError``` Exception if either of the stop IDs are invalid, so there's no specific need to call ```check_stops()``` unless you want the stop ID metadata, or know you'll be calling the same journey multiple times and want to reduce your daily API calls by pre-checking once.
|
30
29
|
|
31
30
|
### check_stops() parameters
|
32
31
|
All parameters are mandatory. Note that ```stop_list``` can be a single string or a list of strings:
|
@@ -63,8 +62,33 @@ Description: # TransportNSWv2
|
|
63
62
|
]
|
64
63
|
}
|
65
64
|
```
|
66
|
-
Most of the top-level properties are pretty self-explanatory. If all you want to do is get a general yes/no then ```all_stops_valid``` is the quickest check
|
65
|
+
Most of the top-level properties are pretty self-explanatory. If all you want to do is get a general yes/no then ```all_stops_valid``` is the quickest check, although with the latest version raising a StopError exception if a stop ID check fails that's become a little bit academic.
|
66
|
+
If the API call was successful then ```stop_detail``` will contain everything that the API sent back for the closest match it found.
|
67
67
|
|
68
|
+
### Sample Code - catching an invalid stop
|
69
|
+
|
70
|
+
The following example checks two stops to see if they're valid, and it turns out that one of them isn't.
|
71
|
+
|
72
|
+
**Code:**
|
73
|
+
```python
|
74
|
+
from TransportNSWv2 import TransportNSWv2, StopError
|
75
|
+
|
76
|
+
tnsw = TransportNSWv2()
|
77
|
+
try:
|
78
|
+
_data = tnsw.check_stops(<your API key>, ['20006012345', '229310'])
|
79
|
+
print (_data['all_stops_valid'])
|
80
|
+
|
81
|
+
except StopError as ex:
|
82
|
+
print (f"Stop error - {ex}")
|
83
|
+
|
84
|
+
except Exception as ex:
|
85
|
+
print (f"Misc error - {ex}")
|
86
|
+
```
|
87
|
+
|
88
|
+
**Result:**
|
89
|
+
```python
|
90
|
+
Stop error - Error 'stop invalid' calling stop finder API for stop ID 20006012345
|
91
|
+
```
|
68
92
|
|
69
93
|
### get_trip() parameters
|
70
94
|
Only the first three parameters are mandatory, the rest are optional. All parameters and their defaults are as follows:
|
@@ -72,6 +96,11 @@ Description: # TransportNSWv2
|
|
72
96
|
.get_trip(origin_stop_id, destination_stop_id, api_key, trip_wait_time = 0, transport_type = 0, strict_transport_type = False, raw_output = False, journeys_to_return = 1, route_filter = '', include_realtime_location = True, include_alerts = 'none', alert_type = 'all', check_stop_ids = True, forced_gtfs_uri = [])
|
73
97
|
```
|
74
98
|
|
99
|
+
```trip_wait_time``` is how many minutes from now the departure should be
|
100
|
+
If you specify a ```transport_type``` then only journeys with at least **one** leg of the journey including that transport type are included, unless ```strict_transport_type``` is ```True```, in which case the **first** leg must be of the requested type to be returned.
|
101
|
+
If ```route_filter``` has a value then only journeys with that value in either the ```origin_line_name``` or ```origin_line_name_short``` fields are included - it's a caseless wildcard search so ```north``` would include ```T1 North Shore & Western Line``` journeys
|
102
|
+
```raw_output``` means that function returns whatever came back from the API call as-is
|
103
|
+
|
75
104
|
Transport types:
|
76
105
|
```
|
77
106
|
1: Train
|
@@ -105,27 +134,7 @@ Description: # TransportNSWv2
|
|
105
134
|
bannerInfo: Alerts potentially relating to network-wide impacts
|
106
135
|
```
|
107
136
|
|
108
|
-
TransportNSW's trip planner can work better if you use the general location IDs (eg Central Station) rather than a specific
|
109
|
-
|
110
|
-
### Sample Code - bus journey, no alerts included
|
111
|
-
|
112
|
-
The following example returns the next trip that starts from a bus stop in St. Ives (207537) at least five minutes from now, to Central Station's general stop ID (200060):
|
113
|
-
|
114
|
-
**Code:**
|
115
|
-
```python
|
116
|
-
from TransportNSWv2 import TransportNSWv2
|
117
|
-
tnsw = TransportNSWv2()
|
118
|
-
journey = tnsw.get_trip('207537', '200060', 'YOUR_API_KEY', journey_wait_time = 5, transport_type = 5)
|
119
|
-
print(journey)
|
120
|
-
```
|
121
|
-
**Result:**
|
122
|
-
```python
|
123
|
-
{"journeys_to_return": 1, "journeys_with_data": 1, "journeys": [
|
124
|
-
{"due": 22, "origin_stop_id": "207537", "origin_name": "Mona Vale Rd at Shinfield Ave, St Ives", "departure_time": "2024-09-10T06:34:24Z", "destination_stop_id": "207235", "destination_name": "Gordon Station, Stand C, Gordon", "arrival_time": "2024-09-
|
125
|
-
10T06:40:36Z", "origin_transport_type": "Bus", "origin_transport_name": "Sydney Buses Network", "origin_line_name": "195", "origin_line_name_short": "195", "changes": 0, "occupancy": "FEW_SEATS", "real_time_trip_id": "2197645", "latitude": -33.728271484375,
|
126
|
-
"longitude": 151.1637420654297, "alerts": []
|
127
|
-
}]}
|
128
|
-
```
|
137
|
+
TransportNSW's trip planner can work better if you use the general location IDs (eg Central Station) rather than a specific stop ID (eg Central Station, Platform 19) for the destination, depending on the transport type. Forcing a specific end destination sometimes results in much more complicated trips. Also note that the API expects (and returns) the stop IDs as strings, although so far they all appear to be numeric.
|
129
138
|
|
130
139
|
### Sample Code - train journey, all stop-related alerts normal priority or higher included
|
131
140
|
|
@@ -141,23 +150,23 @@ Description: # TransportNSWv2
|
|
141
150
|
**Result:**
|
142
151
|
```python
|
143
152
|
{"journeys_to_return": 2, "journeys_with_data": 2, "journeys":[
|
144
|
-
{"due": 8, "origin_stop_id": "207262", "origin_name": "Gordon Station, Platform 2, Gordon", "departure_time": "2024-09-10T05:18:00Z", "destination_stop_id": "2000338", "destination_name": "Central Station, Platform 18, Sydney", "arrival_time": "2024-09-
|
145
|
-
10T05:54:00Z", "origin_transport_type": "Train", "origin_transport_name": "Sydney Trains Network", "origin_line_name": "T1 North Shore & Western Line", "origin_line_name_short": "T1", "changes": 0, "occupancy": "unknown", "real_time_trip_id":
|
153
|
+
{"due": 8, "origin_stop_id": "207262", "origin_name": "Gordon Station, Platform 2, Gordon", "departure_time": "2024-09-10T05:18:00Z", "destination_stop_id": "2000338", "destination_name": "Central Station, Platform 18, Sydney", "arrival_time": "2024-09-
|
154
|
+
10T05:54:00Z", "origin_transport_type": "Train", "origin_transport_name": "Sydney Trains Network", "origin_line_name": "T1 North Shore & Western Line", "origin_line_name_short": "T1", "changes": 0, "occupancy": "unknown", "real_time_trip_id":
|
146
155
|
"171L.1915.100.8.A.8.83064399", "latitude": -33.755828857421875, "longitude": 151.1542205810547, "alerts": [
|
147
|
-
{"priority": "normal", "id": "ems-39380", "version": 3, "type": "stopInfo", "infoLinks": [{"urlText": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available", "url": "https://transportnsw.info/alerts/details#/ems-39380", "content":
|
148
|
-
"At Central Station Lift 20 between Central Walk and Platform 20/21 is temporarily out of service.\n\nIf you need help, ask staff or phone 02 9379 1777.", "subtitle": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available",
|
149
|
-
"smsText": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available", "speechText": "At Central Station Lift 20 between Central Walk and Platform 20/21 is temporarily out of service.\n\nIf you need help, ask staff or phone 02 9379
|
156
|
+
{"priority": "normal", "id": "ems-39380", "version": 3, "type": "stopInfo", "infoLinks": [{"urlText": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available", "url": "https://transportnsw.info/alerts/details#/ems-39380", "content":
|
157
|
+
"At Central Station Lift 20 between Central Walk and Platform 20/21 is temporarily out of service.\n\nIf you need help, ask staff or phone 02 9379 1777.", "subtitle": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available",
|
158
|
+
"smsText": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available", "speechText": "At Central Station Lift 20 between Central Walk and Platform 20/21 is temporarily out of service.\n\nIf you need help, ask staff or phone 02 9379
|
150
159
|
1777.", "properties": {"publisher": "ems.comm.addinfo", "infoType": "stopInfo", "appliesTo": "departingArriving", "stopIDglobalID": "200060:2000340,2000341"}}
|
151
160
|
]}
|
152
161
|
]},
|
153
|
-
{"due": 11, "origin_stop_id": "207261", "origin_name": "Gordon Station, Platform 1, Gordon", "departure_time": "2024-09-10T05:21:00Z", "destination_stop_id": "2067141", "destination_name": "Chatswood Station, Platform 1, Chatswood", "arrival_time": "2024-09-
|
154
|
-
10T05:30:00Z", "origin_transport_type": "Train", "origin_transport_name": "Sydney Trains Network", "origin_line_name": "T1 North Shore & Western Line", "origin_line_name_short": "T1", "changes": 0, "occupancy": "unknown", "real_time_trip_id":
|
162
|
+
{"due": 11, "origin_stop_id": "207261", "origin_name": "Gordon Station, Platform 1, Gordon", "departure_time": "2024-09-10T05:21:00Z", "destination_stop_id": "2067141", "destination_name": "Chatswood Station, Platform 1, Chatswood", "arrival_time": "2024-09-
|
163
|
+
10T05:30:00Z", "origin_transport_type": "Train", "origin_transport_name": "Sydney Trains Network", "origin_line_name": "T1 North Shore & Western Line", "origin_line_name_short": "T1", "changes": 0, "occupancy": "unknown", "real_time_trip_id":
|
155
164
|
"281G.1915.100.12.H.8.83062682", "latitude": -33.709938049316406, "longitude": 151.10427856445312, "alerts": [
|
156
|
-
{"priority": "normal", "id": "ems-38565", "version": 145217, "type": "lineInfo", "infoLinks": [{"urlText": "Metro services temporarily end by 10.30pmMonday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip",
|
157
|
-
"url": "https://transportnsw.info/alerts/details#/ems-38565", "content": "<div>\n<div>For the first four weeks after opening, there are reduced operating hours from Monday to Thursday evenings in the City section between Chatswood and Sydenham to support
|
158
|
-
essential engineering and maintenance works during the early phases of operations.</div>\n<div> </div>\n<div>This is temporary and only affects services between Chatswood and Sydenham. Following the first four weeks, metro services will operate
|
159
|
-
between Tallawong and Sydenham on the normal timetable.</div>\n</div>", "subtitle": "Metro services temporarily end by 10.30pm Monday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip", "smsText": "Metro
|
160
|
-
services temporarily end by 10.30pm Monday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip", "speechText": "There are reduced operating hours from Monday to Thursday evenings in the City section between
|
165
|
+
{"priority": "normal", "id": "ems-38565", "version": 145217, "type": "lineInfo", "infoLinks": [{"urlText": "Metro services temporarily end by 10.30pmMonday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip",
|
166
|
+
"url": "https://transportnsw.info/alerts/details#/ems-38565", "content": "<div>\n<div>For the first four weeks after opening, there are reduced operating hours from Monday to Thursday evenings in the City section between Chatswood and Sydenham to support
|
167
|
+
essential engineering and maintenance works during the early phases of operations.</div>\n<div> </div>\n<div>This is temporary and only affects services between Chatswood and Sydenham. Following the first four weeks, metro services will operate
|
168
|
+
between Tallawong and Sydenham on the normal timetable.</div>\n</div>", "subtitle": "Metro services temporarily end by 10.30pm Monday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip", "smsText": "Metro
|
169
|
+
services temporarily end by 10.30pm Monday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip", "speechText": "There are reduced operating hours from Monday to Thursday evenings in the City section between
|
161
170
|
Chatswood and Sydenham to support essential engineering and maintenance works during the early phases of operations.", "properties": {"publisher": "ems.comm.addinfo", "infoType": "lineInfo"}}
|
162
171
|
]}
|
163
172
|
]}
|
@@ -185,13 +194,16 @@ Description: # TransportNSWv2
|
|
185
194
|
### Notes ###
|
186
195
|
Requesting multiple journeys to be returned doesn't always return that exact number of journeys. The API only ever returns five or six, and if you have any filters applied then that might further reduce the number of 'valid' journeys.
|
187
196
|
|
188
|
-
|
189
|
-
|
190
|
-
Note also that the origin and destination details are just that - information about the first and last stops on the journey at the time the request was made. We don't return any intermediate steps, transport change types etc other than the total number of changes - the assumption is that you'll know the details of your specified trip, you just want to know when the next departure is. If you need much more detailed information then I recommend that you use the full Transport NSW trip planner website or application.
|
197
|
+
Note that the origin and destination details are just that - information about the first and last stops on the journey at the time the request was made. The output doesn't include any intermediate steps, transport change types etc. other than the total number of changes - the assumption is that you'll know the details of your specified trip, you just want to know when the next departure is. If you need much more detailed information then I recommend that you use the full Transport NSW trip planner website or application, or parse the raw output by adding ```raw_output = True``` to your call.
|
191
198
|
|
192
|
-
|
199
|
+
## Exceptions ##
|
200
|
+
The latest release of TransportNSWv2 now uses custom Exceptions when things go wrong, instead of returning None - I think that's probably more 'Pythonic'. The Exceptions that can be imported are as follows:
|
201
|
+
* InvalidAPIKey - API key-related issues
|
202
|
+
* APIRateLimitExceeded - API rate-limit issues
|
203
|
+
* StopError - stop ID issues, usually when checking that a stop ID is valid
|
204
|
+
* TripError - trip-related issues, including no journeys being returned when calling ```.get_trip()```
|
193
205
|
|
194
|
-
|
206
|
+
## Rate limits ##
|
195
207
|
By default the TransportNSW API allows each API key to make 60,000 calls in a day and up to 5 calls per second. When requesting real-time location information some services required me to brute-force up to 12 (!) URIs until I found the right one which sometimes resulted in an API rate limt breach. From version 0.8.7 I found a TransportNSW-maintained CSV that contains mappings of bus agency IDs to URIs so I'm using that, plus adding in a 0.75 second delay between API calls. Alternatively, if you're confident that the origin and destination IDs are correct you can reduce your API calls by adding ```check_trip_ids = False``` in the parameters. Additionally there's a final option ```forced_gtfs_uri``` which, if you're super-confident you know what the GTFS URI is for your particular journey, will again reduce the API calls per trip query... although I'd use this one with caution! ```forced_gtfs_uri``` needs to be a single-item list, here's an example:
|
196
208
|
|
197
209
|
```forced_gtfs_uri = ["/lightrail/innerwest"]```
|
@@ -1,24 +1,23 @@
|
|
1
1
|
# TransportNSWv2
|
2
|
-
Python lib to access Transport NSW information.
|
2
|
+
Python lib to access Transport NSW stop and journey information.
|
3
3
|
|
4
4
|
## How to Use
|
5
5
|
|
6
6
|
### Get an API Key
|
7
|
-
An OpenData account and API key is required to request the data. More information on how to create the free account can be found here
|
8
|
-
https://opendata.transport.nsw.gov.au/user-guide. You need to register an application that needs both the Trip Planner and Realtime Vehicle Positions APIs
|
7
|
+
An OpenData account and API key is required to request the data. More information on how to create the free account can be found [here](https://opendata.transport.nsw.gov.au/user-guide). You need to register an application that needs both the Trip Planner and Realtime Vehicle Positions APIs.
|
9
8
|
|
10
9
|
### Get the stop IDs
|
11
|
-
The
|
10
|
+
The only mandatory parameters are the API key and the from/to stop IDs - the easiest way to get the stop ID is via https://transportnsw.info/stops#/ - that page provides the option to search for either a location or a specific platform, bus stop or ferry wharf. Regardless of if you specify a general location for the origin or destination, the return information shows the stop ID for the actual arrival and destination platform, bus stop or ferry wharf.
|
12
11
|
|
13
12
|
If it's available, the general occupancy level and the latitude and longitude of the selected journey's vehicle (train, bus, etc) will be returned, unless you specifically set ```include_realtime_location``` to ```False```.
|
14
13
|
|
15
14
|
### API Documentation
|
16
|
-
The source API details can be found here
|
15
|
+
The source Transport NSW API details can be found [here](https://opendata.transport.nsw.gov.au/sites/default/files/2023-08/Trip%20Planner%20API%20manual-opendataproduction%20v3.2.pdf).
|
17
16
|
|
18
17
|
### Exposed functions
|
19
18
|
Two functions are available:
|
20
19
|
```get_trip()``` returns trip information between two stop IDs
|
21
|
-
```check_stops()``` lets you confirm that the two stop IDs are valid, plus it returns all the stop ID metadata. Note that ```get_trip()``` calls this function internally and fails
|
20
|
+
```check_stops()``` lets you confirm that the two stop IDs are valid, plus it returns all the stop ID metadata. Note that ```get_trip()``` calls this function internally (unless you tell it not to) and fails with a ```StopError``` Exception if either of the stop IDs are invalid, so there's no specific need to call ```check_stops()``` unless you want the stop ID metadata, or know you'll be calling the same journey multiple times and want to reduce your daily API calls by pre-checking once.
|
22
21
|
|
23
22
|
### check_stops() parameters
|
24
23
|
All parameters are mandatory. Note that ```stop_list``` can be a single string or a list of strings:
|
@@ -55,8 +54,33 @@ The return is a JSON-compatible Python object as per the example here:
|
|
55
54
|
]
|
56
55
|
}
|
57
56
|
```
|
58
|
-
Most of the top-level properties are pretty self-explanatory. If all you want to do is get a general yes/no then ```all_stops_valid``` is the quickest check
|
57
|
+
Most of the top-level properties are pretty self-explanatory. If all you want to do is get a general yes/no then ```all_stops_valid``` is the quickest check, although with the latest version raising a StopError exception if a stop ID check fails that's become a little bit academic.
|
58
|
+
If the API call was successful then ```stop_detail``` will contain everything that the API sent back for the closest match it found.
|
59
59
|
|
60
|
+
### Sample Code - catching an invalid stop
|
61
|
+
|
62
|
+
The following example checks two stops to see if they're valid, and it turns out that one of them isn't.
|
63
|
+
|
64
|
+
**Code:**
|
65
|
+
```python
|
66
|
+
from TransportNSWv2 import TransportNSWv2, StopError
|
67
|
+
|
68
|
+
tnsw = TransportNSWv2()
|
69
|
+
try:
|
70
|
+
_data = tnsw.check_stops(<your API key>, ['20006012345', '229310'])
|
71
|
+
print (_data['all_stops_valid'])
|
72
|
+
|
73
|
+
except StopError as ex:
|
74
|
+
print (f"Stop error - {ex}")
|
75
|
+
|
76
|
+
except Exception as ex:
|
77
|
+
print (f"Misc error - {ex}")
|
78
|
+
```
|
79
|
+
|
80
|
+
**Result:**
|
81
|
+
```python
|
82
|
+
Stop error - Error 'stop invalid' calling stop finder API for stop ID 20006012345
|
83
|
+
```
|
60
84
|
|
61
85
|
### get_trip() parameters
|
62
86
|
Only the first three parameters are mandatory, the rest are optional. All parameters and their defaults are as follows:
|
@@ -64,6 +88,11 @@ Only the first three parameters are mandatory, the rest are optional. All param
|
|
64
88
|
.get_trip(origin_stop_id, destination_stop_id, api_key, trip_wait_time = 0, transport_type = 0, strict_transport_type = False, raw_output = False, journeys_to_return = 1, route_filter = '', include_realtime_location = True, include_alerts = 'none', alert_type = 'all', check_stop_ids = True, forced_gtfs_uri = [])
|
65
89
|
```
|
66
90
|
|
91
|
+
```trip_wait_time``` is how many minutes from now the departure should be
|
92
|
+
If you specify a ```transport_type``` then only journeys with at least **one** leg of the journey including that transport type are included, unless ```strict_transport_type``` is ```True```, in which case the **first** leg must be of the requested type to be returned.
|
93
|
+
If ```route_filter``` has a value then only journeys with that value in either the ```origin_line_name``` or ```origin_line_name_short``` fields are included - it's a caseless wildcard search so ```north``` would include ```T1 North Shore & Western Line``` journeys
|
94
|
+
```raw_output``` means that function returns whatever came back from the API call as-is
|
95
|
+
|
67
96
|
Transport types:
|
68
97
|
```
|
69
98
|
1: Train
|
@@ -97,27 +126,7 @@ stopBlocking: Alerts relating to stop closures
|
|
97
126
|
bannerInfo: Alerts potentially relating to network-wide impacts
|
98
127
|
```
|
99
128
|
|
100
|
-
TransportNSW's trip planner can work better if you use the general location IDs (eg Central Station) rather than a specific
|
101
|
-
|
102
|
-
### Sample Code - bus journey, no alerts included
|
103
|
-
|
104
|
-
The following example returns the next trip that starts from a bus stop in St. Ives (207537) at least five minutes from now, to Central Station's general stop ID (200060):
|
105
|
-
|
106
|
-
**Code:**
|
107
|
-
```python
|
108
|
-
from TransportNSWv2 import TransportNSWv2
|
109
|
-
tnsw = TransportNSWv2()
|
110
|
-
journey = tnsw.get_trip('207537', '200060', 'YOUR_API_KEY', journey_wait_time = 5, transport_type = 5)
|
111
|
-
print(journey)
|
112
|
-
```
|
113
|
-
**Result:**
|
114
|
-
```python
|
115
|
-
{"journeys_to_return": 1, "journeys_with_data": 1, "journeys": [
|
116
|
-
{"due": 22, "origin_stop_id": "207537", "origin_name": "Mona Vale Rd at Shinfield Ave, St Ives", "departure_time": "2024-09-10T06:34:24Z", "destination_stop_id": "207235", "destination_name": "Gordon Station, Stand C, Gordon", "arrival_time": "2024-09-
|
117
|
-
10T06:40:36Z", "origin_transport_type": "Bus", "origin_transport_name": "Sydney Buses Network", "origin_line_name": "195", "origin_line_name_short": "195", "changes": 0, "occupancy": "FEW_SEATS", "real_time_trip_id": "2197645", "latitude": -33.728271484375,
|
118
|
-
"longitude": 151.1637420654297, "alerts": []
|
119
|
-
}]}
|
120
|
-
```
|
129
|
+
TransportNSW's trip planner can work better if you use the general location IDs (eg Central Station) rather than a specific stop ID (eg Central Station, Platform 19) for the destination, depending on the transport type. Forcing a specific end destination sometimes results in much more complicated trips. Also note that the API expects (and returns) the stop IDs as strings, although so far they all appear to be numeric.
|
121
130
|
|
122
131
|
### Sample Code - train journey, all stop-related alerts normal priority or higher included
|
123
132
|
|
@@ -133,23 +142,23 @@ print(journey)
|
|
133
142
|
**Result:**
|
134
143
|
```python
|
135
144
|
{"journeys_to_return": 2, "journeys_with_data": 2, "journeys":[
|
136
|
-
{"due": 8, "origin_stop_id": "207262", "origin_name": "Gordon Station, Platform 2, Gordon", "departure_time": "2024-09-10T05:18:00Z", "destination_stop_id": "2000338", "destination_name": "Central Station, Platform 18, Sydney", "arrival_time": "2024-09-
|
137
|
-
10T05:54:00Z", "origin_transport_type": "Train", "origin_transport_name": "Sydney Trains Network", "origin_line_name": "T1 North Shore & Western Line", "origin_line_name_short": "T1", "changes": 0, "occupancy": "unknown", "real_time_trip_id":
|
145
|
+
{"due": 8, "origin_stop_id": "207262", "origin_name": "Gordon Station, Platform 2, Gordon", "departure_time": "2024-09-10T05:18:00Z", "destination_stop_id": "2000338", "destination_name": "Central Station, Platform 18, Sydney", "arrival_time": "2024-09-
|
146
|
+
10T05:54:00Z", "origin_transport_type": "Train", "origin_transport_name": "Sydney Trains Network", "origin_line_name": "T1 North Shore & Western Line", "origin_line_name_short": "T1", "changes": 0, "occupancy": "unknown", "real_time_trip_id":
|
138
147
|
"171L.1915.100.8.A.8.83064399", "latitude": -33.755828857421875, "longitude": 151.1542205810547, "alerts": [
|
139
|
-
{"priority": "normal", "id": "ems-39380", "version": 3, "type": "stopInfo", "infoLinks": [{"urlText": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available", "url": "https://transportnsw.info/alerts/details#/ems-39380", "content":
|
140
|
-
"At Central Station Lift 20 between Central Walk and Platform 20/21 is temporarily out of service.\n\nIf you need help, ask staff or phone 02 9379 1777.", "subtitle": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available",
|
141
|
-
"smsText": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available", "speechText": "At Central Station Lift 20 between Central Walk and Platform 20/21 is temporarily out of service.\n\nIf you need help, ask staff or phone 02 9379
|
148
|
+
{"priority": "normal", "id": "ems-39380", "version": 3, "type": "stopInfo", "infoLinks": [{"urlText": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available", "url": "https://transportnsw.info/alerts/details#/ems-39380", "content":
|
149
|
+
"At Central Station Lift 20 between Central Walk and Platform 20/21 is temporarily out of service.\n\nIf you need help, ask staff or phone 02 9379 1777.", "subtitle": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available",
|
150
|
+
"smsText": "Central Station Lift 20 between Central Walk and Platform 20/21 is not available", "speechText": "At Central Station Lift 20 between Central Walk and Platform 20/21 is temporarily out of service.\n\nIf you need help, ask staff or phone 02 9379
|
142
151
|
1777.", "properties": {"publisher": "ems.comm.addinfo", "infoType": "stopInfo", "appliesTo": "departingArriving", "stopIDglobalID": "200060:2000340,2000341"}}
|
143
152
|
]}
|
144
153
|
]},
|
145
|
-
{"due": 11, "origin_stop_id": "207261", "origin_name": "Gordon Station, Platform 1, Gordon", "departure_time": "2024-09-10T05:21:00Z", "destination_stop_id": "2067141", "destination_name": "Chatswood Station, Platform 1, Chatswood", "arrival_time": "2024-09-
|
146
|
-
10T05:30:00Z", "origin_transport_type": "Train", "origin_transport_name": "Sydney Trains Network", "origin_line_name": "T1 North Shore & Western Line", "origin_line_name_short": "T1", "changes": 0, "occupancy": "unknown", "real_time_trip_id":
|
154
|
+
{"due": 11, "origin_stop_id": "207261", "origin_name": "Gordon Station, Platform 1, Gordon", "departure_time": "2024-09-10T05:21:00Z", "destination_stop_id": "2067141", "destination_name": "Chatswood Station, Platform 1, Chatswood", "arrival_time": "2024-09-
|
155
|
+
10T05:30:00Z", "origin_transport_type": "Train", "origin_transport_name": "Sydney Trains Network", "origin_line_name": "T1 North Shore & Western Line", "origin_line_name_short": "T1", "changes": 0, "occupancy": "unknown", "real_time_trip_id":
|
147
156
|
"281G.1915.100.12.H.8.83062682", "latitude": -33.709938049316406, "longitude": 151.10427856445312, "alerts": [
|
148
|
-
{"priority": "normal", "id": "ems-38565", "version": 145217, "type": "lineInfo", "infoLinks": [{"urlText": "Metro services temporarily end by 10.30pmMonday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip",
|
149
|
-
"url": "https://transportnsw.info/alerts/details#/ems-38565", "content": "<div>\n<div>For the first four weeks after opening, there are reduced operating hours from Monday to Thursday evenings in the City section between Chatswood and Sydenham to support
|
150
|
-
essential engineering and maintenance works during the early phases of operations.</div>\n<div> </div>\n<div>This is temporary and only affects services between Chatswood and Sydenham. Following the first four weeks, metro services will operate
|
151
|
-
between Tallawong and Sydenham on the normal timetable.</div>\n</div>", "subtitle": "Metro services temporarily end by 10.30pm Monday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip", "smsText": "Metro
|
152
|
-
services temporarily end by 10.30pm Monday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip", "speechText": "There are reduced operating hours from Monday to Thursday evenings in the City section between
|
157
|
+
{"priority": "normal", "id": "ems-38565", "version": 145217, "type": "lineInfo", "infoLinks": [{"urlText": "Metro services temporarily end by 10.30pmMonday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip",
|
158
|
+
"url": "https://transportnsw.info/alerts/details#/ems-38565", "content": "<div>\n<div>For the first four weeks after opening, there are reduced operating hours from Monday to Thursday evenings in the City section between Chatswood and Sydenham to support
|
159
|
+
essential engineering and maintenance works during the early phases of operations.</div>\n<div> </div>\n<div>This is temporary and only affects services between Chatswood and Sydenham. Following the first four weeks, metro services will operate
|
160
|
+
between Tallawong and Sydenham on the normal timetable.</div>\n</div>", "subtitle": "Metro services temporarily end by 10.30pm Monday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip", "smsText": "Metro
|
161
|
+
services temporarily end by 10.30pm Monday to Thursday evenings between Chatswood and Sydenham, please check service times and plan your trip", "speechText": "There are reduced operating hours from Monday to Thursday evenings in the City section between
|
153
162
|
Chatswood and Sydenham to support essential engineering and maintenance works during the early phases of operations.", "properties": {"publisher": "ems.comm.addinfo", "infoType": "lineInfo"}}
|
154
163
|
]}
|
155
164
|
]}
|
@@ -177,13 +186,16 @@ print(journey)
|
|
177
186
|
### Notes ###
|
178
187
|
Requesting multiple journeys to be returned doesn't always return that exact number of journeys. The API only ever returns five or six, and if you have any filters applied then that might further reduce the number of 'valid' journeys.
|
179
188
|
|
180
|
-
|
181
|
-
|
182
|
-
Note also that the origin and destination details are just that - information about the first and last stops on the journey at the time the request was made. We don't return any intermediate steps, transport change types etc other than the total number of changes - the assumption is that you'll know the details of your specified trip, you just want to know when the next departure is. If you need much more detailed information then I recommend that you use the full Transport NSW trip planner website or application.
|
189
|
+
Note that the origin and destination details are just that - information about the first and last stops on the journey at the time the request was made. The output doesn't include any intermediate steps, transport change types etc. other than the total number of changes - the assumption is that you'll know the details of your specified trip, you just want to know when the next departure is. If you need much more detailed information then I recommend that you use the full Transport NSW trip planner website or application, or parse the raw output by adding ```raw_output = True``` to your call.
|
183
190
|
|
184
|
-
|
191
|
+
## Exceptions ##
|
192
|
+
The latest release of TransportNSWv2 now uses custom Exceptions when things go wrong, instead of returning None - I think that's probably more 'Pythonic'. The Exceptions that can be imported are as follows:
|
193
|
+
* InvalidAPIKey - API key-related issues
|
194
|
+
* APIRateLimitExceeded - API rate-limit issues
|
195
|
+
* StopError - stop ID issues, usually when checking that a stop ID is valid
|
196
|
+
* TripError - trip-related issues, including no journeys being returned when calling ```.get_trip()```
|
185
197
|
|
186
|
-
|
198
|
+
## Rate limits ##
|
187
199
|
By default the TransportNSW API allows each API key to make 60,000 calls in a day and up to 5 calls per second. When requesting real-time location information some services required me to brute-force up to 12 (!) URIs until I found the right one which sometimes resulted in an API rate limt breach. From version 0.8.7 I found a TransportNSW-maintained CSV that contains mappings of bus agency IDs to URIs so I'm using that, plus adding in a 0.75 second delay between API calls. Alternatively, if you're confident that the origin and destination IDs are correct you can reduce your API calls by adding ```check_trip_ids = False``` in the parameters. Additionally there's a final option ```forced_gtfs_uri``` which, if you're super-confident you know what the GTFS URI is for your particular journey, will again reduce the API calls per trip query... although I'd use this one with caution! ```forced_gtfs_uri``` needs to be a single-item list, here's an example:
|
188
200
|
|
189
201
|
```forced_gtfs_uri = ["/lightrail/innerwest"]```
|
@@ -31,10 +31,11 @@ ATTR_CHANGES = 'changes'
|
|
31
31
|
|
32
32
|
ATTR_OCCUPANCY = 'occupancy'
|
33
33
|
|
34
|
+
ATTR_AVMS_TRIP_ID = 'avms_trip_id'
|
34
35
|
ATTR_REAL_TIME_TRIP_ID = 'real_time_trip_id'
|
35
36
|
ATTR_LATITUDE = 'latitude'
|
36
37
|
ATTR_LONGITUDE = 'longitude'
|
37
|
-
|
38
|
+
ATTR_GTFS_URI = 'gtfs_uri'
|
38
39
|
ATTR_ALERTS = 'alerts'
|
39
40
|
|
40
41
|
logger = logging.getLogger(__name__)
|
@@ -48,14 +49,6 @@ class TransportNSWv2(object):
|
|
48
49
|
|
49
50
|
def __init__(self):
|
50
51
|
"""Initialize the data object with default values."""
|
51
|
-
self.origin_id = None
|
52
|
-
self.destination_id = None
|
53
|
-
self.api_key = None
|
54
|
-
self.journey_wait_time = None
|
55
|
-
self.transport_type = None
|
56
|
-
self.strict_transport_type = None
|
57
|
-
self.raw_output = None
|
58
|
-
self.journeys_to_return = None
|
59
52
|
self.info = {
|
60
53
|
ATTR_DUE_IN : 'n/a',
|
61
54
|
ATTR_ORIGIN_STOP_ID : 'n/a',
|
@@ -78,7 +71,7 @@ class TransportNSWv2(object):
|
|
78
71
|
}
|
79
72
|
|
80
73
|
|
81
|
-
def check_stops(self, api_key, stops):
|
74
|
+
def check_stops(self, api_key, stops, home_assistant = False):
|
82
75
|
# Check the list of stops and return a JSON array of the stop details, plus if all the checked stops existed
|
83
76
|
# Return a JSON array of the results
|
84
77
|
|
@@ -120,43 +113,29 @@ class TransportNSWv2(object):
|
|
120
113
|
stop_detail = []
|
121
114
|
|
122
115
|
if response.status_code == 401:
|
123
|
-
|
124
|
-
skip_api_calls = True
|
125
|
-
all_stops_valid = False
|
126
|
-
logger.error(f"Error {str(response.status_code)} calling /v1/tp/stop_finder API; invalid API key")
|
116
|
+
raise InvalidAPIKey("Invalid API key")
|
127
117
|
|
128
118
|
elif response.status_code == 403 or response.status_code == 429:
|
129
|
-
|
130
|
-
# So let's assume that the stop ID is probably ok but we'll still raise a warning
|
131
|
-
logger.warn(f"Error {str(response.status_code)} calling /v1/tp/stop_finder API; rate limit exceeded - assuming stop is valid")
|
119
|
+
raise APIRateLimitExceeded("API rate limit exceeded")
|
132
120
|
|
133
121
|
else:
|
134
|
-
|
135
|
-
|
136
|
-
logger.error(f"Error {str(response.status_code)} calling /v1/tp/stop_finder API")
|
122
|
+
raise StopError("Unknown")
|
123
|
+
|
137
124
|
else:
|
138
125
|
# Parse the result as a JSON object
|
139
126
|
stop_detail = response.json()
|
140
127
|
|
141
128
|
# Just a quick check - the presence of systemMessages signifies an error, otherwise we assume it's ok
|
142
129
|
if 'systemMessages' in stop_detail:
|
143
|
-
all_stops_valid = False
|
144
|
-
error_code = stop_detail['systemMessages'][0]['code']
|
145
130
|
error_text = stop_detail['systemMessages'][0]['text']
|
146
|
-
|
147
|
-
|
148
|
-
logger.error(f"Error {error_code} calling /v1/tp/stop_finder API; {error_text} for Stop ID {stop}")
|
131
|
+
raise StopError(f"{error_text}", stop)
|
149
132
|
|
150
133
|
# Put in a pause here to try and make sure we stay under the 5 API calls/second limit
|
151
134
|
# Not usually an issue but if multiple processes are running multiple calls we might hit it
|
152
135
|
time.sleep(1.0)
|
153
136
|
|
154
137
|
except Exception as ex:
|
155
|
-
|
156
|
-
error_code = 999
|
157
|
-
stop_detail = []
|
158
|
-
|
159
|
-
logger.error(f"Error {str(ex)} calling /v1/tp/stop_finder API; assuming stop is invalid")
|
138
|
+
raise StopError(f"Error '{ex}' calling stop finder API for stop ID {stop}", stop)
|
160
139
|
|
161
140
|
finally:
|
162
141
|
# Append the results to the JSON output - only return the 'isBest' location entry if there's more than one
|
@@ -165,11 +144,11 @@ class TransportNSWv2(object):
|
|
165
144
|
for location in stop_detail['locations']:
|
166
145
|
if location['isBest']:
|
167
146
|
stop_detail = location
|
168
|
-
|
169
147
|
break
|
170
148
|
|
171
149
|
else:
|
172
150
|
stop_valid = False
|
151
|
+
all_stops_valid = False
|
173
152
|
|
174
153
|
#Add it to the list
|
175
154
|
data = {"stop_id": stop, "valid": stop_valid, "error_code": error_code, "stop_detail": stop_detail}
|
@@ -183,33 +162,24 @@ class TransportNSWv2(object):
|
|
183
162
|
|
184
163
|
def get_trip(self, name_origin, name_destination , api_key, journey_wait_time = 0, transport_type = 0, \
|
185
164
|
strict_transport_type = False, raw_output = False, journeys_to_return = 1, route_filter = '', \
|
186
|
-
include_realtime_location = True, include_alerts = 'none', alert_type = 'all', check_stop_ids = True, forced_gtfs_uri = []
|
165
|
+
include_realtime_location = True, include_alerts = 'none', alert_type = 'all', check_stop_ids = True, forced_gtfs_uri = [],
|
166
|
+
home_assistant = False):
|
187
167
|
|
188
168
|
"""Get the latest data from Transport NSW."""
|
189
169
|
fmt = '%Y-%m-%dT%H:%M:%SZ'
|
190
170
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
self.journey_wait_time = journey_wait_time
|
195
|
-
self.transport_type = transport_type
|
196
|
-
self.strict_transport_type = strict_transport_type
|
197
|
-
self.raw_output = raw_output
|
198
|
-
self.journeys_to_return = journeys_to_return
|
199
|
-
self.route_filter = route_filter.lower()
|
200
|
-
self.include_realtime_location = include_realtime_location
|
201
|
-
self.include_alerts = include_alerts.lower()
|
202
|
-
self.alert_type = alert_type.lower()
|
171
|
+
route_filter = route_filter.lower()
|
172
|
+
include_alerts = include_alerts.lower()
|
173
|
+
alert_type = alert_type.lower()
|
203
174
|
|
204
175
|
# This query always uses the current date and time - but add in any 'journey_wait_time' minutes
|
205
176
|
now_plus_wait = datetime.now() + timedelta(minutes = journey_wait_time)
|
206
177
|
itdDate = now_plus_wait.strftime('%Y%m%d')
|
207
178
|
itdTime = now_plus_wait.strftime('%H%M')
|
208
179
|
|
209
|
-
auth = 'apikey ' +
|
180
|
+
auth = 'apikey ' + api_key
|
210
181
|
header = {'Accept': 'application/json', 'Authorization': auth}
|
211
182
|
|
212
|
-
|
213
183
|
# First, check if the source and dest stops are valid unless we've been told not to
|
214
184
|
if check_stop_ids:
|
215
185
|
stop_list = [name_origin, name_destination]
|
@@ -222,9 +192,9 @@ class TransportNSWv2(object):
|
|
222
192
|
if not stop['valid']:
|
223
193
|
stop_error += stop['stop_id']+ ", "
|
224
194
|
|
225
|
-
logger.error(f"Stop ID(s) {stop_error[:-2]} do not exist - exiting")
|
226
|
-
|
227
|
-
|
195
|
+
#logger.error(f"Stop ID(s) {stop_error[:-2]} do not exist - exiting")
|
196
|
+
raise StopError (f"Stop ID(s) {stop_error[:-2]} do not exist", stop_error)
|
197
|
+
#return None
|
228
198
|
|
229
199
|
# We don't control how many journeys are returned any more, so need to be careful of running out of valid journeys if there is a filter in place, particularly a strict filter
|
230
200
|
# It would be more efficient to return one journey, check if the filter is met and then retrieve the next one via a new query if not, but for now we'll only be making use of the journeys we've been given
|
@@ -234,31 +204,32 @@ class TransportNSWv2(object):
|
|
234
204
|
'https://api.transport.nsw.gov.au/v1/tp/trip?' \
|
235
205
|
'outputFormat=rapidJSON&coordOutputFormat=EPSG%3A4326' \
|
236
206
|
'&depArrMacro=dep&itdDate=' + itdDate + '&itdTime=' + itdTime + \
|
237
|
-
'&type_origin=any&name_origin=' +
|
238
|
-
'&type_destination=any&name_destination=' +
|
207
|
+
'&type_origin=any&name_origin=' + name_origin + \
|
208
|
+
'&type_destination=any&name_destination=' + name_destination + \
|
239
209
|
'&TfNSWTR=true'
|
240
|
-
# '&calcNumberOfTrips=' + str(journeys_to_retrieve) + \
|
241
210
|
|
242
211
|
# Send the query and return an error if something goes wrong
|
243
212
|
# Otherwise store the response for the next steps
|
244
213
|
try:
|
245
214
|
response = requests.get(url, headers=header, timeout=10)
|
246
215
|
|
247
|
-
|
248
216
|
except Exception as ex:
|
249
|
-
|
250
|
-
return None
|
217
|
+
raise TripError (f"Error '{str(ex)}' calling trip API for journey {name_origin} to {name_destination}")
|
251
218
|
|
252
219
|
# If we get bad status code, log error and return with n/a or an empty string
|
253
220
|
if response.status_code != 200:
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
221
|
+
if response.status_code == 401:
|
222
|
+
# API key issue
|
223
|
+
raise InvalidAPIKey("Error 'Invalid API key' calling trip API for journey {name_origin} to {name_destination}")
|
224
|
+
|
225
|
+
elif response.status_code == 403 or response.status_code == 429:
|
226
|
+
raise APIRateLimitExceeded("Error 'API rate limit exceeded' calling trip API for journey {name_origin} to {name_destination}")
|
258
227
|
|
259
|
-
|
228
|
+
else:
|
229
|
+
raise TripError(f"Error '{str(response.status_cude)}' calling trip API for journey {name_origin} to {name_destination}")
|
230
|
+
|
231
|
+
return None
|
260
232
|
|
261
|
-
# Parse the result as a JSON object
|
262
233
|
result = response.json()
|
263
234
|
|
264
235
|
# The API will always return a valid trip, so it's just a case of grabbing the metadata that we need...
|
@@ -276,10 +247,7 @@ class TransportNSWv2(object):
|
|
276
247
|
retrieved_journeys = len(result['journeys'])
|
277
248
|
|
278
249
|
except:
|
279
|
-
|
280
|
-
logger.error(f"Error {(str(err))} calling /v1/tp/trip API")
|
281
|
-
|
282
|
-
return None
|
250
|
+
raise TripError(f"Error 'no journeys returned' calling trip API for journey {name_origin} to {name_destination}")
|
283
251
|
|
284
252
|
# Loop through the results applying filters where required, and generate the appropriate JSON output including an array of in-scope trips
|
285
253
|
json_output=''
|
@@ -335,6 +303,11 @@ class TransportNSWv2(object):
|
|
335
303
|
# We're also going to need the agency_id if it's a bus journey
|
336
304
|
agencyid = transportation['operator']['id']
|
337
305
|
|
306
|
+
# AVMSTripID is for Home Assistant, if needed
|
307
|
+
avmstripid = 'n/a'
|
308
|
+
if 'properties' in transportation and 'AVMSTripID' in transportation['properties']:
|
309
|
+
avmstripid = transportation['properties']['AVMSTripID']
|
310
|
+
|
338
311
|
# Line info
|
339
312
|
origin_line_name_short = "unknown"
|
340
313
|
if 'disassembledName' in transportation:
|
@@ -350,15 +323,16 @@ class TransportNSWv2(object):
|
|
350
323
|
occupancy = first_stop['properties']['occupancy']
|
351
324
|
|
352
325
|
alerts = "[]"
|
353
|
-
if
|
326
|
+
if include_alerts != 'none':
|
354
327
|
# We'll be adding these to the returned JSON string as an array
|
355
328
|
# Only include alerts of the specified priority or greater, and of the specified type
|
356
|
-
alerts = self._find_alerts(legs,
|
329
|
+
alerts = self._find_alerts(legs, include_alerts, alert_type)
|
357
330
|
|
358
331
|
latitude = 'n/a'
|
359
332
|
longitude = 'n/a'
|
333
|
+
mode_url = 'n/a'
|
360
334
|
|
361
|
-
if
|
335
|
+
if include_realtime_location and realtimetripid != 'n/a':
|
362
336
|
# See if we can get the latitute and longitude via the Realtime Vehicle Positions API
|
363
337
|
# Build the URL(s) - some modes have multiple GTFS sources, unforunately
|
364
338
|
# Some travel modes require brute-forcing the API call a few times, so if we're sure of the URI,
|
@@ -397,10 +371,12 @@ class TransportNSWv2(object):
|
|
397
371
|
break
|
398
372
|
else:
|
399
373
|
# Warn that we didn't get a good return
|
400
|
-
if response.status_code ==
|
401
|
-
logger.error(f"Error
|
374
|
+
if response.status_code == 401:
|
375
|
+
logger.error(f"Error 'Invalid API key' calling {url} API")
|
376
|
+
elif response.status_code == 403 or response.status_code == 429:
|
377
|
+
logger.error(f"Error 'API rate limit exceded' calling {url} API")
|
402
378
|
else:
|
403
|
-
logger.error(f"Error {str(response.status_code)} calling {url} API
|
379
|
+
logger.error(f"Error '{str(response.status_code)}' calling {url} API")
|
404
380
|
|
405
381
|
if bFoundTripID == True:
|
406
382
|
# No need to look any further
|
@@ -426,11 +402,17 @@ class TransportNSWv2(object):
|
|
426
402
|
ATTR_CHANGES: changes,
|
427
403
|
ATTR_OCCUPANCY : occupancy,
|
428
404
|
ATTR_REAL_TIME_TRIP_ID : realtimetripid,
|
405
|
+
ATTR_GTFS_URI: mode_url,
|
429
406
|
ATTR_LATITUDE : latitude,
|
430
407
|
ATTR_LONGITUDE : longitude,
|
431
408
|
ATTR_ALERTS: json.loads(alerts)
|
432
409
|
}
|
433
410
|
|
411
|
+
if home_assistant:
|
412
|
+
self.info.update (
|
413
|
+
{ATTR_AVMS_TRIP_ID: avmstripid}
|
414
|
+
)
|
415
|
+
|
434
416
|
found_journeys = found_journeys + 1
|
435
417
|
|
436
418
|
# Add to the return array
|
@@ -447,7 +429,7 @@ class TransportNSWv2(object):
|
|
447
429
|
|
448
430
|
current_journey_index = next_journey_index
|
449
431
|
|
450
|
-
json_output='{"journeys_to_return": ' + str(
|
432
|
+
json_output='{"journeys_to_return": ' + str(journeys_to_return) + ', "journeys_with_data": ' + str(found_journeys) + ', "journeys": [' + json_output + ']}'
|
451
433
|
return json_output
|
452
434
|
|
453
435
|
|
@@ -629,15 +611,17 @@ class TransportNSWv2(object):
|
|
629
611
|
try:
|
630
612
|
response = requests.get(url, timeout=5)
|
631
613
|
except Exception as ex:
|
632
|
-
logger.error("Error
|
614
|
+
logger.error(f"Error '{str(ex)}' querying GTFS URL datastore")
|
633
615
|
return None
|
634
616
|
|
635
617
|
# If we get bad status code, log error and return with None
|
636
618
|
if response.status_code != 200:
|
637
|
-
if response.status_code ==
|
638
|
-
logger.error("Error
|
619
|
+
if response.status_code == 401:
|
620
|
+
logger.error (f"Error 'Invalid API key' calling GTFS API url {url}")
|
621
|
+
elif response.status_code == 403 or response.status_code == 429:
|
622
|
+
logger.error(f"Error 'API rate limit exceeded' calling GTFS API url {url}")
|
639
623
|
else:
|
640
|
-
logger.error("Error
|
624
|
+
logger.error(f"Error '{str(response.status_code)}' calling GTFS API url {url}")
|
641
625
|
|
642
626
|
return None
|
643
627
|
|
@@ -668,3 +652,23 @@ class TransportNSWv2(object):
|
|
668
652
|
if estimated > datetime.utcnow():
|
669
653
|
due = round((estimated - datetime.utcnow()).seconds / 60)
|
670
654
|
return due
|
655
|
+
|
656
|
+
# Exceptions
|
657
|
+
class InvalidAPIKey(Exception):
|
658
|
+
""" API key error """
|
659
|
+
|
660
|
+
class APIRateLimitExceeded(Exception):
|
661
|
+
""" API rate limit exceeded """
|
662
|
+
|
663
|
+
class StopError(Exception):
|
664
|
+
""" Stop-finder related error """
|
665
|
+
def __init__(self, message = "", stop_detail = ""):
|
666
|
+
super().__init__(message)
|
667
|
+
self.stop_detail = stop_detail
|
668
|
+
|
669
|
+
# def __str__(self):
|
670
|
+
# return f"{self.message}"
|
671
|
+
|
672
|
+
class TripError(Exception):
|
673
|
+
"""" Trip-finder related error """
|
674
|
+
|
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
|
5
5
|
|
6
6
|
setuptools.setup(
|
7
7
|
name="PyTransportNSWv2",
|
8
|
-
version="0.
|
8
|
+
version="1.0.1",
|
9
9
|
author="andystewart999",
|
10
10
|
author_email="andy.stewart@live.com",
|
11
11
|
description="Get detailed per-trip transport information from TransportNSW",
|
File without changes
|
File without changes
|
{PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/PyTransportNSWv2.egg-info/TransportNSWv2.py
RENAMED
File without changes
|
{PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.1}/PyTransportNSWv2.egg-info/dependency_links.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|