PyTransportNSWv2 0.9.2__tar.gz → 1.0.0__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.0}/PKG-INFO +58 -46
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.0}/PyTransportNSWv2.egg-info/PKG-INFO +58 -46
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.0}/README.md +57 -45
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.0}/TransportNSWv2/TransportNSWv2.py +103 -56
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.0}/setup.py +1 -1
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.0}/LICENSE +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.0}/PyTransportNSWv2.egg-info/SOURCES.txt +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.0}/PyTransportNSWv2.egg-info/TransportNSWv2.py +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.0}/PyTransportNSWv2.egg-info/dependency_links.txt +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.0}/PyTransportNSWv2.egg-info/requires.txt +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.0}/PyTransportNSWv2.egg-info/top_level.txt +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.0}/TransportNSWv2/__init__.py +0 -0
- {PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.0}/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.0
|
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.0
|
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,6 +31,7 @@ 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'
|
@@ -121,31 +122,36 @@ class TransportNSWv2(object):
|
|
121
122
|
|
122
123
|
if response.status_code == 401:
|
123
124
|
# API key issue - log that, and don't bother with the other calls as they will all fail
|
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")
|
125
|
+
#skip_api_calls = True
|
126
|
+
#all_stops_valid = False
|
127
|
+
#logger.error(f"Error {str(response.status_code)} calling /v1/tp/stop_finder API; invalid API key")
|
128
|
+
raise InvalidAPIKey("Invalid API key")
|
127
129
|
|
128
130
|
elif response.status_code == 403 or response.status_code == 429:
|
129
131
|
# We've exceeded the rate limit but that doesn't mean the stop isn't valid
|
130
|
-
# So
|
131
|
-
logger.warn(f"Error {str(response.status_code)} calling /v1/tp/stop_finder API; rate limit exceeded - assuming stop is valid")
|
132
|
+
# So raise an API rate limit exception and let the user decide how to handle it
|
133
|
+
#logger.warn(f"Error {str(response.status_code)} calling /v1/tp/stop_finder API; rate limit exceeded - assuming stop is valid")
|
134
|
+
raise APIRateLimitExceeded("API rate limit exceeded")
|
132
135
|
|
133
136
|
else:
|
134
|
-
#
|
135
|
-
all_stops_valid = False
|
136
|
-
logger.error(f"Error {str(response.status_code)} calling /v1/tp/stop_finder API")
|
137
|
+
# Raise a generic error exception
|
138
|
+
#all_stops_valid = False
|
139
|
+
#logger.error(f"Error {str(response.status_code)} calling /v1/tp/stop_finder API")
|
140
|
+
raise StopError("Unknown")
|
141
|
+
|
137
142
|
else:
|
138
143
|
# Parse the result as a JSON object
|
139
144
|
stop_detail = response.json()
|
140
145
|
|
141
146
|
# Just a quick check - the presence of systemMessages signifies an error, otherwise we assume it's ok
|
142
147
|
if 'systemMessages' in stop_detail:
|
143
|
-
all_stops_valid = False
|
144
|
-
error_code = stop_detail['systemMessages'][0]['code']
|
148
|
+
#all_stops_valid = False
|
149
|
+
#error_code = stop_detail['systemMessages'][0]['code']
|
145
150
|
error_text = stop_detail['systemMessages'][0]['text']
|
146
|
-
stop_detail = []
|
147
|
-
|
148
|
-
|
151
|
+
#stop_detail = []
|
152
|
+
#logger.error(f"Error {error_code} calling /v1/tp/stop_finder API; {error_text} for Stop ID {stop}")
|
153
|
+
#raise StopError(f"Error {str(error_code)} ({str(error_text)}) calling stop finder API for stop ID {stop}")
|
154
|
+
raise StopError(f"{error_text}")
|
149
155
|
|
150
156
|
# Put in a pause here to try and make sure we stay under the 5 API calls/second limit
|
151
157
|
# Not usually an issue but if multiple processes are running multiple calls we might hit it
|
@@ -153,10 +159,11 @@ class TransportNSWv2(object):
|
|
153
159
|
|
154
160
|
except Exception as ex:
|
155
161
|
# Some other kind of error, we should assume that the stop is invalid
|
156
|
-
error_code = 999
|
157
|
-
stop_detail = []
|
162
|
+
#error_code = 999
|
163
|
+
#stop_detail = []
|
158
164
|
|
159
|
-
logger.error(f"Error {str(ex)} calling /v1/tp/stop_finder API; assuming stop is invalid")
|
165
|
+
#logger.error(f"Error {str(ex)} calling /v1/tp/stop_finder API; assuming stop is invalid")
|
166
|
+
raise StopError(f"Error '{ex}' calling stop finder API for stop ID {stop}")
|
160
167
|
|
161
168
|
finally:
|
162
169
|
# Append the results to the JSON output - only return the 'isBest' location entry if there's more than one
|
@@ -170,6 +177,7 @@ class TransportNSWv2(object):
|
|
170
177
|
|
171
178
|
else:
|
172
179
|
stop_valid = False
|
180
|
+
all_stops_valid = False
|
173
181
|
|
174
182
|
#Add it to the list
|
175
183
|
data = {"stop_id": stop, "valid": stop_valid, "error_code": error_code, "stop_detail": stop_detail}
|
@@ -183,33 +191,35 @@ class TransportNSWv2(object):
|
|
183
191
|
|
184
192
|
def get_trip(self, name_origin, name_destination , api_key, journey_wait_time = 0, transport_type = 0, \
|
185
193
|
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 = []
|
194
|
+
include_realtime_location = True, include_alerts = 'none', alert_type = 'all', check_stop_ids = True, forced_gtfs_uri = [],
|
195
|
+
home_assistant = False):
|
187
196
|
|
188
197
|
"""Get the latest data from Transport NSW."""
|
189
198
|
fmt = '%Y-%m-%dT%H:%M:%SZ'
|
190
199
|
|
191
|
-
self.name_origin = name_origin
|
192
|
-
self.destination = name_destination
|
193
|
-
self.api_key = api_key
|
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()
|
203
|
-
|
200
|
+
#self.name_origin = name_origin
|
201
|
+
#self.destination = name_destination
|
202
|
+
#self.api_key = api_key
|
203
|
+
#self.journey_wait_time = journey_wait_time
|
204
|
+
#self.transport_type = transport_type
|
205
|
+
#self.strict_transport_type = strict_transport_type
|
206
|
+
#self.raw_output = raw_output
|
207
|
+
#self.journeys_to_return = journeys_to_return
|
208
|
+
#self.route_filter = route_filter.lower()
|
209
|
+
#self.include_realtime_location = include_realtime_location
|
210
|
+
#self.include_alerts = include_alerts.lower()
|
211
|
+
#self.alert_type = alert_type.lower()
|
212
|
+
route_filter = route_filter.lower()
|
213
|
+
include_alerts = include_alerts.lower()
|
214
|
+
alert_type = alert_type.lower()
|
204
215
|
# This query always uses the current date and time - but add in any 'journey_wait_time' minutes
|
205
216
|
now_plus_wait = datetime.now() + timedelta(minutes = journey_wait_time)
|
206
217
|
itdDate = now_plus_wait.strftime('%Y%m%d')
|
207
218
|
itdTime = now_plus_wait.strftime('%H%M')
|
208
219
|
|
209
|
-
auth = 'apikey ' +
|
220
|
+
auth = 'apikey ' + api_key
|
210
221
|
header = {'Accept': 'application/json', 'Authorization': auth}
|
211
222
|
|
212
|
-
|
213
223
|
# First, check if the source and dest stops are valid unless we've been told not to
|
214
224
|
if check_stop_ids:
|
215
225
|
stop_list = [name_origin, name_destination]
|
@@ -222,9 +232,9 @@ class TransportNSWv2(object):
|
|
222
232
|
if not stop['valid']:
|
223
233
|
stop_error += stop['stop_id']+ ", "
|
224
234
|
|
225
|
-
logger.error(f"Stop ID(s) {stop_error[:-2]} do not exist - exiting")
|
226
|
-
|
227
|
-
|
235
|
+
#logger.error(f"Stop ID(s) {stop_error[:-2]} do not exist - exiting")
|
236
|
+
raise StopError (f"Stop ID(s) {stop_error[:-2]} do not exist")
|
237
|
+
#return None
|
228
238
|
|
229
239
|
# 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
240
|
# 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,8 +244,8 @@ class TransportNSWv2(object):
|
|
234
244
|
'https://api.transport.nsw.gov.au/v1/tp/trip?' \
|
235
245
|
'outputFormat=rapidJSON&coordOutputFormat=EPSG%3A4326' \
|
236
246
|
'&depArrMacro=dep&itdDate=' + itdDate + '&itdTime=' + itdTime + \
|
237
|
-
'&type_origin=any&name_origin=' +
|
238
|
-
'&type_destination=any&name_destination=' +
|
247
|
+
'&type_origin=any&name_origin=' + name_origin + \
|
248
|
+
'&type_destination=any&name_destination=' + destination + \
|
239
249
|
'&TfNSWTR=true'
|
240
250
|
# '&calcNumberOfTrips=' + str(journeys_to_retrieve) + \
|
241
251
|
|
@@ -244,21 +254,27 @@ class TransportNSWv2(object):
|
|
244
254
|
try:
|
245
255
|
response = requests.get(url, headers=header, timeout=10)
|
246
256
|
|
247
|
-
|
248
257
|
except Exception as ex:
|
249
|
-
logger.error(f"Error {str(ex)} calling /v1/tp/trip API")
|
250
|
-
return None
|
258
|
+
#logger.error(f"Error {str(ex)} calling /v1/tp/trip API")
|
259
|
+
#return None
|
260
|
+
raise TripError (f"Error '{str(ex)}' calling trip API for journey {self.name_origin} to {self.destination}")
|
251
261
|
|
252
262
|
# If we get bad status code, log error and return with n/a or an empty string
|
253
263
|
if response.status_code != 200:
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
logger.error(f"Error {str(response.status_code)} calling /v1/tp/trip API; check API key")
|
264
|
+
if response.status_code == 401:
|
265
|
+
# API key issue
|
266
|
+
raise InvalidAPIKey("Error 'Invalid API key' calling trip API for journey {self.name_origin} to {self.destination}")
|
258
267
|
|
259
|
-
|
268
|
+
elif response.status_code == 403 or response.status_code == 429:
|
269
|
+
raise APIRateLimitExceeded("Error 'API rate limit exceeded' calling trip API for journey {self.name_origin} to {self.destination}")
|
270
|
+
#logger.error(f"Error {str(response.status_code)} calling /v1/tp/trip API; rate limit exceeded")
|
271
|
+
|
272
|
+
else:
|
273
|
+
raise TripError(f"Error '{str(response.status_cude)}' calling trip API for journey {self.name_origin} to {self.destination}")
|
274
|
+
#logger.error(f"Error {str(response.status_code)} calling /v1/tp/trip API; check API key")
|
275
|
+
|
276
|
+
return None
|
260
277
|
|
261
|
-
# Parse the result as a JSON object
|
262
278
|
result = response.json()
|
263
279
|
|
264
280
|
# The API will always return a valid trip, so it's just a case of grabbing the metadata that we need...
|
@@ -277,9 +293,9 @@ class TransportNSWv2(object):
|
|
277
293
|
|
278
294
|
except:
|
279
295
|
# Looks like an empty response
|
280
|
-
logger.error(f"Error {(str(err))} calling /v1/tp/trip API")
|
281
|
-
|
282
|
-
return None
|
296
|
+
#logger.error(f"Error {(str(err))} calling /v1/tp/trip API")
|
297
|
+
raise TripError(f"Error 'no journeys returned' calling trip API for journey {self.name_origin} to {self.destination}")
|
298
|
+
#return None
|
283
299
|
|
284
300
|
# Loop through the results applying filters where required, and generate the appropriate JSON output including an array of in-scope trips
|
285
301
|
json_output=''
|
@@ -335,6 +351,11 @@ class TransportNSWv2(object):
|
|
335
351
|
# We're also going to need the agency_id if it's a bus journey
|
336
352
|
agencyid = transportation['operator']['id']
|
337
353
|
|
354
|
+
# AVMSTripID is for Home Assistant, if needed
|
355
|
+
avmstripid = 'n/a'
|
356
|
+
if 'properties' in transportation and 'AVMSTripID' in transportation['properties']:
|
357
|
+
avmstripid = transportation['properties']['AVMSTripID']
|
358
|
+
|
338
359
|
# Line info
|
339
360
|
origin_line_name_short = "unknown"
|
340
361
|
if 'disassembledName' in transportation:
|
@@ -397,10 +418,12 @@ class TransportNSWv2(object):
|
|
397
418
|
break
|
398
419
|
else:
|
399
420
|
# Warn that we didn't get a good return
|
400
|
-
if response.status_code ==
|
401
|
-
logger.error(f"Error
|
421
|
+
if response.status_code == 401:
|
422
|
+
logger.error(f"Error 'Invalid API key' calling {url} API")
|
423
|
+
elif response.status_code == 403 or response.status_code == 429:
|
424
|
+
logger.error(f"Error 'API rate limit exceded' calling {url} API")
|
402
425
|
else:
|
403
|
-
logger.error(f"Error {str(response.status_code)} calling {url} API
|
426
|
+
logger.error(f"Error '{str(response.status_code)}' calling {url} API")
|
404
427
|
|
405
428
|
if bFoundTripID == True:
|
406
429
|
# No need to look any further
|
@@ -431,6 +454,11 @@ class TransportNSWv2(object):
|
|
431
454
|
ATTR_ALERTS: json.loads(alerts)
|
432
455
|
}
|
433
456
|
|
457
|
+
if self.home_assistant:
|
458
|
+
self.info.update (
|
459
|
+
{ATTR_AVMS_TRIP_ID: avmstripid}
|
460
|
+
)
|
461
|
+
|
434
462
|
found_journeys = found_journeys + 1
|
435
463
|
|
436
464
|
# Add to the return array
|
@@ -629,15 +657,17 @@ class TransportNSWv2(object):
|
|
629
657
|
try:
|
630
658
|
response = requests.get(url, timeout=5)
|
631
659
|
except Exception as ex:
|
632
|
-
logger.error("Error
|
660
|
+
logger.error(f"Error '{str(ex)}' querying GTFS URL datastore")
|
633
661
|
return None
|
634
662
|
|
635
663
|
# If we get bad status code, log error and return with None
|
636
664
|
if response.status_code != 200:
|
637
|
-
if response.status_code ==
|
638
|
-
logger.error("Error
|
665
|
+
if response.status_code == 401:
|
666
|
+
logger.error (f"Error 'Invalid API key' calling GTFS API url {url}")
|
667
|
+
elif response.status_code == 403 or response.status_code == 429:
|
668
|
+
logger.error(f"Error 'API rate limit exceeded' calling GTFS API url {url}")
|
639
669
|
else:
|
640
|
-
logger.error("Error
|
670
|
+
logger.error(f"Error '{str(response.status_code)}' calling GTFS API url {url}")
|
641
671
|
|
642
672
|
return None
|
643
673
|
|
@@ -668,3 +698,20 @@ class TransportNSWv2(object):
|
|
668
698
|
if estimated > datetime.utcnow():
|
669
699
|
due = round((estimated - datetime.utcnow()).seconds / 60)
|
670
700
|
return due
|
701
|
+
|
702
|
+
# Exceptions
|
703
|
+
class TFNSWAPIError(Exception):
|
704
|
+
""" Base error for all exceptions """
|
705
|
+
|
706
|
+
class InvalidAPIKey(TFNSWAPIError):
|
707
|
+
""" API key error """
|
708
|
+
|
709
|
+
class APIRateLimitExceeded(TFNSWAPIError):
|
710
|
+
""" API rate limit exceeded """
|
711
|
+
|
712
|
+
class StopError(TFNSWAPIError):
|
713
|
+
""" Stop-finder related error """
|
714
|
+
|
715
|
+
class TripError(TFNSWAPIError):
|
716
|
+
"""" Trip-finder related error """
|
717
|
+
|
@@ -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.0",
|
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.0}/PyTransportNSWv2.egg-info/TransportNSWv2.py
RENAMED
File without changes
|
{PyTransportNSWv2-0.9.2 → PyTransportNSWv2-1.0.0}/PyTransportNSWv2.egg-info/dependency_links.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|