From eccdcfc8c55ded85f5a9297128cca42d19f2f59c Mon Sep 17 00:00:00 2001 From: athuler <22741115+athuler@users.noreply.github.com> Date: Mon, 19 Aug 2024 17:49:29 +0200 Subject: [PATCH 01/24] Get Route By ID - Implemented #17 --- CHANGELOG.md | 13 +++++++++++++ README.md | 1 + docs/api.md | 21 +++++++++++++++++++++ passiogo/__init__.py | 21 ++++++++++++++++++++- tests/test_main.py | 5 ++++- 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 846cab7..6316503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Changelog +## 0.3.0 (Unreleased) + +### Added + +- `TransportationSystem.getRouteById()` method to get a Route object by its ID + +### Changed + +- `Route.id` is now handled as a string + +### Removed + + ## 0.2.1 (2024-08-18) ### Added diff --git a/README.md b/README.md index 8f7aedd..048431b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![PyPI - Version](https://img.shields.io/pypi/v/passiogo?label=Latest%20Version&link=https%3A%2F%2Fpypi.org%2Fproject%2FPassioGo%2F)](https://pypi.org/project/PassioGo/) [![Pepy Total Downlods](https://img.shields.io/pepy/dt/PassioGo)](https://www.pepy.tech/projects/passiogo) +[![GitHub Testing](https://img.shields.io/github/actions/workflow/status/athuler/passiogo/testing.yml?branch=main&label=tests)](https://github.com/athuler/PassioGo) [![Documentation Status](https://readthedocs.org/projects/passiogo/badge/?version=latest)](https://passiogo.readthedocs.io/en/latest/?badge=latest) diff --git a/docs/api.md b/docs/api.md index ac0e905..af1f555 100644 --- a/docs/api.md +++ b/docs/api.md @@ -118,6 +118,27 @@ passiogo.getSystemFromID(1068).getRoutes() ] ``` +### `TransportationSystem.getRouteById()` + +Get all routes for the appropriate system. + +**Input**: + +- **routeId** (*str*): ID of the desired route +- **appVersion** (*int*): Version of the application (Default: 1) +- **amount** (*int*): Unknown (Default: 1) + +**Output**: [`Route`](#route) + +```python +passiogo.getSystemFromID(1068).getRouteById(133007) +``` + +``` + +``` + + ### `TransportationSystem.getStops()` Gets all stops for the given transportation system. diff --git a/passiogo/__init__.py b/passiogo/__init__.py index 39bdf71..dcf6cf4 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -188,6 +188,25 @@ def getRoutes( return(allRoutes) + def getRouteById( + self, + routeId: str, + appVersion: int = 1, + amount: int = 1 + ) -> "Route": + """ + Returns a Route object corresponding to the provided ID. + """ + allRoutes = self.getRoutes( + appVersion = appVersion, + amount = amount + ) + + for route in allRoutes: + if str(route.id) == str(routeId): + return route + return None + def getStops( self, appVersion = 2, @@ -493,7 +512,7 @@ class Route: def __init__( self, - id: int, + id: str, groupId: int = None, groupColor: str = None, name: str = None, diff --git a/tests/test_main.py b/tests/test_main.py index 9ebc91a..dbb8a54 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -23,6 +23,9 @@ def test_getSystemFromId(): testSystem = passiogo.getSystemFromID(1068) assert True +def test_getRouteById(): + testSystem.getRouteById(133007) + pass @pytest.mark.parametrize("system", pytest.allSystems, ids=ids) def test_getAllRoutes(system): @@ -41,4 +44,4 @@ def test_getSystemAlerts(system): @pytest.mark.parametrize("system", pytest.allSystems, ids=ids) def test_getVehicles(system): system.getVehicles() - \ No newline at end of file + From e74182b4701aade530a87ca7ae7bc835232aa335 Mon Sep 17 00:00:00 2001 From: athuler <22741115+athuler@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:28:34 +0200 Subject: [PATCH 02/24] get Stop by ID implementation Implemented #21 --- CHANGELOG.md | 1 + README.md | 2 +- docs/api.md | 24 ++++++++++++++++++++++-- passiogo/__init__.py | 23 ++++++++++++++++++++++- setup.py | 2 +- tests/test_main.py | 3 +++ 6 files changed, 50 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6316503..66d32d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Added - `TransportationSystem.getRouteById()` method to get a Route object by its ID +- `TransportationSystem.getStopById()` method to get a Stop object by its ID ### Changed diff --git a/README.md b/README.md index 048431b..6223b12 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ pip install passiogo ## Documentation -Project documentation for the latest stable version is available at [passiogo.readthedocs.io](https://passiogo.readthedocs.io/). Documentation for other versions is available at [passiogo.readthedocs.io/en/X.X.X](https://passiogo.readthedocs.io/en/0.1.2/). +Project documentation for the latest stable version is available at [passiogo.readthedocs.io](https://passiogo.readthedocs.io/). Documentation for other versions is available at [passiogo.readthedocs.io/en/X.X.X](https://passiogo.readthedocs.io/en/0.3.0/). The documentation is built using `mkdocs` and can be rebuilt using the following: diff --git a/docs/api.md b/docs/api.md index af1f555..e0a95c0 100644 --- a/docs/api.md +++ b/docs/api.md @@ -120,7 +120,7 @@ passiogo.getSystemFromID(1068).getRoutes() ### `TransportationSystem.getRouteById()` -Get all routes for the appropriate system. +Get the route for the appropriate ID. **Input**: @@ -128,7 +128,7 @@ Get all routes for the appropriate system. - **appVersion** (*int*): Version of the application (Default: 1) - **amount** (*int*): Unknown (Default: 1) -**Output**: [`Route`](#route) +**Output**: [`Route`](#route) or *None* if no match ```python passiogo.getSystemFromID(1068).getRouteById(133007) @@ -168,6 +168,26 @@ passiogo.getSystemFromID(1068).getStops() ] ``` +### `TransportationSystem.getStopById()` + +Get the stop for the appropriate ID. + +**Input**: + +- **stopId** (*str*): ID of the desired stop +- **appVersion** (*int*): Version of the application (Default: 1) +- **sA** (*int*): Unknown (Default: 1) + +**Output**: [`Stop`](#stop) or *None* if no match + +```python +passiogo.getSystemFromID(3499).getStopById(140059) +``` + +``` + +``` + ### `TransportationSystem.getSystemAlerts()` Gets all alerts for the corresponding transportation system. diff --git a/passiogo/__init__.py b/passiogo/__init__.py index dcf6cf4..7b98bde 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -211,7 +211,7 @@ def getStops( self, appVersion = 2, sA = 1, - raw = False + raw = False, ) -> list["Stop"]: """ Obtains all stop for the given system. @@ -291,6 +291,27 @@ def getStops( return(allStops) + def getStopById( + self, + stopId: str, + appVersion = 2, + sA = 1, + raw = False, + ) -> "Stop": + """ + Returns the Stop object corresponding to the passed ID. + """ + allStops = self.getStops( + appVersion = appVersion, + sA = sA, + raw = raw, + ) + + for stop in allStops: + if str(stop.id) == str(stopId): + return stop + return None + def getSystemAlerts( self, appVersion = 1, diff --git a/setup.py b/setup.py index 5d3df85..b08d9c0 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='PassioGo', - version="0.2.1", + version="0.3.0", description="An unofficial API for Passio Go", long_description=long_description, long_description_content_type='text/markdown', diff --git a/tests/test_main.py b/tests/test_main.py index dbb8a54..e74c60f 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -31,6 +31,9 @@ def test_getRouteById(): def test_getAllRoutes(system): system.getRoutes() +def test_getStopById(): + testSystem.getStopById(140059) + pass @pytest.mark.parametrize("system", pytest.allSystems, ids=ids) def test_getAllStops(system): From 0c5e2cc19717ef9b27608fc79baa53fd9330277c Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Sat, 28 Sep 2024 12:06:26 -0400 Subject: [PATCH 03/24] Fixed some missing route and vehicle values. Added optional return types, removed None comparison with == and some redundant parentheses. Began testing on live timing. Added getStopById to Routes and Systems for use with live timing. --- passiogo/__init__.py | 97 ++++++++++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index e80855b..02eb5ae 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -1,4 +1,6 @@ import json +from typing import Optional + import requests import websocket @@ -8,9 +10,9 @@ ### Helper Functions ### def toIntInclNone(toInt): - if toInt == None: + if toInt is None: return toInt - return(int(toInt)) + return int(toInt) def sendApiRequest(url, body): @@ -23,7 +25,6 @@ def sendApiRequest(url, body): response = response.json() except Exception as e: raise Exception(f"Error converting API response to JSON! Here is the response received: {response}") - return None # Handle API Error @@ -33,7 +34,7 @@ def sendApiRequest(url, body): ): raise Exception(f"Error in Response! Here is the received response: {response}") - return(response) + return response @@ -124,7 +125,7 @@ def getRoutes( self, appVersion = 1, amount = 1 - ) -> list["Route"]: + ) -> Optional[list["Route"]]: """ Obtains every route for the selected system. ========= @@ -146,8 +147,8 @@ def getRoutes( routes = sendApiRequest(url, body) # Handle Request Error - if(routes == None): - return(None) + if routes is None: + return None # Handle Differing Response Format @@ -186,14 +187,14 @@ def getRoutes( system = self )) - return(allRoutes) + return allRoutes def getStops( self, appVersion = 2, sA = 1, raw = False - ) -> list["Stop"]: + ) -> Optional[list["Stop"]]: """ Obtains all stop for the given system. ========= @@ -215,18 +216,18 @@ def getStops( # Return Raw Response if raw: - return(stops) + return stops # Handle Request Error - if(stops == None): - return(None) + if stops is None: + return None # Handle Empty Routes - if stops["routes"] == []: + if not stops["routes"]: stops["routes"] = {} # Handle Empty Stops - if stops["stops"] == []: + if not stops["stops"]: stops["stops"] = {} @@ -270,14 +271,22 @@ def getStops( system = self, )) - return(allStops) + return allStops + + def getStopById(self, stopId): + stops = self.getStops() + for stop in stops: + if int(stop.id) == stopId: + return stop + + return None def getSystemAlerts( self, appVersion = 1, amount = 1, routesAmount = 0 - ) -> list["SystemAlert"]: + ) -> Optional[list["SystemAlert"]]: """ Gets all system alerts for the selected system. ========= @@ -298,8 +307,8 @@ def getSystemAlerts( errorMsgs = sendApiRequest(url, body) # Handle Request Error - if(errorMsgs == None): - return(None) + if errorMsgs is None: + return None # Create SystemAlert Objects allAlerts = [] @@ -336,12 +345,12 @@ def getSystemAlerts( toOk = errorMsg["toOk"], )) - return(allAlerts) + return allAlerts def getVehicles( self, appVersion = 2 - ) -> list["Vehicle"]: + ) -> Optional[list["Vehicle"]]: """ Gets all currently running buses. ========= @@ -361,8 +370,8 @@ def getVehicles( vehicles = sendApiRequest(url, body) # Handle Request Error - if(vehicles == None): - return(None) + if vehicles is None : + return None allVehicles = [] for vehicleId, vehicle in vehicles["buses"].items(): @@ -395,21 +404,21 @@ def getVehicles( tripId = vehicle["tripId"], )) - return(allVehicles) + return allVehicles def getSystems( appVersion = 2, sortMode = 1, ) -> list["TransportationSystem"]: - ''' + """ Gets all systems. Returns a list of TransportationSystem. sortMode: Unknown appVersion: <2: Error 2: Valid - ''' + """ # Initialize & Send Request @@ -418,8 +427,8 @@ def getSystems( # Handle Request Error - if(systems == None): - return([]) + if systems is None: + return [] allSystems = [] @@ -453,14 +462,14 @@ def getSystems( )) - return(allSystems) + return allSystems def getSystemFromID( id, appVersion = 2, sortMode = 1, -) -> TransportationSystem: +) -> Optional[TransportationSystem]: # Check Input Type assert type(id) == int, "`id` must be of type int" @@ -532,6 +541,7 @@ def __init__( self.distance = distance self.latitude = latitude self.longitude = longitude + self.timezone = timezone self.serviceTime = serviceTime self.serviceTimeShort = serviceTimeShort self.systemId = systemId @@ -552,8 +562,15 @@ def getStops(self): self.groupId in list(stop.routesAndPositions.keys()): stopsForRoute.append(stop) - return(stopsForRoute) + return stopsForRoute + def getStopById(self, stopId): + stopsForRoute = self.getStops() + + for stop in stopsForRoute: + if stop.id == stopId: + return stop + return None ### Stops ### @@ -683,7 +700,8 @@ def __init__( self.routeName = routeName self.color = color self.created = created - self.longitude = latitude + self.latitude = latitude + self.longitude = longitude self.speed = speed self.paxLoad = paxLoad self.outOfService = outOfService @@ -705,7 +723,7 @@ def launchWS(): wsapp = websocket.WebSocketApp( uri, on_open = subscribeWS, - #on_message = ..., + on_message = on_message, on_error = handleWsError, on_close = handleWsClose ) @@ -722,19 +740,26 @@ def handleWsError(wsapp, error): def handleWsClose(wsapp, close_status_code, close_msg): wsapp.close() +def on_message(wsapp, message): + # print(message) + message = json.loads(message) + print(f'{message["routeBlock"]}({message["busId"]}) stopping {getSystemFromID(4006).getStopById(message["stopId"]).__dict__["name"]}. Lat: {message["latitude"]}, Long: {message["longitude"]} at {message["speed"]} speed') def subscribeWS( - wsapp, - userId + wsapp ): - + #comment out field to see all options subscriptionMsg = { "subscribe":"location", - "userId":[userId], + "userId":["4006"], "field":[ "busId", + "routeStopId", + "routeBlock", + "stopId", "latitude", "longitude", + "speed", "course", "paxLoad", "more" From 9f3d361127ac468d02b9ab9f9e3b64c735d41a9d Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Sun, 29 Sep 2024 01:08:21 -0400 Subject: [PATCH 04/24] Simple caching system with refresh() method for systems and routes. Vehicle getNextStop() and getEtas() use 3rd party API to get stops and times. Stop getNextVehicle() and getEtas() gets vehicles and times. --- passiogo/__init__.py | 154 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 138 insertions(+), 16 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index 02eb5ae..d109209 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -15,10 +15,12 @@ def toIntInclNone(toInt): return int(toInt) -def sendApiRequest(url, body): - +def sendApiRequest(url, body = None): # Send Request - response = requests.post(url, json = body) + if body is None: + response = requests.get(url) + else: + response = requests.post(url, json = body) try: # Handle JSON Response @@ -75,6 +77,10 @@ def __init__( self.goSupportEmail = goSupportEmail self.goSharedCode = goSharedCode self.goAuthenticationType = goAuthenticationType + self.routes = [] + self.stops = [] + self.alerts = [] + self.vehicles = [] self.checkTypes() @@ -137,7 +143,9 @@ def getRoutes( >=2: Returns all routes for given system in addition to unrelated routes. Exact methodology unsure. """ - + if self.routes: + return self.routes + # Initialize & Send Request url = BASE_URL+f"/mapGetData.php?getRoutes={appVersion}" body = { @@ -186,8 +194,19 @@ def getRoutes( systemId = int(route["userId"]), system = self )) - + + self.routes = allRoutes return allRoutes + + + def getRouteById(self, routeId): + routes = self.getRoutes() + for route in routes: + if int(route.myid) == int(routeId): + return route + + return None + def getStops( self, @@ -205,7 +224,9 @@ def getStops( >=2: Returns unrelated stops as well """ - + if self.stops: + return self.stops + # Initialize & Send Request url = BASE_URL+"/mapGetData.php?getStops="+str(appVersion) body = { @@ -262,6 +283,7 @@ def getStops( allStops.append(Stop( id = stop["id"], + routeId = stop["routeId"], routesAndPositions = routesAndPositions, systemId = None if stop["userId"] is None else int(stop["userId"]), name = stop["name"], @@ -269,8 +291,10 @@ def getStops( longitude = stop["longitude"], radius = stop["radius"], system = self, + route = self.getRouteById(stop["routeId"]) )) - + + self.stops = allStops return allStops def getStopById(self, stopId): @@ -280,6 +304,7 @@ def getStopById(self, stopId): return stop return None + def getSystemAlerts( self, @@ -295,7 +320,9 @@ def getSystemAlerts( 0: Error >=1: Valid """ - + + if self.alerts: + return self.alerts # Initialize & Send Request url = BASE_URL+f"/goServices.php?getAlertMessages={appVersion}" @@ -344,7 +371,8 @@ def getSystemAlerts( fromOk = errorMsg["fromOk"], toOk = errorMsg["toOk"], )) - + + self.alerts = allAlerts return allAlerts def getVehicles( @@ -359,7 +387,9 @@ def getVehicles( 0: Error >=1: Valid """ - + + if self.vehicles: + return self.vehicles # Initialize & Send Request url = BASE_URL+"/mapGetData.php?getBuses="+str(appVersion) @@ -403,9 +433,25 @@ def getVehicles( more = vehicle["more"], tripId = vehicle["tripId"], )) - + + self.vehicles = allVehicles return allVehicles + def getVehicleById(self, vehicleId): + vehicles = self.getVehicles() + for vehicle in vehicles: + if int(vehicle.id) == vehicleId: + return vehicle + + return None + + def refresh(self): + self.routes, self.stops, self.alerts, self.vehicles = [], [], [], [] + self.routes = self.getRoutes() + self.stops = self.getStops() + self.alerts = self.getSystemAlerts() + self.vehicles = self.getVehicles() + def getSystems( appVersion = 2, @@ -546,12 +592,18 @@ def __init__( self.serviceTimeShort = serviceTimeShort self.systemId = systemId self.system = system + self.stops = [] + self.vehicles = [] def getStops(self): """ Gets the list of stops for this route and stores it as an argument """ + + if self.stops: + return self.stops + stopsForRoute = [] allStops = self.system.getStops() @@ -561,9 +613,21 @@ def getStops(self): self.id in list(stop.routesAndPositions.keys()) or \ self.groupId in list(stop.routesAndPositions.keys()): stopsForRoute.append(stop) - + + self.stops = stopsForRoute return stopsForRoute + + def getVehicles(self): + if self.vehicles: + return self.vehicles + + vehiclesForSystem = self.system.getVehicles() + vehiclesForRoute = [vehicle for vehicle in vehiclesForSystem if int(vehicle.__dict__["routeId"]) == self.id] + self.vehicles = vehiclesForRoute + return vehiclesForRoute + + def getStopById(self, stopId): stopsForRoute = self.getStops() @@ -572,6 +636,20 @@ def getStopById(self, stopId): return stop return None + def getVehicleById(self, vehicleId): + vehiclesForSystem = self.getVehicles() + for vehicle in vehiclesForSystem: + if vehicle.__dict__["id"] == vehicleId: + return vehicle + return None + + def refresh(self): + self.stops, self.vehicles = [], [] + self.stops = self.getStops() + self.vehicles = self.getVehicles() + + + ### Stops ### class Stop: @@ -579,6 +657,7 @@ class Stop: def __init__( self, id: str, + routeId: str = None, routesAndPositions: dict = None, systemId: int = None, name: str = None, @@ -586,11 +665,13 @@ def __init__( longitude: float = None, radius: int = None, system : TransportationSystem = None, + route: Route = None ): if routesAndPositions is None: routesAndPositions = {} self.id = id + self.routeId = routeId self.routesAndPositions = routesAndPositions self.systemId = systemId self.name = name @@ -598,6 +679,24 @@ def __init__( self.longitude = longitude self.radius = radius self.system = system + self.route = route + + def getNextVehicle(self): + etaUrl = f'https://store.transitstat.us/passio_go/{self.system.__dict__["username"]}' + data = sendApiRequest(etaUrl) + etas = self.getEtas() + return sorted(etas, key=lambda x : x[1])[0] + + def getEtas(self): + etaUrl = f'https://store.transitstat.us/passio_go/{self.system.__dict__["username"]}' + data = sendApiRequest(etaUrl) + trains = [] + for vehicle in self.system.getVehicles(): + if vehicle.__dict__["name"] in data["trains"]: + for prediction in data["trains"][vehicle.__dict__["name"]]["predictions"]: + if prediction["stationID"] == str(self.id): + trains.append((self.system.getVehicleById(int(vehicle.__dict__["id"])), prediction["actualETA"])) + return trains ### System Alerts ### @@ -708,6 +807,27 @@ def __init__( self.more = more self.tripId = tripId + def getNextStop(self): + etaUrl = f'https://store.transitstat.us/passio_go/{self.system.__dict__["username"]}' + data = sendApiRequest(etaUrl) + if str(self.name) in data["trains"]: + info = data["trains"][str(self.name)] + else: + return None + return (self.system.getStopById(int(info["predictions"][0]["stationID"])), info["predictions"][0]["actualETA"]) + + def getEtas(self): + etaUrl = f'https://store.transitstat.us/passio_go/{self.system.__dict__["username"]}' + data = sendApiRequest(etaUrl) + if str(self.name) in data["trains"]: + info = data["trains"][str(self.name)] + else: + return None + etas = [] + for prediction in info["predictions"]: + etas.append(((self.system.getStopById(int(prediction["stationID"]))), prediction["actualETA"])) + return etas + @@ -741,17 +861,19 @@ def handleWsClose(wsapp, close_status_code, close_msg): wsapp.close() def on_message(wsapp, message): - # print(message) message = json.loads(message) - print(f'{message["routeBlock"]}({message["busId"]}) stopping {getSystemFromID(4006).getStopById(message["stopId"]).__dict__["name"]}. Lat: {message["latitude"]}, Long: {message["longitude"]} at {message["speed"]} speed') + print(message) + #Pretty output + # print(f'{message["routeBlock"]}({message["busId"]}) stopping {getSystemFromID(...).getStopById(message["stopId"]).__dict__["name"]}. Lat: {message["latitude"]}, Long: {message["longitude"]} at {message["speed"]} speed') def subscribeWS( - wsapp + wsapp, + userId ): #comment out field to see all options subscriptionMsg = { "subscribe":"location", - "userId":["4006"], + "userId":[userId], "field":[ "busId", "routeStopId", From 40ec984d688523dbbe8d07f729ec6037c3f4a1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20Th=C3=BCler?= <22741115+athuler@users.noreply.github.com> Date: Sun, 29 Sep 2024 07:05:30 -0500 Subject: [PATCH 05/24] Update CHANGELOG.md --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d32d7..dea9a47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,18 @@ ### Removed +## 0.2.2 (2024-09-10) + +### Added + + +### Changed + +- Fixed key error while fetching systems. (#22) + +### Removed + + ## 0.2.1 (2024-08-18) ### Added From 08db43de909c8265dfb99298e71623a8be2ad0a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20Th=C3=BCler?= <22741115+athuler@users.noreply.github.com> Date: Sun, 29 Sep 2024 07:14:54 -0500 Subject: [PATCH 06/24] Run Tests on Pull Requests --- .github/workflows/testing.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 77ae680..d237239 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -3,7 +3,7 @@ name: Testing Package -on: push +on: [push, pull_request] permissions: @@ -30,4 +30,4 @@ jobs: - name: Test with pytest run: | pip install pytest pytest-cov - pytest \ No newline at end of file + pytest From 3bcd830a81ab83d2034df5dca3bfab74e8e8f1a6 Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Sun, 29 Sep 2024 11:15:26 -0400 Subject: [PATCH 07/24] Removed use of 3rd party API --- passiogo/__init__.py | 73 +++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index d109209..bf76600 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -1,9 +1,14 @@ import json -from typing import Optional - import requests import websocket +import random +from datetime import timedelta, datetime, timezone +from pytimeparse.timeparse import timeparse +from typing import Optional + + + BASE_URL = "https://passiogo.com" @@ -14,7 +19,21 @@ def toIntInclNone(toInt): return toInt return int(toInt) - +#Needs refining based on different formats +#Have to wait until I find a bus with eta >= 1 day +def convertToUnix(eta): + if eta == '--': + return 0 + elif eta == "arrived" or eta == "arriving": + return datetime.now(timezone.utc).timestamp() + elif "less than" in eta: + return (datetime.now(timezone.utc) + timedelta(seconds=30)).timestamp() #Approximation, may be a better way + elif "-" in eta: + eta=eta.split("-")[1] + "".join(eta.split()) + seconds = timeparse(eta) + return (datetime.now(timezone.utc) + timedelta(seconds=seconds)).timestamp() + def sendApiRequest(url, body = None): # Send Request if body is None: @@ -682,20 +701,22 @@ def __init__( self.route = route def getNextVehicle(self): - etaUrl = f'https://store.transitstat.us/passio_go/{self.system.__dict__["username"]}' - data = sendApiRequest(etaUrl) etas = self.getEtas() return sorted(etas, key=lambda x : x[1])[0] def getEtas(self): - etaUrl = f'https://store.transitstat.us/passio_go/{self.system.__dict__["username"]}' - data = sendApiRequest(etaUrl) + etaUrl = f'https://passiogo.com/mapGetData.php?eta=3&deviceId={random.randint(10000000,99999999)}&stopIds={self.id}' + data = sendApiRequest(etaUrl)["ETAs"] + if str(self.id) not in data: + return None trains = [] - for vehicle in self.system.getVehicles(): - if vehicle.__dict__["name"] in data["trains"]: - for prediction in data["trains"][vehicle.__dict__["name"]]["predictions"]: - if prediction["stationID"] == str(self.id): - trains.append((self.system.getVehicleById(int(vehicle.__dict__["id"])), prediction["actualETA"])) + for vehicle in data[str(self.id)]: + eta = vehicle["eta"] + if eta == 'arrived': + eta = datetime.now(timezone.utc) + else: + eta = convertToUnix(eta) + trains.append((self.system.getVehicleById(int(vehicle["busId"])), eta)) return trains @@ -808,29 +829,25 @@ def __init__( self.tripId = tripId def getNextStop(self): - etaUrl = f'https://store.transitstat.us/passio_go/{self.system.__dict__["username"]}' - data = sendApiRequest(etaUrl) - if str(self.name) in data["trains"]: - info = data["trains"][str(self.name)] - else: - return None - return (self.system.getStopById(int(info["predictions"][0]["stationID"])), info["predictions"][0]["actualETA"]) + sortedEta = sorted(self.getEtas(), key = lambda x:x[1]) + if sortedEta: + return sortedEta[0] + return None + def getEtas(self): - etaUrl = f'https://store.transitstat.us/passio_go/{self.system.__dict__["username"]}' - data = sendApiRequest(etaUrl) - if str(self.name) in data["trains"]: - info = data["trains"][str(self.name)] - else: - return None etas = [] - for prediction in info["predictions"]: - etas.append(((self.system.getStopById(int(prediction["stationID"]))), prediction["actualETA"])) + for stop in self.system.getRouteById(self.routeId).getStops(): + etaUrl = f'https://passiogo.com/mapGetData.php?eta=3&deviceId={random.randint(10000000, 99999999)}&stopIds={stop.id}' + data = sendApiRequest(etaUrl)["ETAs"] + for vehicle in data[str(stop.id)]: + if vehicle["busId"] == self.id: + print(vehicle) + etas.append((stop, convertToUnix(vehicle["eta"]))) return etas - ### Live Timings ### ## Not Yet Supported! ## From 9204f024ac4b56412c2ddcdcd356a7731da68f34 Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Sun, 29 Sep 2024 11:25:37 -0400 Subject: [PATCH 08/24] Rudimentary caching system, needs time based implementation if user does not wish to manually refresh systems. --- passiogo/__init__.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index e80855b..433fc4f 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -74,6 +74,10 @@ def __init__( self.goSupportEmail = goSupportEmail self.goSharedCode = goSharedCode self.goAuthenticationType = goAuthenticationType + self.routes = [] + self.stops = [] + self.vehicles = [] + self.alerts = [] self.checkTypes() @@ -136,7 +140,9 @@ def getRoutes( >=2: Returns all routes for given system in addition to unrelated routes. Exact methodology unsure. """ - + if self.routes: + return self.routes + # Initialize & Send Request url = BASE_URL+f"/mapGetData.php?getRoutes={appVersion}" body = { @@ -203,7 +209,9 @@ def getStops( 1: Returns all stops for the given system >=2: Returns unrelated stops as well """ - + + if self.stops: + return self.stops # Initialize & Send Request url = BASE_URL+"/mapGetData.php?getStops="+str(appVersion) @@ -286,7 +294,9 @@ def getSystemAlerts( 0: Error >=1: Valid """ - + + if self.alerts: + return self.alerts # Initialize & Send Request url = BASE_URL+f"/goServices.php?getAlertMessages={appVersion}" @@ -351,7 +361,9 @@ def getVehicles( >=1: Valid """ - + if self.vehicles: + return self.vehicles + # Initialize & Send Request url = BASE_URL+"/mapGetData.php?getBuses="+str(appVersion) body = { @@ -397,6 +409,13 @@ def getVehicles( return(allVehicles) + def refresh(self): + self.routes, self.stops, self.vehicles, self.alerts = [], [], [], [] + self.routes = self.getRoutes() + self.stops = self.getStops() + self.vehicles = self.getVehicles() + self.alerts = self.getSystemAlerts() + def getSystems( appVersion = 2, @@ -536,12 +555,17 @@ def __init__( self.serviceTimeShort = serviceTimeShort self.systemId = systemId self.system = system + self.stops = [] def getStops(self): """ Gets the list of stops for this route and stores it as an argument """ + + if self.stops: + return self.stops + stopsForRoute = [] allStops = self.system.getStops() @@ -554,6 +578,10 @@ def getStops(self): return(stopsForRoute) + def refresh(self): + self.stops = [] + self.stops = self.getStops() + ### Stops ### From 19a7a06523137a2e9e6bc252caed3e567aba0c39 Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Sun, 29 Sep 2024 11:27:55 -0400 Subject: [PATCH 09/24] Moved caching to separate branch --- passiogo/__init__.py | 42 ++---------------------------------------- 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index bf76600..4dabcae 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -96,10 +96,6 @@ def __init__( self.goSupportEmail = goSupportEmail self.goSharedCode = goSharedCode self.goAuthenticationType = goAuthenticationType - self.routes = [] - self.stops = [] - self.alerts = [] - self.vehicles = [] self.checkTypes() @@ -161,9 +157,7 @@ def getRoutes( 0: Not Valid, Gives Error >=2: Returns all routes for given system in addition to unrelated routes. Exact methodology unsure. """ - - if self.routes: - return self.routes + # Initialize & Send Request url = BASE_URL+f"/mapGetData.php?getRoutes={appVersion}" @@ -214,7 +208,6 @@ def getRoutes( system = self )) - self.routes = allRoutes return allRoutes @@ -242,9 +235,7 @@ def getStops( 1: Returns all stops for the given system >=2: Returns unrelated stops as well """ - - if self.stops: - return self.stops + # Initialize & Send Request url = BASE_URL+"/mapGetData.php?getStops="+str(appVersion) @@ -313,7 +304,6 @@ def getStops( route = self.getRouteById(stop["routeId"]) )) - self.stops = allStops return allStops def getStopById(self, stopId): @@ -340,8 +330,6 @@ def getSystemAlerts( >=1: Valid """ - if self.alerts: - return self.alerts # Initialize & Send Request url = BASE_URL+f"/goServices.php?getAlertMessages={appVersion}" @@ -391,7 +379,6 @@ def getSystemAlerts( toOk = errorMsg["toOk"], )) - self.alerts = allAlerts return allAlerts def getVehicles( @@ -407,8 +394,6 @@ def getVehicles( >=1: Valid """ - if self.vehicles: - return self.vehicles # Initialize & Send Request url = BASE_URL+"/mapGetData.php?getBuses="+str(appVersion) @@ -453,7 +438,6 @@ def getVehicles( tripId = vehicle["tripId"], )) - self.vehicles = allVehicles return allVehicles def getVehicleById(self, vehicleId): @@ -464,13 +448,6 @@ def getVehicleById(self, vehicleId): return None - def refresh(self): - self.routes, self.stops, self.alerts, self.vehicles = [], [], [], [] - self.routes = self.getRoutes() - self.stops = self.getStops() - self.alerts = self.getSystemAlerts() - self.vehicles = self.getVehicles() - def getSystems( appVersion = 2, @@ -611,8 +588,6 @@ def __init__( self.serviceTimeShort = serviceTimeShort self.systemId = systemId self.system = system - self.stops = [] - self.vehicles = [] def getStops(self): @@ -620,9 +595,6 @@ def getStops(self): Gets the list of stops for this route and stores it as an argument """ - if self.stops: - return self.stops - stopsForRoute = [] allStops = self.system.getStops() @@ -633,17 +605,13 @@ def getStops(self): self.groupId in list(stop.routesAndPositions.keys()): stopsForRoute.append(stop) - self.stops = stopsForRoute return stopsForRoute def getVehicles(self): - if self.vehicles: - return self.vehicles vehiclesForSystem = self.system.getVehicles() vehiclesForRoute = [vehicle for vehicle in vehiclesForSystem if int(vehicle.__dict__["routeId"]) == self.id] - self.vehicles = vehiclesForRoute return vehiclesForRoute @@ -662,12 +630,6 @@ def getVehicleById(self, vehicleId): return vehicle return None - def refresh(self): - self.stops, self.vehicles = [], [] - self.stops = self.getStops() - self.vehicles = self.getVehicles() - - ### Stops ### From 7f6b35d59190f530d80ca242a70fe77436f9d2ce Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Sun, 29 Sep 2024 11:29:37 -0400 Subject: [PATCH 10/24] Fixed get functions not caching their results --- passiogo/__init__.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index 433fc4f..ab51069 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -191,7 +191,8 @@ def getRoutes( systemId = int(route["userId"]), system = self )) - + + self.routes = allRoutes return(allRoutes) def getStops( @@ -277,7 +278,8 @@ def getStops( radius = stop["radius"], system = self, )) - + + self.stops = allStops return(allStops) def getSystemAlerts( @@ -345,7 +347,8 @@ def getSystemAlerts( fromOk = errorMsg["fromOk"], toOk = errorMsg["toOk"], )) - + + self.alerts = allAlerts return(allAlerts) def getVehicles( @@ -406,7 +409,8 @@ def getVehicles( more = vehicle["more"], tripId = vehicle["tripId"], )) - + + self.vehicles = allVehicles return(allVehicles) def refresh(self): @@ -575,7 +579,8 @@ def getStops(self): self.id in list(stop.routesAndPositions.keys()) or \ self.groupId in list(stop.routesAndPositions.keys()): stopsForRoute.append(stop) - + + self.stops = stopsForRoute return(stopsForRoute) def refresh(self): From 3c92ececeaadfae3637b35a1553ca15a3fb97877 Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Sun, 29 Sep 2024 14:52:33 -0400 Subject: [PATCH 11/24] Documentation, use of secondsSpent, remove Vehicle.getEtas() --- passiogo/__init__.py | 143 ++++++++++++++++++------------------------- 1 file changed, 61 insertions(+), 82 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index 3819532..19e4408 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -4,10 +4,7 @@ import random from datetime import timedelta, datetime, timezone -from pytimeparse.timeparse import timeparse -from typing import Optional - - +from typing import Optional, List, Tuple BASE_URL = "https://passiogo.com" @@ -19,20 +16,8 @@ def toIntInclNone(toInt): return toInt return int(toInt) -#Needs refining based on different formats -#Have to wait until I find a bus with eta >= 1 day -def convertToUnix(eta): - if eta == '--': - return 0 - elif eta == "arrived" or eta == "arriving": - return datetime.now(timezone.utc).timestamp() - elif "less than" in eta: - return (datetime.now(timezone.utc) + timedelta(seconds=30)).timestamp() #Approximation, may be a better way - elif "-" in eta: - eta=eta.split("-")[1] - "".join(eta.split()) - seconds = timeparse(eta) - return (datetime.now(timezone.utc) + timedelta(seconds=seconds)).timestamp() +def convertToUnixEta(eta): + return (datetime.now(timezone.utc) + timedelta(seconds=eta)).timestamp() def sendApiRequest(url, body = None): # Send Request @@ -210,22 +195,13 @@ def getRoutes( return allRoutes - - def getRouteById(self, routeId): - routes = self.getRoutes() - for route in routes: - if int(route.myid) == int(routeId): - return route - - return None - def getRouteById( self, routeId: str, appVersion: int = 1, amount: int = 1 - ) -> "Route": + ) -> Optional["Route"]: """ Returns a Route object corresponding to the provided ID. """ @@ -312,7 +288,6 @@ def getStops( allStops.append(Stop( id = stop["id"], - routeId = stop["routeId"], routesAndPositions = routesAndPositions, systemId = None if stop["userId"] is None else int(stop["userId"]), name = stop["name"], @@ -320,19 +295,10 @@ def getStops( longitude = stop["longitude"], radius = stop["radius"], system = self, - route = self.getRouteById(stop["routeId"]) )) return allStops - def getStopById(self, stopId): - stops = self.getStops() - for stop in stops: - if int(stop.id) == stopId: - return stop - - return None - def getStopById( self, @@ -340,7 +306,7 @@ def getStopById( appVersion = 2, sA = 1, raw = False, - ) -> "Stop": + ) -> Optional["Stop"]: """ Returns the Stop object corresponding to the passed ID. """ @@ -480,12 +446,19 @@ def getVehicles( return allVehicles - def getVehicleById(self, vehicleId): - vehicles = self.getVehicles() + def getVehicleById( + self, + vehicleId, + appVersion: int = 1 + ) -> Optional["Vehicle"]: + """ + Returns a Vehicle object corresponding to the provided ID. + """ + + vehicles = self.getVehicles(appVersion = appVersion) for vehicle in vehicles: if int(vehicle.id) == vehicleId: return vehicle - return None @@ -648,25 +621,44 @@ def getStops(self): return stopsForRoute - def getVehicles(self): + def getVehicles( + self, + appVersion: int = 1 + ) -> List["Vehicle"]: + """ + Gets all vehicles following this route + """ - vehiclesForSystem = self.system.getVehicles() - vehiclesForRoute = [vehicle for vehicle in vehiclesForSystem if int(vehicle.__dict__["routeId"]) == self.id] + vehiclesForSystem = self.system.getVehicles(appVersion = appVersion) + vehiclesForRoute = [vehicle for vehicle in vehiclesForSystem if str(vehicle.routeId) == self.myid] return vehiclesForRoute - def getStopById(self, stopId): - stopsForRoute = self.getStops() + def getStopById( + self, + stopId: str + ) -> Optional["Stop"]: + """ + Returns a Stop object corresponding to the provided ID. + """ + stopsForRoute = self.getStops() for stop in stopsForRoute: - if stop.id == stopId: + if str(stop.id) == str(stopId): return stop return None - def getVehicleById(self, vehicleId): + def getVehicleById( + self, + vehicleId: str, + ) -> Optional["Vehicle"]: + """ + Returns a Vehicle object corresponding to the provided ID. + """ + vehiclesForSystem = self.getVehicles() for vehicle in vehiclesForSystem: - if vehicle.__dict__["id"] == vehicleId: + if str(vehicle.id) == str(vehicleId): return vehicle return None @@ -678,7 +670,6 @@ class Stop: def __init__( self, id: str, - routeId: str = None, routesAndPositions: dict = None, systemId: int = None, name: str = None, @@ -686,13 +677,11 @@ def __init__( longitude: float = None, radius: int = None, system : TransportationSystem = None, - route: Route = None ): if routesAndPositions is None: routesAndPositions = {} self.id = id - self.routeId = routeId self.routesAndPositions = routesAndPositions self.systemId = systemId self.name = name @@ -700,25 +689,34 @@ def __init__( self.longitude = longitude self.radius = radius self.system = system - self.route = route - def getNextVehicle(self): + def getNextVehicle( + self + ) -> Tuple[float, Optional["Vehicle"]]: + """ + Gets the next vehicle that will arrive to this stop + """ + etas = self.getEtas() return sorted(etas, key=lambda x : x[1])[0] - def getEtas(self): - etaUrl = f'https://passiogo.com/mapGetData.php?eta=3&deviceId={random.randint(10000000,99999999)}&stopIds={self.id}' + def getEtas( + self + ) -> List[Tuple[float, Optional["Vehicle"]]]: + """ + Returns a list of all vehicles that stop at this stop, + along with the Unix Timestamp of their arrival in the form: + (unixTimestamp, ) + """ + + etaUrl = f'{BASE_URL}/mapGetData.php?eta=3&deviceId={random.randint(10000000,99999999)}&stopIds={self.id}' data = sendApiRequest(etaUrl)["ETAs"] if str(self.id) not in data: return None trains = [] for vehicle in data[str(self.id)]: - eta = vehicle["eta"] - if eta == 'arrived': - eta = datetime.now(timezone.utc) - else: - eta = convertToUnix(eta) - trains.append((self.system.getVehicleById(int(vehicle["busId"])), eta)) + eta = convertToUnixEta(vehicle["secondsSpent"]) + trains.append((eta, self.system.getVehicleById(int(vehicle["busId"])))) return trains @@ -830,25 +828,6 @@ def __init__( self.more = more self.tripId = tripId - def getNextStop(self): - sortedEta = sorted(self.getEtas(), key = lambda x:x[1]) - if sortedEta: - return sortedEta[0] - return None - - - def getEtas(self): - etas = [] - for stop in self.system.getRouteById(self.routeId).getStops(): - etaUrl = f'https://passiogo.com/mapGetData.php?eta=3&deviceId={random.randint(10000000, 99999999)}&stopIds={stop.id}' - data = sendApiRequest(etaUrl)["ETAs"] - for vehicle in data[str(stop.id)]: - if vehicle["busId"] == self.id: - print(vehicle) - etas.append((stop, convertToUnix(vehicle["eta"]))) - return etas - - ### Live Timings ### ## Not Yet Supported! ## From ab489316eb4ce6b824ff92d47a3945dd590e4f88 Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Sun, 29 Sep 2024 15:00:52 -0400 Subject: [PATCH 12/24] Fixed Stop.getNextVehicle() attempting to sort None --- passiogo/__init__.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index 19e4408..f6fec74 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -692,17 +692,26 @@ def __init__( def getNextVehicle( self - ) -> Tuple[float, Optional["Vehicle"]]: + ) -> Optional[ + Tuple[float, Optional["Vehicle"]] + ]: """ Gets the next vehicle that will arrive to this stop """ etas = self.getEtas() - return sorted(etas, key=lambda x : x[1])[0] + if not etas: + return None + + return sorted(etas, key=lambda x : x[0])[0] def getEtas( self - ) -> List[Tuple[float, Optional["Vehicle"]]]: + ) -> Optional[ + List[ + Tuple[float, Optional["Vehicle"]] + ] + ]: """ Returns a list of all vehicles that stop at this stop, along with the Unix Timestamp of their arrival in the form: @@ -711,13 +720,13 @@ def getEtas( etaUrl = f'{BASE_URL}/mapGetData.php?eta=3&deviceId={random.randint(10000000,99999999)}&stopIds={self.id}' data = sendApiRequest(etaUrl)["ETAs"] + vehicles = [] if str(self.id) not in data: - return None - trains = [] + return vehicles for vehicle in data[str(self.id)]: eta = convertToUnixEta(vehicle["secondsSpent"]) - trains.append((eta, self.system.getVehicleById(int(vehicle["busId"])))) - return trains + vehicles.append((eta, self.system.getVehicleById(int(vehicle["busId"])))) + return vehicles ### System Alerts ### From 67efde01aed074668c40f3f6c4ec7bce0ae44155 Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Sun, 29 Sep 2024 18:39:27 -0400 Subject: [PATCH 13/24] Implemented casting so objects are created with correct types. --- passiogo/__init__.py | 62 ++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index e80855b..fc09ac3 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -8,11 +8,21 @@ ### Helper Functions ### def toIntInclNone(toInt): + """ + Cast to int, returning None if input is None + """ if toInt == None: return toInt return(int(toInt)) - +def toFloatInclNone(toFloat): + """ + Cast to float, returning None if input is None + """ + if toFloat is None: + return toFloat + return float(toFloat) + def sendApiRequest(url, body): # Send Request @@ -494,7 +504,7 @@ class Route: def __init__( self, - id: int, + id: str, groupId: int = None, groupColor: str = None, name: str = None, @@ -517,21 +527,22 @@ def __init__( system: TransportationSystem = None, ): self.id = id - self.groupId = groupId + self.groupId = toIntInclNone(groupId) self.groupColor = groupColor self.name = name self.shortName = shortName self.nameOrig = nameOrig self.fullname = fullname - self.myid = myid - self.mapApp = mapApp - self.archive = archive - self.goPrefixRouteName = goPrefixRouteName - self.goShowSchedule = goShowSchedule - self.outdated = outdated + self.myid = toIntInclNone(myid) + self.mapApp = bool(toIntInclNone(mapApp)) + self.archive = bool(toIntInclNone(archive)) + self.goPrefixRouteName = bool(toIntInclNone(goPrefixRouteName)) + self.goShowSchedule = bool(toIntInclNone(goShowSchedule)) + self.outdated = bool(toIntInclNone(outdated)) self.distance = distance - self.latitude = latitude - self.longitude = longitude + self.latitude = toFloatInclNone(latitude) + self.longitude = toFloatInclNone(longitude) + self.timezone = timezone self.serviceTime = serviceTime self.serviceTimeShort = serviceTimeShort self.systemId = systemId @@ -589,7 +600,7 @@ class SystemAlert: def __init__( self, - id: int, + id: str, systemId: int = None, system: TransportationSystem = None, routeId: int = None, @@ -620,34 +631,34 @@ def __init__( toOk: bool = None, ): self.id = id - self.systemId = systemId + self.systemId = toIntInclNone(systemId) self.system = system self.routeId = routeId self.name = name self.html = html - self.archive = archive - self.important = important + self.archive = bool(toIntInclNone(archive)) + self.important = bool(toIntInclNone(important)) self.dateTimeCreated = dateTimeCreated self.dateTimeFrom = dateTimeFrom self.dateTimeTo = dateTimeTo - self.asPush = asPush - self.gtfs = gtfs - self.gtfsAlertCauseId = gtfsAlertCauseId - self.gtfsAlertEffectId = gtfsAlertEffectId + self.asPush = bool(toIntInclNone(asPush)) + self.gtfs = bool(toIntInclNone(gtfs)) + self.gtfsAlertCauseId = bool(toIntInclNone(gtfsAlertCauseId)) + self.gtfsAlertEffectId = bool(toIntInclNone(gtfsAlertEffectId)) self.gtfsAlertUrl = gtfsAlertUrl self.gtfsAlertHeaderText = gtfsAlertHeaderText self.gtfsAlertDescriptionText = gtfsAlertDescriptionText self.routeGroupId = routeGroupId self.createdUtc = createdUtc - self.authorId = authorId + self.authorId = toIntInclNone(authorId) self.author = author self.updated = updated - self.updateAuthorId = updateAuthorId + self.updateAuthorId = toIntInclNone(updateAuthorId) self.updateAuthor = updateAuthor self.createdF = createdF self.fromF = fromF - self.fromOk = fromOk - self.toOk = toOk + self.fromOk = bool(toIntInclNone(fromOk)) + self.toOk = bool(toIntInclNone(toOk)) @@ -683,10 +694,11 @@ def __init__( self.routeName = routeName self.color = color self.created = created - self.longitude = latitude + self.latitude = toFloatInclNone(latitude) + self.longitude = toFloatInclNone(longitude) self.speed = speed self.paxLoad = paxLoad - self.outOfService = outOfService + self.outOfService = bool(outOfService) self.more = more self.tripId = tripId From ca66a2d41cdab28613cb3b77a0cca61cf663330d Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Sun, 29 Sep 2024 18:39:27 -0400 Subject: [PATCH 14/24] Implemented casting so objects are created with correct types. --- passiogo/__init__.py | 60 ++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index e80855b..92c3b74 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -8,11 +8,21 @@ ### Helper Functions ### def toIntInclNone(toInt): + """ + Cast to int, returning None if input is None + """ if toInt == None: return toInt return(int(toInt)) - +def toFloatInclNone(toFloat): + """ + Cast to float, returning None if input is None + """ + if toFloat is None: + return toFloat + return float(toFloat) + def sendApiRequest(url, body): # Send Request @@ -494,7 +504,7 @@ class Route: def __init__( self, - id: int, + id: str, groupId: int = None, groupColor: str = None, name: str = None, @@ -517,21 +527,22 @@ def __init__( system: TransportationSystem = None, ): self.id = id - self.groupId = groupId + self.groupId = toIntInclNone(groupId) self.groupColor = groupColor self.name = name self.shortName = shortName self.nameOrig = nameOrig self.fullname = fullname - self.myid = myid - self.mapApp = mapApp - self.archive = archive - self.goPrefixRouteName = goPrefixRouteName - self.goShowSchedule = goShowSchedule - self.outdated = outdated + self.myid = toIntInclNone(myid) + self.mapApp = bool(toIntInclNone(mapApp)) + self.archive = bool(toIntInclNone(archive)) + self.goPrefixRouteName = bool(toIntInclNone(goPrefixRouteName)) + self.goShowSchedule = bool(toIntInclNone(goShowSchedule)) + self.outdated = bool(toIntInclNone(outdated)) self.distance = distance - self.latitude = latitude - self.longitude = longitude + self.latitude = toFloatInclNone(latitude) + self.longitude = toFloatInclNone(longitude) + self.timezone = timezone self.serviceTime = serviceTime self.serviceTimeShort = serviceTimeShort self.systemId = systemId @@ -620,34 +631,34 @@ def __init__( toOk: bool = None, ): self.id = id - self.systemId = systemId + self.systemId = toIntInclNone(systemId) self.system = system self.routeId = routeId self.name = name self.html = html - self.archive = archive - self.important = important + self.archive = bool(toIntInclNone(archive)) + self.important = bool(toIntInclNone(important)) self.dateTimeCreated = dateTimeCreated self.dateTimeFrom = dateTimeFrom self.dateTimeTo = dateTimeTo - self.asPush = asPush - self.gtfs = gtfs - self.gtfsAlertCauseId = gtfsAlertCauseId - self.gtfsAlertEffectId = gtfsAlertEffectId + self.asPush = bool(toIntInclNone(asPush)) + self.gtfs = bool(toIntInclNone(gtfs)) + self.gtfsAlertCauseId = bool(toIntInclNone(gtfsAlertCauseId)) + self.gtfsAlertEffectId = bool(toIntInclNone(gtfsAlertEffectId)) self.gtfsAlertUrl = gtfsAlertUrl self.gtfsAlertHeaderText = gtfsAlertHeaderText self.gtfsAlertDescriptionText = gtfsAlertDescriptionText self.routeGroupId = routeGroupId self.createdUtc = createdUtc - self.authorId = authorId + self.authorId = toIntInclNone(authorId) self.author = author self.updated = updated - self.updateAuthorId = updateAuthorId + self.updateAuthorId = toIntInclNone(updateAuthorId) self.updateAuthor = updateAuthor self.createdF = createdF self.fromF = fromF - self.fromOk = fromOk - self.toOk = toOk + self.fromOk = bool(toIntInclNone(fromOk)) + self.toOk = bool(toIntInclNone(toOk)) @@ -683,10 +694,11 @@ def __init__( self.routeName = routeName self.color = color self.created = created - self.longitude = latitude + self.latitude = toFloatInclNone(latitude) + self.longitude = toFloatInclNone(longitude) self.speed = speed self.paxLoad = paxLoad - self.outOfService = outOfService + self.outOfService = bool(outOfService) self.more = more self.tripId = tripId From db648c7ae1aabeabd452ec8448a733c43d7971c0 Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Sun, 29 Sep 2024 18:43:09 -0400 Subject: [PATCH 15/24] SystemAlert.id should be int --- passiogo/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index fc09ac3..92c3b74 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -600,7 +600,7 @@ class SystemAlert: def __init__( self, - id: str, + id: int, systemId: int = None, system: TransportationSystem = None, routeId: int = None, From 1902c17486b908845f79f6e6335eb9fef4dac4a1 Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Sun, 29 Sep 2024 18:54:16 -0400 Subject: [PATCH 16/24] Route.id actually should be int --- passiogo/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index 92c3b74..53a9c14 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -504,14 +504,14 @@ class Route: def __init__( self, - id: str, + id: int, groupId: int = None, groupColor: str = None, name: str = None, shortName: str = None, nameOrig: str = None, fullname: str = None, - myid: int = None, + myid: str = None, mapApp: bool = None, archive: bool = None, goPrefixRouteName: bool = None, @@ -533,7 +533,7 @@ def __init__( self.shortName = shortName self.nameOrig = nameOrig self.fullname = fullname - self.myid = toIntInclNone(myid) + self.myid = myid self.mapApp = bool(toIntInclNone(mapApp)) self.archive = bool(toIntInclNone(archive)) self.goPrefixRouteName = bool(toIntInclNone(goPrefixRouteName)) From 214e1e3b63e09c43ce49f61c38c10c07815aa47a Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Sun, 29 Sep 2024 18:56:13 -0400 Subject: [PATCH 17/24] Route.id cast to int --- passiogo/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index 53a9c14..466244b 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -526,7 +526,7 @@ def __init__( systemId: id = None, system: TransportationSystem = None, ): - self.id = id + self.id = toIntInclNone(id) self.groupId = toIntInclNone(groupId) self.groupColor = groupColor self.name = name From 06a9b4f2fde1e2801d8fd3c857cfa9c38548b896 Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Mon, 30 Sep 2024 09:37:37 -0400 Subject: [PATCH 18/24] returnInUTC argument added to eta methods --- passiogo/__init__.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index f6fec74..12179cf 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -691,7 +691,8 @@ def __init__( self.system = system def getNextVehicle( - self + self, + returnInUTC: bool = False, ) -> Optional[ Tuple[float, Optional["Vehicle"]] ]: @@ -699,14 +700,15 @@ def getNextVehicle( Gets the next vehicle that will arrive to this stop """ - etas = self.getEtas() + etas = self.getEtas(returnInUTC = returnInUTC) if not etas: return None - return sorted(etas, key=lambda x : x[0])[0] + return sorted(etas, key = lambda x : x[0])[0] def getEtas( - self + self, + returnInUTC: bool = False ) -> Optional[ List[ Tuple[float, Optional["Vehicle"]] @@ -724,7 +726,10 @@ def getEtas( if str(self.id) not in data: return vehicles for vehicle in data[str(self.id)]: - eta = convertToUnixEta(vehicle["secondsSpent"]) + if returnInUTC: + eta = convertToUnixEta(vehicle["secondsSpent"]) + else: + eta = vehicle["secondsSpent"] vehicles.append((eta, self.system.getVehicleById(int(vehicle["busId"])))) return vehicles From bf4aafd31cfa4062991e9c8caa35007b0e5df83d Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Mon, 30 Sep 2024 22:12:54 -0400 Subject: [PATCH 19/24] Updated docs --- passiogo/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index 12179cf..74b9354 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -716,8 +716,9 @@ def getEtas( ]: """ Returns a list of all vehicles that stop at this stop, - along with the Unix Timestamp of their arrival in the form: - (unixTimestamp, ) + along with the seconds until their arrival in the form: + (seconds, ), or optionally (timestampUTC, ) with the + returnInUtc argument. """ etaUrl = f'{BASE_URL}/mapGetData.php?eta=3&deviceId={random.randint(10000000,99999999)}&stopIds={self.id}' From 31bfc6dcb27b697fd245758a2c27248a9b0563c9 Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Tue, 1 Oct 2024 00:21:41 -0400 Subject: [PATCH 20/24] Stop.getNextVehicle() does not need to sort etas --- passiogo/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index 74b9354..9ed96c8 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -704,7 +704,7 @@ def getNextVehicle( if not etas: return None - return sorted(etas, key = lambda x : x[0])[0] + return min(etas, key = lambda x : x[0]) def getEtas( self, From 38b6c592484955b84692c51f5bdff3efc0a35568 Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Tue, 1 Oct 2024 12:04:10 -0400 Subject: [PATCH 21/24] Removed Route.getStop/VehicleById() --- passiogo/__init__.py | 39 ++++++--------------------------------- 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index 9ed96c8..f09419b 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -634,35 +634,6 @@ def getVehicles( return vehiclesForRoute - def getStopById( - self, - stopId: str - ) -> Optional["Stop"]: - """ - Returns a Stop object corresponding to the provided ID. - """ - - stopsForRoute = self.getStops() - for stop in stopsForRoute: - if str(stop.id) == str(stopId): - return stop - return None - - def getVehicleById( - self, - vehicleId: str, - ) -> Optional["Vehicle"]: - """ - Returns a Vehicle object corresponding to the provided ID. - """ - - vehiclesForSystem = self.getVehicles() - for vehicle in vehiclesForSystem: - if str(vehicle.id) == str(vehicleId): - return vehicle - return None - - ### Stops ### class Stop: @@ -704,6 +675,7 @@ def getNextVehicle( if not etas: return None + # Generally operates in O(1) as etas come sorted by API return min(etas, key = lambda x : x[0]) def getEtas( @@ -727,10 +699,11 @@ def getEtas( if str(self.id) not in data: return vehicles for vehicle in data[str(self.id)]: - if returnInUTC: - eta = convertToUnixEta(vehicle["secondsSpent"]) - else: - eta = vehicle["secondsSpent"] + if vehicle["etaR"]: #etaR is "" when eta is unavailable + if returnInUTC: + eta = convertToUnixEta(vehicle["secondsSpent"]) + else: + eta = vehicle["secondsSpent"] vehicles.append((eta, self.system.getVehicleById(int(vehicle["busId"])))) return vehicles From b1d5e3c992e4fd055da564bb0df8ec0444a2bd7b Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Tue, 1 Oct 2024 12:11:52 -0400 Subject: [PATCH 22/24] Moved Route.getStop/VehicleById() --- passiogo/__init__.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index ab51069..7768a45 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -1,4 +1,6 @@ import json +from typing import Optional, List + import requests import websocket @@ -583,6 +585,47 @@ def getStops(self): self.stops = stopsForRoute return(stopsForRoute) + def getVehicles( + self, + appVersion: int = 1 + ) -> List["Vehicle"]: + """ + Gets all vehicles following this route + """ + + vehiclesForSystem = self.system.getVehicles(appVersion=appVersion) + vehiclesForRoute = [vehicle for vehicle in vehiclesForSystem if str(vehicle.routeId) == self.myid] + return vehiclesForRoute + + def getStopById( + self, + stopId: str + ) -> Optional["Stop"]: + """ + Returns a Stop object corresponding to the provided ID. + """ + + stopsForRoute = self.getStops() + for stop in stopsForRoute: + if str(stop.id) == str(stopId): + return stop + return None + + def getVehicleById( + self, + vehicleId: str, + ) -> Optional["Vehicle"]: + """ + Returns a Vehicle object corresponding to the provided ID. + """ + + vehiclesForSystem = self.getVehicles() + for vehicle in vehiclesForSystem: + if str(vehicle.id) == str(vehicleId): + return vehicle + return None + + def refresh(self): self.stops = [] self.stops = self.getStops() From 1758ed3671ac5e447724fa5fedac50c0ea74fc7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20Th=C3=BCler?= <22741115+athuler@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:23:21 -0500 Subject: [PATCH 23/24] Fix indentation inconsistency --- passiogo/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index b6f626a..b5447ec 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -12,7 +12,7 @@ ### Helper Functions ### def toIntInclNone(toInt): - """ + """ Cast to int, returning None if input is None """ if toInt is None: From 3b4ca2ef618ead7c0dac2e0edea9b2841f9f2a39 Mon Sep 17 00:00:00 2001 From: Dominic Vinciulla Date: Wed, 2 Oct 2024 22:53:45 -0400 Subject: [PATCH 24/24] Implemented dict caching for improved id lookup --- passiogo/__init__.py | 89 +++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/passiogo/__init__.py b/passiogo/__init__.py index 7768a45..47cef31 100644 --- a/passiogo/__init__.py +++ b/passiogo/__init__.py @@ -76,10 +76,10 @@ def __init__( self.goSupportEmail = goSupportEmail self.goSharedCode = goSharedCode self.goAuthenticationType = goAuthenticationType - self.routes = [] - self.stops = [] - self.vehicles = [] - self.alerts = [] + self.routes = {} + self.stops = {} + self.vehicles = {} + self.alerts = {} self.checkTypes() @@ -143,7 +143,7 @@ def getRoutes( """ if self.routes: - return self.routes + return list(self.routes.values()) # Initialize & Send Request url = BASE_URL+f"/mapGetData.php?getRoutes={appVersion}" @@ -169,8 +169,8 @@ def getRoutes( for possibleKey in possibleKeys: if possibleKey not in route.keys(): route[possibleKey] = None - - allRoutes.append(Route( + + newRoute = Route( id = route["id"], groupId = route["groupId"], groupColor = route["groupColor"], @@ -192,9 +192,10 @@ def getRoutes( serviceTimeShort = route["serviceTimeShort"], systemId = int(route["userId"]), system = self - )) + ) + allRoutes.append(newRoute) + self.routes[newRoute.myid] = newRoute - self.routes = allRoutes return(allRoutes) def getStops( @@ -214,7 +215,7 @@ def getStops( """ if self.stops: - return self.stops + return list(self.stops.values()) # Initialize & Send Request url = BASE_URL+"/mapGetData.php?getStops="+str(appVersion) @@ -269,8 +270,8 @@ def getStops( for key in keys: if key not in stop: stop[key] = None - - allStops.append(Stop( + + newStop = Stop( id = stop["id"], routesAndPositions = routesAndPositions, systemId = None if stop["userId"] is None else int(stop["userId"]), @@ -279,9 +280,10 @@ def getStops( longitude = stop["longitude"], radius = stop["radius"], system = self, - )) + ) + allStops.append(newStop) + self.stops[newStop.id] = newStop - self.stops = allStops return(allStops) def getSystemAlerts( @@ -300,7 +302,7 @@ def getSystemAlerts( """ if self.alerts: - return self.alerts + return list(self.alerts.values()) # Initialize & Send Request url = BASE_URL+f"/goServices.php?getAlertMessages={appVersion}" @@ -318,7 +320,7 @@ def getSystemAlerts( # Create SystemAlert Objects allAlerts = [] for errorMsg in errorMsgs["msgs"]: - allAlerts.append(SystemAlert( + newAlert = SystemAlert( id = errorMsg["id"], systemId = errorMsg["userId"], system = self, @@ -348,9 +350,10 @@ def getSystemAlerts( fromF = errorMsg["fromF"], fromOk = errorMsg["fromOk"], toOk = errorMsg["toOk"], - )) + ) + allAlerts.append(newAlert) + self.alerts[newAlert.id] = newAlert - self.alerts = allAlerts return(allAlerts) def getVehicles( @@ -367,7 +370,7 @@ def getVehicles( """ if self.vehicles: - return self.vehicles + return list(self.vehicles.values()) # Initialize & Send Request url = BASE_URL+"/mapGetData.php?getBuses="+str(appVersion) @@ -392,8 +395,7 @@ def getVehicles( if key not in vehicle: vehicle[key] = None - - allVehicles.append(Vehicle( + newVehicle = Vehicle( id = vehicle["busId"], name = vehicle["busName"], type = vehicle["busType"], @@ -410,17 +412,18 @@ def getVehicles( outOfService = vehicle["outOfService"], more = vehicle["more"], tripId = vehicle["tripId"], - )) + ) + allVehicles.append(newVehicle) + self.vehicles[newVehicle.id] = newVehicle - self.vehicles = allVehicles return(allVehicles) def refresh(self): - self.routes, self.stops, self.vehicles, self.alerts = [], [], [], [] - self.routes = self.getRoutes() - self.stops = self.getStops() - self.vehicles = self.getVehicles() - self.alerts = self.getSystemAlerts() + self.routes, self.stops, self.vehicles, self.alerts = {}, {}, {}, {} + self.getRoutes() + self.getStops() + self.getVehicles() + self.getSystemAlerts() def getSystems( @@ -561,7 +564,8 @@ def __init__( self.serviceTimeShort = serviceTimeShort self.systemId = systemId self.system = system - self.stops = [] + self.stops = {} + self.vehicles = {} def getStops(self): @@ -570,7 +574,7 @@ def getStops(self): """ if self.stops: - return self.stops + return list(self.stops.values()) stopsForRoute = [] allStops = self.system.getStops() @@ -580,9 +584,9 @@ def getStops(self): self.myid in list(stop.routesAndPositions.keys()) or \ self.id in list(stop.routesAndPositions.keys()) or \ self.groupId in list(stop.routesAndPositions.keys()): - stopsForRoute.append(stop) + self.stops[stop.id] = stop + stopsForRoute.append(stop) - self.stops = stopsForRoute return(stopsForRoute) def getVehicles( @@ -593,8 +597,17 @@ def getVehicles( Gets all vehicles following this route """ + if self.vehicles: + return list(self.vehicles.values()) + vehiclesForSystem = self.system.getVehicles(appVersion=appVersion) - vehiclesForRoute = [vehicle for vehicle in vehiclesForSystem if str(vehicle.routeId) == self.myid] + vehiclesForRoute = [] + + for vehicle in vehiclesForSystem: + if vehicle.routeId == self.myid: + self.vehicles[vehicle.id] = vehicle + vehiclesForRoute.append(vehicle) + return vehiclesForRoute def getStopById( @@ -605,6 +618,9 @@ def getStopById( Returns a Stop object corresponding to the provided ID. """ + if stopId in self.stops: + return self.stops[stopId] + stopsForRoute = self.getStops() for stop in stopsForRoute: if str(stop.id) == str(stopId): @@ -619,6 +635,9 @@ def getVehicleById( Returns a Vehicle object corresponding to the provided ID. """ + if vehicleId in self.vehicles: + return self.vehicles[vehicleId] + vehiclesForSystem = self.getVehicles() for vehicle in vehiclesForSystem: if str(vehicle.id) == str(vehicleId): @@ -627,8 +646,8 @@ def getVehicleById( def refresh(self): - self.stops = [] - self.stops = self.getStops() + self.stops = {} + self.getStops() ### Stops ###