PyTransportNSWv2 0.8.5__tar.gz → 0.8.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyTransportNSWv2
3
- Version: 0.8.5
3
+ Version: 0.8.7
4
4
  Summary: Get detailed per-trip transport information from TransportNSW
5
5
  Home-page: https://github.com/andystewart999/TransportNSW
6
6
  Author: andystewart999
@@ -148,7 +148,7 @@ Description: # TransportNSWv2
148
148
  Also note that the 'transport_type' filter, if present, only makes sure that at least **one** leg of the journey includes that transport type unless ```strict_transport_type``` is True, in which case the **first** leg must be of the requested type to be returned.
149
149
 
150
150
  ### Rate limits ###
151
- By default the TransportNSW API allows each API key to make 60,000 calls in a day and up to 5 calls per second. Usually this wouldn't be an issue but if you're requesting real-time location data for buses, because I haven't found an elegant way to map the bus route to the correct API URL (and there are about 14) it's brute-forcing it by iterating through ALL the possible URLs until it finds the appropriate journey or it runs out of URLs. From version 0.8.2 I've added in a 0.5 second delay between loops until I can find a better approach.
151
+ By default the TransportNSW API allows each API key to make 60,000 calls in a day and up to 5 calls per second. From version 0.8.7 I found a TransportNSW-maintained CSV that contains mappings of bus agency IDs to URLs so I'm using that, plus adding in a 0.75 second delay between API calls.
152
152
 
153
153
  ## Thank you
154
154
  Thank you Dav0815 for your TransportNSW library that the vast majority of this fork is based on. I couldn't have done it without you!
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyTransportNSWv2
3
- Version: 0.8.5
3
+ Version: 0.8.7
4
4
  Summary: Get detailed per-trip transport information from TransportNSW
5
5
  Home-page: https://github.com/andystewart999/TransportNSW
6
6
  Author: andystewart999
@@ -148,7 +148,7 @@ Description: # TransportNSWv2
148
148
  Also note that the 'transport_type' filter, if present, only makes sure that at least **one** leg of the journey includes that transport type unless ```strict_transport_type``` is True, in which case the **first** leg must be of the requested type to be returned.
149
149
 
150
150
  ### Rate limits ###
151
- By default the TransportNSW API allows each API key to make 60,000 calls in a day and up to 5 calls per second. Usually this wouldn't be an issue but if you're requesting real-time location data for buses, because I haven't found an elegant way to map the bus route to the correct API URL (and there are about 14) it's brute-forcing it by iterating through ALL the possible URLs until it finds the appropriate journey or it runs out of URLs. From version 0.8.2 I've added in a 0.5 second delay between loops until I can find a better approach.
151
+ By default the TransportNSW API allows each API key to make 60,000 calls in a day and up to 5 calls per second. From version 0.8.7 I found a TransportNSW-maintained CSV that contains mappings of bus agency IDs to URLs so I'm using that, plus adding in a 0.75 second delay between API calls.
152
152
 
153
153
  ## Thank you
154
154
  Thank you Dav0815 for your TransportNSW library that the vast majority of this fork is based on. I couldn't have done it without you!
@@ -140,7 +140,7 @@ Note also that the origin and destination details are just that - information ab
140
140
  Also note that the 'transport_type' filter, if present, only makes sure that at least **one** leg of the journey includes that transport type unless ```strict_transport_type``` is True, in which case the **first** leg must be of the requested type to be returned.
141
141
 
142
142
  ### Rate limits ###
143
- By default the TransportNSW API allows each API key to make 60,000 calls in a day and up to 5 calls per second. Usually this wouldn't be an issue but if you're requesting real-time location data for buses, because I haven't found an elegant way to map the bus route to the correct API URL (and there are about 14) it's brute-forcing it by iterating through ALL the possible URLs until it finds the appropriate journey or it runs out of URLs. From version 0.8.2 I've added in a 0.5 second delay between loops until I can find a better approach.
143
+ By default the TransportNSW API allows each API key to make 60,000 calls in a day and up to 5 calls per second. From version 0.8.7 I found a TransportNSW-maintained CSV that contains mappings of bus agency IDs to URLs so I'm using that, plus adding in a 0.75 second delay between API calls.
144
144
 
145
145
  ## Thank you
146
146
  Thank you Dav0815 for your TransportNSW library that the vast majority of this fork is based on. I couldn't have done it without you!
@@ -115,8 +115,8 @@ class TransportNSWv2(object):
115
115
 
116
116
  # Send the query and return an error if something goes wrong
117
117
  try:
118
- response = requests.get(url, headers=header, timeout=20)
119
- except:
118
+ response = requests.get(url, headers=header, timeout=5)
119
+ except Exception as ex:
120
120
  logger.error("Network or Timeout error when calling /v1/tp/stop_finder API")
121
121
  return None
122
122
 
@@ -157,7 +157,7 @@ class TransportNSWv2(object):
157
157
  # Send the query and return an error if something goes wrong
158
158
  # Otherwise store the response
159
159
  try:
160
- response = requests.get(url, headers=header, timeout=20)
160
+ response = requests.get(url, headers=header, timeout=10)
161
161
  except:
162
162
  logger.error("Network or timeout error")
163
163
  return None
@@ -240,6 +240,9 @@ class TransportNSWv2(object):
240
240
  if 'RealtimeTripId' in transportation['properties']:
241
241
  realtimetripid = transportation['properties']['RealtimeTripId']
242
242
 
243
+ # We're also going to need the agency_id
244
+ agencyid = transportation['operator']['id']
245
+
243
246
  # Line info
244
247
  origin_line_name_short = "unknown"
245
248
  if 'disassembledName' in transportation:
@@ -267,44 +270,42 @@ class TransportNSWv2(object):
267
270
  if ((realtimetripid != 'n/a') and (self.include_realtime_location) == True):
268
271
  # See if we can get the latitute and longitude via the Realtime Vehicle Positions API
269
272
  # Build the URL(s) - some modes have multiple GTFS sources, unforunately
270
- url_path = self.get_url(origin_mode)
271
- url_list = self.get_url_path(origin_mode)
272
- bFoundTripID = False
273
-
274
- auth = 'apikey ' + self.api_key
275
- header = {'Authorization': auth}
276
-
277
- for mode_url in url_list:
278
- url = url_path + mode_url
279
-
280
- response = requests.get(url, headers=header, timeout=10)
281
-
282
- # Only try and process the results if we got a good return code
283
- if response.status_code == 200:
284
- # Search the feed and see if we can match realtimetripid to trip_id
285
- # If we do, capture the latitude and longitude
286
- feed = gtfs_realtime_pb2.FeedMessage()
287
- feed.ParseFromString(response.content)
288
-
289
- reg = re.compile(realtimetripid)
290
-
291
- for entity in feed.entity:
292
- if bool(re.match(reg, entity.vehicle.trip.trip_id)):
293
- latitude = entity.vehicle.position.latitude
294
- longitude = entity.vehicle.position.longitude
295
-
296
- # We found it, so flag it and break out
297
- bFoundTripID = True
298
- break
299
273
 
300
- if bFoundTripID == True:
301
- # No need to look any further
302
- break
303
-
304
- # Put in a quick pause here to try and make sure we stay under the 5 API calls/second limit
305
- # Not usually an issue but if multiple processes are running multiple calls we might hit it
306
- time.sleep(0.5)
274
+ url_base_path = self.get_base_url(origin_mode)
275
+ url_mode_list = self.get_mode_list(origin_mode, agencyid)
276
+ bFoundTripID = False
307
277
 
278
+ #auth = 'apikey ' + self.api_key
279
+ #header = {'Authorization': auth}
280
+
281
+ if not url_mode_list is None:
282
+ for mode_url in url_mode_list:
283
+ url = url_base_path + mode_url
284
+ response = requests.get(url, headers=header, timeout=10)
285
+ # Only try and process the results if we got a good return code
286
+ if response.status_code == 200:
287
+ # Search the feed and see if we can match realtimetripid to trip_id
288
+ # If we do, capture the latitude and longitude
289
+ feed = gtfs_realtime_pb2.FeedMessage()
290
+ feed.ParseFromString(response.content)
291
+ reg = re.compile(realtimetripid)
292
+
293
+ for entity in feed.entity:
294
+ if bool(re.match(reg, entity.vehicle.trip.trip_id)):
295
+ latitude = entity.vehicle.position.latitude
296
+ longitude = entity.vehicle.position.longitude
297
+
298
+ # We found it, so flag it and break out
299
+ bFoundTripID = True
300
+ break
301
+
302
+ if bFoundTripID == True:
303
+ # No need to look any further
304
+ break
305
+
306
+ # Put in a quick pause here to try and make sure we stay under the 5 API calls/second limit
307
+ # Not usually an issue but if multiple processes are running multiple calls we might hit it
308
+ time.sleep(0.75)
308
309
 
309
310
  self.info = {
310
311
  ATTR_DUE_IN: due,
@@ -490,7 +491,7 @@ class TransportNSWv2(object):
490
491
  }
491
492
  return modes.get(iconId, None)
492
493
 
493
- def get_url(self, mode):
494
+ def get_base_url(self, mode):
494
495
  """Map the journey mode to the proper base real time location URL """
495
496
  v1_url = "https://api.transport.nsw.gov.au/v1/gtfs/vehiclepos"
496
497
  v2_url = "https://api.transport.nsw.gov.au/v2/gtfs/vehiclepos"
@@ -521,21 +522,50 @@ class TransportNSWv2(object):
521
522
  return alert_priorities.get(alert_priority, 99)
522
523
 
523
524
 
524
- def get_url_path(self, mode):
525
- """Map the journey mode to the proper modifier URL """
526
- # There's probably a better way of mapping the bus route/stop/line number to the correct URL to call in the GTFS API, but as of now I don't know it... hence the brute force approach, although I hate the lack of elegance
527
- bus_list = ["/buses", "/regionbuses/centralwestandorana", "/regionbuses/centralwestandorana2", "/regionbuses/newenglandnorthwest", "/regionbuses/northcoast", "/regionbuses/northcoast2", "/regionbuses/northcoast3", "/regionbuses/riverinamurray", "/regionbuses/riverinamurray2", "/regionbuses/southeasttablelands", "/regionbuses/southeasttablelands2", "/regionbuses/sydneysurrounds", "/regionbuses/newcastlehunter", "/regionbuses/farwest"]
525
+ def get_mode_list(self, mode, agencyid):
526
+ """
527
+ Map the journey mode to the proper modifier URL. If the mode is Bus, Coach or School bus then use the agency ID to invoke the GTFS datastore search API
528
+ which will give us the appropriate URL to call later - we still have to do light rail the old-fashioned, brute-force way though
529
+ """
528
530
 
529
- url_options = {
530
- "Train" : ["/sydneytrains"],
531
- "Metro" : ["/metro"],
532
- "Light rail" : ["/lightrail/innerwest", "/lightrail/cbdandsoutheast", "/lightrail/newcastle"],
533
- "Bus" : bus_list,
534
- "Coach" : bus_list,
535
- "Ferry" : ["/ferries/sydneyferries"],
536
- "School bus" : bus_list
537
- }
538
- return url_options.get(mode, None)
531
+ if mode in ["Bus", "Coach", "School bus"]:
532
+ # Use the API to determine the appropriate URL
533
+ url = "https://opendata.transport.nsw.gov.au/data/api/action/datastore_search?resource_id=30b850b7-f439-4e30-8072-e07ef62a2a36&filters={%22For%20Realtime%20GTFS%20agency_id%22:%22" + agencyid + "%22}&limit=1"
534
+
535
+ # Send the query and return an error if something goes wrong
536
+ try:
537
+ response = requests.get(url, timeout=5)
538
+ except Exception as ex:
539
+ logger.error("Error " + str(ex) + " querying GTFS URL datastore")
540
+ return None
541
+
542
+ # If we get bad status code, log error and return with None
543
+ if response.status_code != 200:
544
+ if response.status_code == 429:
545
+ logger.error("Error " + str(response.status_code) + " calling /v1/tp/stop_finder API; rate limit exceeded")
546
+ else:
547
+ logger.error("Error " + str(response.status_code) + " calling /v1/tp/stop_finder API; check api key")
548
+
549
+ return None
550
+
551
+ # Parse the result as JSON
552
+ result = response.json()
553
+ if 'records' in result['result'] and len(result['result']['records']) > 0:
554
+ mode_path = result['result']['records'][0]['For Realtime parameter']
555
+ else:
556
+ return None
557
+
558
+ bus_list = ["/" + mode_path]
559
+ return bus_list
560
+ else:
561
+ # Handle the other modes
562
+ url_options = {
563
+ "Train" : ["/sydneytrains"],
564
+ "Metro" : ["/metro"],
565
+ "Light rail" : ["/lightrail/innerwest", "/lightrail/cbdandsoutheast", "/lightrail/newcastle"],
566
+ "Ferry" : ["/ferries/sydneyferries"]
567
+ }
568
+ return url_options.get(mode, None)
539
569
 
540
570
 
541
571
  def get_due(self, estimated):
@@ -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.5",
8
+ version="0.8.7",
9
9
  author="andystewart999",
10
10
  author_email="andy.stewart@live.com",
11
11
  description="Get detailed per-trip transport information from TransportNSW",