MBTAclient 0.2.4__tar.gz → 0.2.6__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.
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/PKG-INFO +6 -1
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/pyproject.toml +9 -2
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/MBTAclient.egg-info/PKG-INFO +6 -1
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/MBTAclient.egg-info/SOURCES.txt +1 -0
- mbtaclient-0.2.6/src/mbtaclient/__init__.py +31 -0
- mbtaclient-0.2.6/src/mbtaclient/__version__.py +1 -0
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/mbtaclient/base_handler.py +140 -146
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/mbtaclient/journey.py +8 -9
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/mbtaclient/journey_stop.py +2 -2
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/mbtaclient/journeys_handler.py +7 -7
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/mbtaclient/mbta_alert.py +1 -1
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/mbtaclient/mbta_client.py +11 -10
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/mbtaclient/mbta_prediction.py +1 -1
- mbtaclient-0.2.6/src/mbtaclient/mbta_utils.py +100 -0
- mbtaclient-0.2.6/src/mbtaclient/trip_handler.py +124 -0
- mbtaclient-0.2.4/src/mbtaclient/__init__.py +0 -0
- mbtaclient-0.2.4/src/mbtaclient/mbta_utils.py +0 -50
- mbtaclient-0.2.4/src/mbtaclient/trip_handler.py +0 -85
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/LICENSE +0 -0
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/README.md +0 -0
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/setup.cfg +0 -0
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/MBTAclient.egg-info/dependency_links.txt +0 -0
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/MBTAclient.egg-info/requires.txt +0 -0
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/MBTAclient.egg-info/top_level.txt +0 -0
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/mbtaclient/mbta_route.py +0 -0
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/mbtaclient/mbta_schedule.py +0 -0
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/mbtaclient/mbta_stop.py +0 -0
- {mbtaclient-0.2.4 → mbtaclient-0.2.6}/src/mbtaclient/mbta_trip.py +0 -0
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: MBTAclient
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: A Python client for interacting with the MBTA API
|
|
5
5
|
Author-email: Luca Chiabrera <luca.chiabrera@gmail.com>
|
|
6
|
+
License: MIT
|
|
6
7
|
Project-URL: Homepage, https://github.com/chiabre/MBTAclient
|
|
7
8
|
Project-URL: Issues, https://github.com/chiabre/MBTAclient/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
8
13
|
Requires-Python: >=3.12
|
|
9
14
|
Description-Content-Type: text/markdown
|
|
10
15
|
License-File: LICENSE
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "MBTAclient"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.6"
|
|
8
8
|
description = "A Python client for interacting with the MBTA API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -12,6 +12,14 @@ dependencies = [
|
|
|
12
12
|
"aiohttp>=3.9.5"
|
|
13
13
|
]
|
|
14
14
|
|
|
15
|
+
license = { text = "MIT" }
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.9",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent"
|
|
21
|
+
]
|
|
22
|
+
|
|
15
23
|
[[project.authors]]
|
|
16
24
|
name = "Luca Chiabrera"
|
|
17
25
|
email = "luca.chiabrera@gmail.com"
|
|
@@ -20,6 +28,5 @@ email = "luca.chiabrera@gmail.com"
|
|
|
20
28
|
Homepage = "https://github.com/chiabre/MBTAclient"
|
|
21
29
|
Issues = "https://github.com/chiabre/MBTAclient/issues"
|
|
22
30
|
|
|
23
|
-
|
|
24
31
|
[tool.setuptools.packages.find]
|
|
25
32
|
where = ["src"]
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: MBTAclient
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: A Python client for interacting with the MBTA API
|
|
5
5
|
Author-email: Luca Chiabrera <luca.chiabrera@gmail.com>
|
|
6
|
+
License: MIT
|
|
6
7
|
Project-URL: Homepage, https://github.com/chiabre/MBTAclient
|
|
7
8
|
Project-URL: Issues, https://github.com/chiabre/MBTAclient/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
8
13
|
Requires-Python: >=3.12
|
|
9
14
|
Description-Content-Type: text/markdown
|
|
10
15
|
License-File: LICENSE
|
|
@@ -7,6 +7,7 @@ src/MBTAclient.egg-info/dependency_links.txt
|
|
|
7
7
|
src/MBTAclient.egg-info/requires.txt
|
|
8
8
|
src/MBTAclient.egg-info/top_level.txt
|
|
9
9
|
src/mbtaclient/__init__.py
|
|
10
|
+
src/mbtaclient/__version__.py
|
|
10
11
|
src/mbtaclient/base_handler.py
|
|
11
12
|
src/mbtaclient/journey.py
|
|
12
13
|
src/mbtaclient/journey_stop.py
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# mbtaclient/__init__.py
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from .journey_stop import JourneyStop
|
|
5
|
+
from .journey import Journey
|
|
6
|
+
from .journeys_handler import JourneysHandler
|
|
7
|
+
from .mbta_alert import MBTAAlert
|
|
8
|
+
from .mbta_client import MBTAClient
|
|
9
|
+
from .mbta_prediction import MBTAPrediction
|
|
10
|
+
from .mbta_route import MBTARoute
|
|
11
|
+
from .mbta_schedule import MBTASchedule
|
|
12
|
+
from .mbta_stop import MBTAStop
|
|
13
|
+
from .mbta_trip import MBTATrip
|
|
14
|
+
from .trip_handler import TripHandler
|
|
15
|
+
from .__version__ import __version__
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"JourneyStop",
|
|
19
|
+
"Journey",
|
|
20
|
+
"JourneysHandler",
|
|
21
|
+
"MBTAAlert",
|
|
22
|
+
"MBTAClient",
|
|
23
|
+
"MBTARoute",
|
|
24
|
+
"MBTATrip",
|
|
25
|
+
"MBTAStop",
|
|
26
|
+
"MBTASchedule",
|
|
27
|
+
"MBTAPrediction",
|
|
28
|
+
"TripHandler",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
__version__ = __version__
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.5"
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import traceback
|
|
3
2
|
import aiohttp
|
|
4
3
|
|
|
5
4
|
from typing import Optional
|
|
6
|
-
from datetime import date
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
6
|
+
from .mbta_client import MBTAClient
|
|
7
|
+
from .journey import Journey
|
|
8
|
+
from .mbta_stop import MBTAStop
|
|
9
|
+
from .mbta_route import MBTARoute
|
|
10
|
+
from .mbta_schedule import MBTASchedule
|
|
11
|
+
from .mbta_prediction import MBTAPrediction
|
|
12
|
+
from .mbta_trip import MBTATrip
|
|
13
|
+
from .mbta_alert import MBTAAlert
|
|
14
|
+
from .mbta_utils import memoize_async
|
|
17
15
|
|
|
18
16
|
|
|
19
17
|
class BaseHandler:
|
|
20
18
|
"""Base class for handling MBTA journeys."""
|
|
21
19
|
|
|
22
|
-
def __init__(self,
|
|
20
|
+
def __init__(self, depart_from_name: str , arrive_at_name: str, api_key: str = None, session: aiohttp.ClientSession = None, logger: logging.Logger = None) -> None:
|
|
23
21
|
|
|
24
22
|
self.depart_from = {
|
|
25
23
|
'name' : depart_from_name,
|
|
@@ -31,20 +29,14 @@ class BaseHandler:
|
|
|
31
29
|
'stops' : None,
|
|
32
30
|
'ids' : None
|
|
33
31
|
}
|
|
34
|
-
|
|
32
|
+
|
|
33
|
+
client_session = session or aiohttp.ClientSession()
|
|
34
|
+
self.mbta_client = MBTAClient(client_session,logger,api_key)
|
|
35
35
|
|
|
36
36
|
self.journeys: dict[str, Journey] = {}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
self._stops_cache: Optional[list[MBTAStop]] = None
|
|
40
|
-
self._schedules_cache: Optional[list[MBTASchedule]] = None
|
|
41
|
-
self._schedules_cache_date: Optional[date] = None
|
|
42
|
-
self._trip_cache: dict[str, MBTATrip] = {}
|
|
43
|
-
self._route_cache: dict[str, MBTARoute] = {}
|
|
37
|
+
|
|
38
|
+
self.logger: logging.Logger = logger or logging.getLogger(__name__)
|
|
44
39
|
|
|
45
|
-
# Logger
|
|
46
|
-
self.logger: logging.Logger = logger
|
|
47
|
-
|
|
48
40
|
def __repr__(self) -> str:
|
|
49
41
|
return (f"BaseHandler(depart_from_name={self.depart_from['name']}, arrive_at_name={self.arrive_at['name']})")
|
|
50
42
|
|
|
@@ -53,56 +45,52 @@ class BaseHandler:
|
|
|
53
45
|
stops = await self.__fetch_stops()
|
|
54
46
|
self.__process_stops(stops)
|
|
55
47
|
|
|
48
|
+
@memoize_async()
|
|
56
49
|
async def __fetch_stops(self, params: dict = None) -> list[MBTAStop]:
|
|
57
|
-
"""
|
|
58
|
-
self.logger.debug("
|
|
59
|
-
|
|
60
|
-
# Check if stops are already cached
|
|
61
|
-
if self._stops_cache is not None:
|
|
62
|
-
return self._stops_cache
|
|
63
|
-
|
|
64
|
-
# Cache is empty, so we fetch the stops from the API
|
|
50
|
+
"""Retrive stops """
|
|
51
|
+
self.logger.debug("Retriving MBTA stops")
|
|
65
52
|
base_params = {'filter[location_type]': '0'}
|
|
66
|
-
|
|
67
53
|
if params is not None:
|
|
68
54
|
base_params.update(params)
|
|
69
|
-
|
|
70
55
|
try:
|
|
71
56
|
stops: list[MBTAStop] = await self.mbta_client.list_stops(base_params)
|
|
72
|
-
self.logger.debug("Updating cached stops")
|
|
73
|
-
self._stops_cache = stops
|
|
74
57
|
return stops
|
|
75
|
-
|
|
76
58
|
except Exception as e:
|
|
77
|
-
self.logger.error(f"Error
|
|
78
|
-
traceback.print_exc()
|
|
59
|
+
self.logger.error(f"Error retriving MBTA stops: {e}")
|
|
79
60
|
return []
|
|
80
61
|
|
|
81
|
-
def
|
|
62
|
+
def __process_stops(self, stops: list[MBTAStop]):
|
|
63
|
+
self.logger.debug("Processing MBTA stops")
|
|
82
64
|
depart_from_stops = []
|
|
83
65
|
depart_from_ids = []
|
|
84
66
|
arrive_at_stops = []
|
|
85
67
|
arrive_at_ids = []
|
|
68
|
+
|
|
86
69
|
for stop in stops:
|
|
70
|
+
if not isinstance(stop, MBTAStop): # Validate data type
|
|
71
|
+
self.logger.warning(f"Unexpected data type for stop: {type(stop)}")
|
|
72
|
+
continue # Skip invalid data
|
|
73
|
+
|
|
87
74
|
if stop.name.lower() == self.depart_from['name'].lower():
|
|
88
75
|
depart_from_stops.append(stop)
|
|
89
76
|
depart_from_ids.append(stop.id)
|
|
77
|
+
|
|
90
78
|
if stop.name.lower() == self.arrive_at['name'].lower():
|
|
91
79
|
arrive_at_stops.append(stop)
|
|
92
80
|
arrive_at_ids.append(stop.id)
|
|
93
|
-
|
|
81
|
+
|
|
94
82
|
if len(depart_from_stops) == 0:
|
|
95
|
-
self.logger.error(f"Error
|
|
96
|
-
raise
|
|
97
|
-
|
|
83
|
+
self.logger.error(f"Error processing MBTA stop data for {self.depart_from['name']}")
|
|
84
|
+
raise MBTAStopError(f"Invalid stop name: {self.depart_from['name']}")
|
|
85
|
+
|
|
98
86
|
if len(arrive_at_stops) == 0:
|
|
99
|
-
self.logger.error(f"Error
|
|
100
|
-
raise
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
87
|
+
self.logger.error(f"Error processing MBTA stop data for {self.arrive_at['name']}")
|
|
88
|
+
raise MBTAStopError(f"Invalid stop name: {self.arrive_at['name']}")
|
|
89
|
+
|
|
90
|
+
self.depart_from['stops'] = depart_from_stops
|
|
91
|
+
self.depart_from['ids'] = depart_from_ids
|
|
92
|
+
self.arrive_at['stops'] = arrive_at_stops
|
|
93
|
+
self.arrive_at['ids'] = arrive_at_ids
|
|
106
94
|
|
|
107
95
|
def __get_stop_by_id(self, stop_id: str) -> Optional[MBTAStop]:
|
|
108
96
|
for stop in (self.depart_from['stops'] + self.arrive_at['stops']):
|
|
@@ -120,106 +108,116 @@ class BaseHandler:
|
|
|
120
108
|
return self.arrive_at['ids']
|
|
121
109
|
return None
|
|
122
110
|
|
|
111
|
+
@memoize_async(expire_at_end_of_day=True)
|
|
123
112
|
async def _fetch_schedules(self, params: Optional[dict] = None) -> list[MBTASchedule]:
|
|
124
|
-
"""
|
|
125
|
-
self.logger.debug("
|
|
126
|
-
|
|
127
|
-
# Check if the cache is outdated
|
|
128
|
-
if self._schedules_cache_date is not None and self._schedules_cache_date == date.today():
|
|
129
|
-
self.logger.debug("Returning cached schedules")
|
|
130
|
-
return self._schedules_cache
|
|
131
|
-
|
|
113
|
+
"""Retrive MBTA schedules"""
|
|
114
|
+
self.logger.debug("Retriving MBTA schedules")
|
|
132
115
|
base_params = {
|
|
133
116
|
'filter[stop]': ','.join(self._get_stops_ids()),
|
|
134
117
|
'sort': 'departure_time'
|
|
135
118
|
}
|
|
136
119
|
if params is not None:
|
|
137
120
|
base_params.update(params)
|
|
138
|
-
|
|
139
121
|
try:
|
|
140
|
-
schedules: list[MBTASchedule] = await self.mbta_client.list_schedules(
|
|
141
|
-
# Update the cache with new data and timestamp
|
|
142
|
-
self.logger.debug("Updating cached schedules")
|
|
143
|
-
self._schedules_cache = schedules
|
|
144
|
-
self._schedules_cache_date = date.today()
|
|
122
|
+
schedules: list[MBTASchedule] = await self.mbta_client.list_schedules(params)
|
|
145
123
|
return schedules
|
|
146
124
|
except Exception as e:
|
|
147
|
-
self.logger.error(f"Error
|
|
148
|
-
traceback.print_exc()
|
|
125
|
+
self.logger.error(f"Error retriving MBTA schedules: {e}")
|
|
149
126
|
return []
|
|
150
127
|
|
|
151
128
|
async def _process_schedules(self, schedules: list[MBTASchedule]):
|
|
152
|
-
self.logger.debug("Processing schedules")
|
|
153
|
-
|
|
154
|
-
for schedule in schedules:
|
|
155
|
-
|
|
156
|
-
|
|
129
|
+
self.logger.debug("Processing MBTA schedules")
|
|
130
|
+
|
|
131
|
+
for schedule in schedules:
|
|
132
|
+
# Validate schedule data
|
|
133
|
+
if not schedule.trip_id or not schedule.stop_id:
|
|
134
|
+
self.logger.error(f"Invalid schedule data: {schedule}")
|
|
135
|
+
continue # Skip to the next schedule
|
|
136
|
+
|
|
137
|
+
# If the schedule trip_id is not in the journeys
|
|
157
138
|
if schedule.trip_id not in self.journeys:
|
|
158
|
-
#
|
|
139
|
+
# Create the journey
|
|
159
140
|
journey = Journey()
|
|
160
|
-
#
|
|
141
|
+
# Add the journey to the journeys dict using the trip_id as key
|
|
161
142
|
self.journeys[schedule.trip_id] = journey
|
|
162
|
-
|
|
163
|
-
|
|
143
|
+
|
|
144
|
+
# Validate stop
|
|
145
|
+
stop = self.__get_stop_by_id(schedule.stop_id)
|
|
146
|
+
if not stop:
|
|
147
|
+
self.logger.debug(f"Stop {schedule.stop_id} of schedule {schedule.id} doesn't belong to the journey stop ids")
|
|
148
|
+
continue # Skip to the next schedule
|
|
149
|
+
|
|
164
150
|
departure_stops_ids = self.__get_stops_ids_by_stop_type('departure')
|
|
165
151
|
arrival_stops_ids = self.__get_stops_ids_by_stop_type('arrival')
|
|
166
|
-
|
|
152
|
+
|
|
153
|
+
# Check if the stop_id is in the departure or arrival stops lists
|
|
167
154
|
if schedule.stop_id in departure_stops_ids:
|
|
168
|
-
self.journeys[schedule.trip_id].add_stop('departure',schedule,stop,'SCHEDULED')
|
|
155
|
+
self.journeys[schedule.trip_id].add_stop('departure', schedule, stop, 'SCHEDULED')
|
|
169
156
|
elif schedule.stop_id in arrival_stops_ids:
|
|
170
|
-
self.journeys[schedule.trip_id].add_stop('arrival',schedule, stop,'SCHEDULED')
|
|
157
|
+
self.journeys[schedule.trip_id].add_stop('arrival', schedule, stop, 'SCHEDULED')
|
|
158
|
+
else:
|
|
159
|
+
self.logger.warning(f"Stop ID {schedule.stop_id} is not categorized as departure or arrival for schedule: {schedule}")
|
|
160
|
+
|
|
171
161
|
|
|
172
162
|
|
|
173
163
|
async def _fetch_predictions(self, params: str = None) -> list[MBTAPrediction]:
|
|
174
|
-
"""
|
|
175
|
-
self.logger.debug("
|
|
176
|
-
|
|
164
|
+
"""Retrive MBTA predictions based on the provided stop IDs"""
|
|
165
|
+
self.logger.debug("Retriving MBTA predictions")
|
|
177
166
|
base_params = {
|
|
178
167
|
'filter[stop]': ','.join(self._get_stops_ids()),
|
|
179
168
|
'filter[revenue]': 'REVENUE',
|
|
180
169
|
'sort': 'departure_time'
|
|
181
170
|
}
|
|
182
|
-
|
|
183
171
|
if params is not None:
|
|
184
172
|
base_params.update(params)
|
|
185
|
-
|
|
186
173
|
try:
|
|
187
174
|
predictions: list[MBTAPrediction] = await self.mbta_client.list_predictions(base_params)
|
|
188
175
|
return predictions
|
|
189
|
-
|
|
190
176
|
except Exception as e:
|
|
191
|
-
self.logger.error(f"Error
|
|
192
|
-
|
|
177
|
+
self.logger.error(f"Error retriving MBTA predictions: {e}")
|
|
178
|
+
|
|
193
179
|
|
|
194
|
-
async def _process_predictions
|
|
195
|
-
self.logger.debug("Processing predictions")
|
|
196
|
-
|
|
180
|
+
async def _process_predictions(self, predictions: list[MBTAPrediction]):
|
|
181
|
+
self.logger.debug("Processing MBTA predictions")
|
|
182
|
+
|
|
197
183
|
for prediction in predictions:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
#
|
|
184
|
+
# Validate prediction data
|
|
185
|
+
if not prediction.trip_id or not prediction.stop_id:
|
|
186
|
+
self.logger.error(f"Invalid prediction data: {prediction}")
|
|
187
|
+
continue # Skip to the next prediction
|
|
188
|
+
|
|
189
|
+
# If the trip of the prediction is not in the journeys dict
|
|
190
|
+
if prediction.trip_id not in self.journeys:
|
|
191
|
+
# Create the journey
|
|
202
192
|
journey = Journey()
|
|
203
|
-
#
|
|
193
|
+
# Add the journey to the journeys dict using the trip_id as key
|
|
204
194
|
self.journeys[prediction.trip_id] = journey
|
|
205
|
-
|
|
206
|
-
|
|
195
|
+
|
|
196
|
+
# Validate stop
|
|
197
|
+
stop = self.__get_stop_by_id(prediction.stop_id)
|
|
198
|
+
if not stop:
|
|
199
|
+
self.logger.error(f"Invalid stop ID: {prediction.stop_id} for prediction: {prediction}")
|
|
200
|
+
continue # Skip to the next prediction
|
|
201
|
+
|
|
207
202
|
departure_stops_ids = self.__get_stops_ids_by_stop_type('departure')
|
|
208
203
|
arrival_stops_ids = self.__get_stops_ids_by_stop_type('arrival')
|
|
209
|
-
|
|
204
|
+
|
|
205
|
+
# Default schedule relationship to 'PREDICTED' if not set
|
|
210
206
|
if prediction.schedule_relationship is None:
|
|
211
|
-
prediction.schedule_relationship = 'PREDICTED'
|
|
212
|
-
|
|
213
|
-
# if the prediction
|
|
207
|
+
prediction.schedule_relationship = 'PREDICTED'
|
|
208
|
+
|
|
209
|
+
# Check if the prediction stop_id is in the departure or arrival stops lists
|
|
214
210
|
if prediction.stop_id in departure_stops_ids:
|
|
215
|
-
self.journeys[prediction.trip_id].add_stop('departure',prediction,stop,prediction.schedule_relationship)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
211
|
+
self.journeys[prediction.trip_id].add_stop('departure', prediction, stop, prediction.schedule_relationship)
|
|
212
|
+
elif prediction.stop_id in arrival_stops_ids:
|
|
213
|
+
self.journeys[prediction.trip_id].add_stop('arrival', prediction, stop, prediction.schedule_relationship)
|
|
214
|
+
else:
|
|
215
|
+
self.logger.warning(f"Stop ID {prediction.stop_id} is not categorized as departure or arrival for prediction: {prediction}")
|
|
216
|
+
|
|
219
217
|
|
|
220
218
|
async def _fetch_alerts(self,params: str = None) -> list[MBTAAlert]:
|
|
221
|
-
"""
|
|
222
|
-
self.logger.debug("
|
|
219
|
+
"""Retrive MBTA alerts"""
|
|
220
|
+
self.logger.debug("Retriving MBTA alerts")
|
|
223
221
|
|
|
224
222
|
# Prepare filter parameters
|
|
225
223
|
base_params = {
|
|
@@ -235,22 +233,31 @@ class BaseHandler:
|
|
|
235
233
|
alerts: list[MBTAAlert] = await self.mbta_client.list_alerts(base_params)
|
|
236
234
|
return alerts
|
|
237
235
|
except Exception as e:
|
|
238
|
-
self.logger.error(f"Error
|
|
239
|
-
|
|
236
|
+
self.logger.error(f"Error retriving MBTA alerts: {e}")
|
|
237
|
+
|
|
240
238
|
|
|
241
239
|
def _process_alerts(self, alerts: list[MBTAAlert]):
|
|
242
|
-
self.logger.debug("Processing alerts")
|
|
240
|
+
self.logger.debug("Processing MBTA alerts")
|
|
243
241
|
|
|
244
242
|
for alert in alerts:
|
|
245
|
-
|
|
243
|
+
# Validate alert data
|
|
244
|
+
if not alert.id or not alert.effect:
|
|
245
|
+
self.logger.error(f"Invalid alert data: {alert}")
|
|
246
|
+
continue # Skip to the next alert
|
|
247
|
+
|
|
246
248
|
# Iterate through each journey and associate relevant alerts
|
|
247
249
|
for journey in self.journeys.values():
|
|
248
|
-
|
|
250
|
+
# Check if the alert is already associated by comparing IDs
|
|
251
|
+
if any(existing_alert.id == alert.id for existing_alert in journey.alerts):
|
|
249
252
|
continue # Skip if alert is already associated
|
|
250
253
|
|
|
251
254
|
# Check if the alert is relevant to the journey
|
|
252
|
-
|
|
253
|
-
|
|
255
|
+
try:
|
|
256
|
+
if self.__is_alert_relevant(alert, journey):
|
|
257
|
+
journey.alerts.append(alert)
|
|
258
|
+
except Exception as e:
|
|
259
|
+
self.logger.error(f"Error processing MBTA alert {alert.id}: {e}")
|
|
260
|
+
continue # Skip to the next journey if an error occurs
|
|
254
261
|
|
|
255
262
|
def __is_alert_relevant(self, alert: MBTAAlert, journey: Journey) -> bool:
|
|
256
263
|
"""Check if an alert is relevant to a given journey."""
|
|
@@ -281,55 +288,42 @@ class BaseHandler:
|
|
|
281
288
|
return False
|
|
282
289
|
return True
|
|
283
290
|
|
|
291
|
+
@memoize_async()
|
|
284
292
|
async def _fetch_trip(self, trip_id: str, params: dict = None) -> Optional[MBTATrip]:
|
|
285
|
-
"""Retrieve
|
|
286
|
-
self.logger.debug(f"
|
|
287
|
-
|
|
288
|
-
# Check if the trip is already cached
|
|
289
|
-
if trip_id in self._trip_cache:
|
|
290
|
-
self.logger.debug(f"Returning cached trip: {trip_id}")
|
|
291
|
-
return self._trip_cache[trip_id]
|
|
292
|
-
|
|
293
|
-
# Trip is not in the cache, so fetch it from the API
|
|
293
|
+
"""Retrieve MBTA trip based on trip_id."""
|
|
294
|
+
self.logger.debug(f"Retriving MBTA trip: {trip_id} ")
|
|
294
295
|
try:
|
|
295
296
|
trip: MBTATrip = await self.mbta_client.get_trip(trip_id, params)
|
|
296
|
-
self.logger.debug(f"Updating cached trip: {trip_id}")
|
|
297
|
-
self._trip_cache[trip_id] = trip
|
|
298
297
|
return trip
|
|
299
|
-
|
|
300
298
|
except Exception as e:
|
|
301
|
-
self.logger.error(f"Error fetching trip: {e}")
|
|
302
|
-
traceback.print_exc()
|
|
299
|
+
self.logger.error(f"Error fetching trip {trip_id}: {e}")
|
|
303
300
|
return None
|
|
304
|
-
|
|
301
|
+
|
|
302
|
+
@memoize_async()
|
|
305
303
|
async def _fetch_route(self, route_id: str, params: dict = None) -> Optional[MBTARoute]:
|
|
306
|
-
"""
|
|
307
|
-
self.logger.debug(f"
|
|
308
|
-
|
|
309
|
-
# Check if the trip is already cached
|
|
310
|
-
if route_id in self._route_cache:
|
|
311
|
-
return self._route_cache[route_id]
|
|
312
|
-
|
|
313
|
-
# Trip is not in the cache, so fetch it from the API
|
|
304
|
+
"""Retrive MBTA route based on route_id."""
|
|
305
|
+
self.logger.debug(f"Retriving MBTA route: {route_id} ")
|
|
314
306
|
try:
|
|
315
307
|
route: MBTARoute = await self.mbta_client.get_route(route_id, params)
|
|
316
|
-
# Update the cache
|
|
317
|
-
self.logger.debug(f"Updating cached route: {route_id}")
|
|
318
|
-
self._route_cache[route_id] = route
|
|
319
308
|
return route
|
|
320
|
-
|
|
321
309
|
except Exception as e:
|
|
322
|
-
self.logger.error(f"Error
|
|
323
|
-
traceback.print_exc()
|
|
310
|
+
self.logger.error(f"Error retriving MBTA route {route_id}: {e}")
|
|
324
311
|
return None
|
|
325
|
-
|
|
312
|
+
|
|
313
|
+
@memoize_async()
|
|
326
314
|
async def _fetch_trips(self, params: dict = None) -> Optional[MBTARoute]:
|
|
327
|
-
|
|
315
|
+
"""Retrive MBTA trips"""
|
|
316
|
+
self.logger.debug("Retriving MBTA trips")
|
|
328
317
|
try:
|
|
329
318
|
trips: list[MBTATrip] = await self.mbta_client.list_trips(params)
|
|
330
319
|
return trips
|
|
331
320
|
except Exception as e:
|
|
332
|
-
self.logger.error(f"Error
|
|
333
|
-
traceback.print_exc()
|
|
321
|
+
self.logger.error(f"Error retriving MBTA route: {e}")
|
|
334
322
|
return None
|
|
335
323
|
|
|
324
|
+
|
|
325
|
+
class MBTAStopError(Exception):
|
|
326
|
+
pass
|
|
327
|
+
|
|
328
|
+
class MBTATripError(Exception):
|
|
329
|
+
pass
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
from typing import Union, Optional
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
|
|
4
|
-
from journey_stop import JourneyStop
|
|
5
|
-
from mbta_schedule import MBTASchedule
|
|
6
|
-
from mbta_prediction import MBTAPrediction
|
|
7
|
-
from mbta_stop import MBTAStop
|
|
8
|
-
from mbta_route import MBTARoute
|
|
9
|
-
from mbta_trip import MBTATrip
|
|
10
|
-
from mbta_alert import MBTAAlert
|
|
11
|
-
from mbta_utils import MBTAUtils
|
|
4
|
+
from .journey_stop import JourneyStop
|
|
5
|
+
from .mbta_schedule import MBTASchedule
|
|
6
|
+
from .mbta_prediction import MBTAPrediction
|
|
7
|
+
from .mbta_stop import MBTAStop
|
|
8
|
+
from .mbta_route import MBTARoute
|
|
9
|
+
from .mbta_trip import MBTATrip
|
|
10
|
+
from .mbta_alert import MBTAAlert
|
|
11
|
+
from .mbta_utils import MBTAUtils
|
|
12
12
|
|
|
13
13
|
class Journey:
|
|
14
14
|
"""A class to manage a journey with multiple stops."""
|
|
@@ -90,7 +90,6 @@ class Journey:
|
|
|
90
90
|
return self.route.color if self.route else None
|
|
91
91
|
|
|
92
92
|
def get_route_description(self) -> Optional[str]:
|
|
93
|
-
from mbta_utils import MBTAUtils
|
|
94
93
|
return MBTAUtils.get_route_type_desc_by_type_id(self.route.type) if self.route else None
|
|
95
94
|
|
|
96
95
|
def get_route_type(self) -> Optional[str]:
|
|
@@ -3,18 +3,18 @@ import logging
|
|
|
3
3
|
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
|
|
6
|
-
from base_handler import BaseHandler
|
|
7
|
-
from journey import Journey
|
|
8
|
-
from mbta_route import MBTARoute
|
|
9
|
-
from mbta_trip import MBTATrip
|
|
10
|
-
from mbta_schedule import MBTASchedule
|
|
6
|
+
from .base_handler import BaseHandler
|
|
7
|
+
from .journey import Journey
|
|
8
|
+
from .mbta_route import MBTARoute
|
|
9
|
+
from .mbta_trip import MBTATrip
|
|
10
|
+
from .mbta_schedule import MBTASchedule
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class JourneysHandler(BaseHandler):
|
|
14
14
|
"""Handler for managing a specific journey."""
|
|
15
15
|
|
|
16
|
-
def __init__(self,
|
|
17
|
-
super().__init__(
|
|
16
|
+
def __init__(self, depart_from_name: str, arrive_at_name: str, max_journeys: int, api_key:str = None, session: aiohttp.ClientSession = None, logger: logging.Logger = None):
|
|
17
|
+
super().__init__(depart_from_name=depart_from_name, arrive_at_name=arrive_at_name, api_key=api_key, session=session, logger=logger)
|
|
18
18
|
self.max_journeys = max_journeys
|
|
19
19
|
|
|
20
20
|
async def async_init(self):
|
|
@@ -7,7 +7,7 @@ class MBTAAlert:
|
|
|
7
7
|
attributes = alert.get('attributes', {})
|
|
8
8
|
|
|
9
9
|
# Basic attributes
|
|
10
|
-
self.
|
|
10
|
+
self.id: str = alert.get('id', '')
|
|
11
11
|
self.active_period_start: Optional[str] = attributes.get('active_period', [{}])[0].get('start', None)
|
|
12
12
|
self.active_period_end: Optional[str] = attributes.get('active_period', [{}])[0].get('end', None)
|
|
13
13
|
self.cause: str = attributes.get('cause', '')
|
|
@@ -2,12 +2,13 @@ import aiohttp
|
|
|
2
2
|
import logging
|
|
3
3
|
from aiohttp import ClientConnectionError, ClientResponseError
|
|
4
4
|
from typing import Optional, Any
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from
|
|
5
|
+
|
|
6
|
+
from .mbta_route import MBTARoute
|
|
7
|
+
from .mbta_stop import MBTAStop
|
|
8
|
+
from .mbta_schedule import MBTASchedule
|
|
9
|
+
from .mbta_prediction import MBTAPrediction
|
|
10
|
+
from .mbta_trip import MBTATrip
|
|
11
|
+
from .mbta_alert import MBTAAlert
|
|
11
12
|
|
|
12
13
|
MBTA_DEFAULT_HOST = "api-v3.mbta.com"
|
|
13
14
|
|
|
@@ -23,10 +24,10 @@ ENDPOINTS = {
|
|
|
23
24
|
class MBTAClient:
|
|
24
25
|
"""Class to interact with the MBTA v3 API."""
|
|
25
26
|
|
|
26
|
-
def __init__(self, session: aiohttp.ClientSession, logger: logging.Logger, api_key: Optional[str] = None)-> None:
|
|
27
|
-
self._session = session
|
|
28
|
-
self.
|
|
29
|
-
self.
|
|
27
|
+
def __init__(self, session: aiohttp.ClientSession = None, logger: logging.Logger = None, api_key: Optional[str] = None)-> None:
|
|
28
|
+
self._session = session or aiohttp.ClientSession()
|
|
29
|
+
self.logger: logging.Logger = logger or logging.getLogger(__name__)
|
|
30
|
+
self._api_key: str = api_key
|
|
30
31
|
|
|
31
32
|
async def get_route(self, id: str, params: Optional[dict[str, Any]] = None) -> MBTARoute:
|
|
32
33
|
"""Get a route by its ID."""
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from collections.abc import Hashable
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
# logging.basicConfig(level=logging.DEBUG)
|
|
8
|
+
_LOGGER = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MBTAUtils:
|
|
12
|
+
|
|
13
|
+
ROUTE_TYPES= {
|
|
14
|
+
# 0: 'Light Rail', # Example: Green Line
|
|
15
|
+
# 1: 'Heavy Rail', # Example: Red Line
|
|
16
|
+
0: 'Subway',
|
|
17
|
+
1: 'Subway',
|
|
18
|
+
2: 'Commuter Rail',
|
|
19
|
+
3: 'Bus',
|
|
20
|
+
4: 'Ferry'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
UNCERTAINTY = {
|
|
24
|
+
'60': 'Trip that has already started',
|
|
25
|
+
'120': 'Trip not started and a vehicle is awaiting departure at the origin',
|
|
26
|
+
'300': 'Vehicle has not yet been assigned to the trip',
|
|
27
|
+
'301': 'Vehicle appears to be stalled or significantly delayed',
|
|
28
|
+
'360': 'Trip not started and a vehicle is completing a previous trip'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def get_route_type_desc_by_type_id(route_type: int) -> str:
|
|
33
|
+
"""Get a description of the route type."""
|
|
34
|
+
return MBTAUtils.ROUTE_TYPES.get(route_type, 'Unknown')
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def get_uncertainty_description(key: str) -> str:
|
|
38
|
+
return MBTAUtils.UNCERTAINTY.get(key, 'None')
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def time_to(time: Optional[datetime], now: datetime) -> Optional[float]:
|
|
42
|
+
if time is None:
|
|
43
|
+
return None
|
|
44
|
+
return (time - now).total_seconds()
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def calculate_time_difference(real_time: Optional[datetime], time: Optional[datetime]) -> Optional[float]:
|
|
48
|
+
if real_time is None or time is None:
|
|
49
|
+
return None
|
|
50
|
+
return (real_time - time).total_seconds()
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def parse_datetime(time_str: str) -> Optional[datetime]:
|
|
54
|
+
"""Parse a string in ISO 8601 format to a datetime object."""
|
|
55
|
+
if not isinstance(time_str, str):
|
|
56
|
+
return None
|
|
57
|
+
return datetime.fromisoformat(time_str)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
from datetime import datetime, timedelta
|
|
62
|
+
import logging
|
|
63
|
+
|
|
64
|
+
logger = logging.getLogger(__name__)
|
|
65
|
+
|
|
66
|
+
def memoize_async(expire_at_end_of_day=False):
|
|
67
|
+
def decorator(func):
|
|
68
|
+
cache = {}
|
|
69
|
+
|
|
70
|
+
def make_hashable(item):
|
|
71
|
+
if isinstance(item, dict):
|
|
72
|
+
# Exclude the 'filter[min_time]' key from the dictionary
|
|
73
|
+
item = {k: v for k, v in item.items() if k != 'filter[min_time]'}
|
|
74
|
+
return frozenset((make_hashable(k), make_hashable(v)) for k, v in item.items())
|
|
75
|
+
return str(item) # Convert non-dict items to string
|
|
76
|
+
|
|
77
|
+
async def wrapper(*args):
|
|
78
|
+
current_time = datetime.now()
|
|
79
|
+
cache_key = tuple(make_hashable(arg) for arg in args)
|
|
80
|
+
|
|
81
|
+
if cache_key in cache:
|
|
82
|
+
cached_result, timestamp = cache[cache_key]
|
|
83
|
+
|
|
84
|
+
if expire_at_end_of_day:
|
|
85
|
+
if timestamp.date() == current_time.date():
|
|
86
|
+
_LOGGER.debug(f"Cache hit for {func.__name__} with arguments {cache_key} at {current_time}")
|
|
87
|
+
return cached_result
|
|
88
|
+
else: # Expiration based on 30 days
|
|
89
|
+
if current_time - timestamp < timedelta(days=30):
|
|
90
|
+
_LOGGER.debug(f"Cache hit for {func.__name__} with arguments {cache_key} at {current_time}")
|
|
91
|
+
return cached_result
|
|
92
|
+
|
|
93
|
+
_LOGGER.debug(f"Cache miss for {func.__name__} with arguments {cache_key} at {current_time}")
|
|
94
|
+
result = await func(*args)
|
|
95
|
+
cache[cache_key] = (result, current_time)
|
|
96
|
+
_LOGGER.debug(f"Cache updated for key: {cache_key} at {current_time}")
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
return wrapper
|
|
100
|
+
return decorator
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from .base_handler import BaseHandler, MBTATripError
|
|
6
|
+
from .journey import Journey
|
|
7
|
+
from .mbta_route import MBTARoute
|
|
8
|
+
from .mbta_trip import MBTATrip
|
|
9
|
+
from .mbta_schedule import MBTASchedule
|
|
10
|
+
from .mbta_prediction import MBTAPrediction
|
|
11
|
+
|
|
12
|
+
class TripHandler(BaseHandler):
|
|
13
|
+
"""Handler for managing a specific trip."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, depart_from_name: str, arrive_at_name: str, trip_name: str, api_key:str = None, session: aiohttp.ClientSession = None, logger: logging.Logger = None):
|
|
16
|
+
super().__init__( depart_from_name=depart_from_name, arrive_at_name=arrive_at_name, api_key=api_key, session=session, logger=logger)
|
|
17
|
+
self.trip_name = trip_name
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def async_init(self):
|
|
21
|
+
self.logger.debug("Initializing TripHandler")
|
|
22
|
+
try:
|
|
23
|
+
await super()._async_init()
|
|
24
|
+
|
|
25
|
+
self.logger.debug("Retriving MBTA trip")
|
|
26
|
+
params = {
|
|
27
|
+
'filter[revenue]': 'REVENUE',
|
|
28
|
+
'filter[name]': self.trip_name
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Fetch trips and validate the response
|
|
32
|
+
trips: list[MBTATrip] = await super()._fetch_trips(params)
|
|
33
|
+
if not trips or not isinstance(trips, list) or not trips[0]:
|
|
34
|
+
self.logger.error(f"Error retriving MBTA trip {self.trip_name}")
|
|
35
|
+
raise MBTATripError("Invalid trip name")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Create a new journey and assign the first trip
|
|
39
|
+
journey = Journey()
|
|
40
|
+
journey.trip = trips[0]
|
|
41
|
+
|
|
42
|
+
# Fetch route and validate the response
|
|
43
|
+
self.logger.debug("Retriving MBTA route")
|
|
44
|
+
route: MBTARoute = await super()._fetch_route(journey.trip.route_id)
|
|
45
|
+
|
|
46
|
+
journey.route = route
|
|
47
|
+
self.journeys[trips[0].id] = journey
|
|
48
|
+
|
|
49
|
+
except Exception as e:
|
|
50
|
+
self.logger.error(f"Error during TripHandler initialization: {e}")
|
|
51
|
+
|
|
52
|
+
async def update(self) -> list[Journey]:
|
|
53
|
+
now = datetime.now().astimezone()
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
for i in range(7):
|
|
57
|
+
params = {}
|
|
58
|
+
# Calculate the date for each attempt (i days after today)
|
|
59
|
+
date_to_try = (now + timedelta(days=i)).strftime('%Y-%m-%d')
|
|
60
|
+
params['filter[date]'] = date_to_try
|
|
61
|
+
if i == 0:
|
|
62
|
+
params['filter[min_time]'] = now.strftime('%H:%M')
|
|
63
|
+
|
|
64
|
+
# Attempt to get schedules for up to the next 7 days
|
|
65
|
+
schedules = await self.__fetch_schedules(params)
|
|
66
|
+
await super()._process_schedules(schedules)
|
|
67
|
+
if next(iter(self.journeys.values())).get_stop_time_to('arrival') is not None:
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
# If it's the last attempt and no valid schedules were found, log an error and raise an exception
|
|
71
|
+
if i == 6:
|
|
72
|
+
self.logger.error(
|
|
73
|
+
f"Error retrieving scheduling for {self.depart_from['name']} and {self.arrive_at['name']} on trip {self.trip_name}"
|
|
74
|
+
)
|
|
75
|
+
raise MBTATripError("Invalid stops for the trip")
|
|
76
|
+
|
|
77
|
+
except MBTATripError as e:
|
|
78
|
+
# Handle the error here without re-raising it
|
|
79
|
+
self.logger.error(f"{e}")
|
|
80
|
+
# Continue with other operations despite the failure
|
|
81
|
+
|
|
82
|
+
predictions = await self.__fetch_predictions()
|
|
83
|
+
await super()._process_predictions(predictions)
|
|
84
|
+
|
|
85
|
+
alerts = await self.__fetch_alerts()
|
|
86
|
+
super()._process_alerts(alerts)
|
|
87
|
+
return list(self.journeys.values())
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def __fetch_schedules(self, params: dict) -> list[MBTASchedule]:
|
|
91
|
+
journey = next(iter(self.journeys.values()))
|
|
92
|
+
trip_id = journey.trip.id
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
base_params = {
|
|
96
|
+
'filter[trip]': trip_id,
|
|
97
|
+
}
|
|
98
|
+
if params is not None:
|
|
99
|
+
base_params.update(params)
|
|
100
|
+
|
|
101
|
+
schedules = await super()._fetch_schedules(base_params)
|
|
102
|
+
return schedules
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
async def __fetch_predictions(self) -> list[MBTAPrediction]:
|
|
106
|
+
jounrey = next(iter(self.journeys.values()))
|
|
107
|
+
jounrey.trip.id
|
|
108
|
+
params = {
|
|
109
|
+
'filter[trip]': jounrey.trip.id,
|
|
110
|
+
}
|
|
111
|
+
predictions = await super()._fetch_predictions(params)
|
|
112
|
+
return predictions
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async def __fetch_alerts(self) -> list[MBTAPrediction]:
|
|
116
|
+
jounrey = next(iter(self.journeys.values()))
|
|
117
|
+
jounrey.trip.id
|
|
118
|
+
params = {
|
|
119
|
+
'filter[trip]': jounrey.trip.id,
|
|
120
|
+
}
|
|
121
|
+
alerts = await super()._fetch_alerts(params)
|
|
122
|
+
return alerts
|
|
123
|
+
|
|
124
|
+
|
|
File without changes
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
from typing import Optional
|
|
3
|
-
|
|
4
|
-
class MBTAUtils:
|
|
5
|
-
|
|
6
|
-
ROUTE_TYPES= {
|
|
7
|
-
# 0: 'Light Rail', # Example: Green Line
|
|
8
|
-
# 1: 'Heavy Rail', # Example: Red Line
|
|
9
|
-
0: 'Subway',
|
|
10
|
-
1: 'Subway',
|
|
11
|
-
2: 'Commuter Rail',
|
|
12
|
-
3: 'Bus',
|
|
13
|
-
4: 'Ferry'
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
UNCERTAINTY = {
|
|
17
|
-
'60': 'Trip that has already started',
|
|
18
|
-
'120': 'Trip not started and a vehicle is awaiting departure at the origin',
|
|
19
|
-
'300': 'Vehicle has not yet been assigned to the trip',
|
|
20
|
-
'301': 'Vehicle appears to be stalled or significantly delayed',
|
|
21
|
-
'360': 'Trip not started and a vehicle is completing a previous trip'
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
@staticmethod
|
|
25
|
-
def get_route_type_desc_by_type_id(route_type: int) -> str:
|
|
26
|
-
"""Get a description of the route type."""
|
|
27
|
-
return MBTAUtils.ROUTE_TYPES.get(route_type, 'Unknown')
|
|
28
|
-
|
|
29
|
-
@staticmethod
|
|
30
|
-
def get_uncertainty_description(key: str) -> str:
|
|
31
|
-
return MBTAUtils.UNCERTAINTY.get(key, 'None')
|
|
32
|
-
|
|
33
|
-
@staticmethod
|
|
34
|
-
def time_to(time: Optional[datetime], now: datetime) -> Optional[float]:
|
|
35
|
-
if time is None:
|
|
36
|
-
return None
|
|
37
|
-
return (time - now).total_seconds()
|
|
38
|
-
|
|
39
|
-
@staticmethod
|
|
40
|
-
def calculate_time_difference(real_time: Optional[datetime], time: Optional[datetime]) -> Optional[float]:
|
|
41
|
-
if real_time is None or time is None:
|
|
42
|
-
return None
|
|
43
|
-
return (real_time - time).total_seconds()
|
|
44
|
-
|
|
45
|
-
@staticmethod
|
|
46
|
-
def parse_datetime(time_str: str) -> Optional[datetime]:
|
|
47
|
-
"""Parse a string in ISO 8601 format to a datetime object."""
|
|
48
|
-
if not isinstance(time_str, str):
|
|
49
|
-
return None
|
|
50
|
-
return datetime.fromisoformat(time_str)
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import aiohttp
|
|
2
|
-
import logging
|
|
3
|
-
|
|
4
|
-
from base_handler import BaseHandler
|
|
5
|
-
from journey import Journey
|
|
6
|
-
from mbta_route import MBTARoute
|
|
7
|
-
from mbta_trip import MBTATrip
|
|
8
|
-
from mbta_schedule import MBTASchedule
|
|
9
|
-
from mbta_prediction import MBTAPrediction
|
|
10
|
-
|
|
11
|
-
class TripHandler(BaseHandler):
|
|
12
|
-
"""Handler for managing a specific trip."""
|
|
13
|
-
|
|
14
|
-
def __init__(self, session: aiohttp.ClientSession, logger: logging.Logger, depart_from_name: str, arrive_at_name: str, trip_name: str, api_key:str = None ) :
|
|
15
|
-
super().__init__(session, logger, depart_from_name, arrive_at_name, api_key)
|
|
16
|
-
self.trip_name = trip_name
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
async def async_init(self):
|
|
20
|
-
await super()._async_init()
|
|
21
|
-
|
|
22
|
-
params = {
|
|
23
|
-
'filter[revenue]' :'REVENUE',
|
|
24
|
-
'filter[name]' : self.trip_name
|
|
25
|
-
}
|
|
26
|
-
trips: list[MBTATrip] = await super()._fetch_trips(params)
|
|
27
|
-
|
|
28
|
-
journey = Journey()
|
|
29
|
-
journey.trip = trips[0]
|
|
30
|
-
|
|
31
|
-
route: MBTARoute = await super()._fetch_route(journey.trip.route_id)
|
|
32
|
-
|
|
33
|
-
journey.route = route
|
|
34
|
-
self.journeys[trips[0].id] = journey
|
|
35
|
-
|
|
36
|
-
async def update(self) -> list[Journey]:
|
|
37
|
-
|
|
38
|
-
schedules = await self.__fetch_schedules()
|
|
39
|
-
await super()._process_schedules(schedules)
|
|
40
|
-
|
|
41
|
-
predictions = await self.__fetch_predictions()
|
|
42
|
-
await super()._process_predictions(predictions)
|
|
43
|
-
|
|
44
|
-
alerts = await self.__fetch_alerts()
|
|
45
|
-
super()._process_alerts(alerts)
|
|
46
|
-
return list(self.journeys.values())
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
async def __fetch_schedules(self) -> list[MBTASchedule]:
|
|
50
|
-
|
|
51
|
-
jounrey = next(iter(self.journeys.values()))
|
|
52
|
-
jounrey.trip.id
|
|
53
|
-
|
|
54
|
-
params = {
|
|
55
|
-
'filter[trip]': jounrey.trip.id,
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
schedules = await super()._fetch_schedules(params)
|
|
59
|
-
return schedules
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
async def __fetch_predictions(self) -> list[MBTAPrediction]:
|
|
63
|
-
|
|
64
|
-
jounrey = next(iter(self.journeys.values()))
|
|
65
|
-
jounrey.trip.id
|
|
66
|
-
|
|
67
|
-
params = {
|
|
68
|
-
'filter[trip]': jounrey.trip.id,
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
predictions = await super()._fetch_predictions(params)
|
|
72
|
-
return predictions
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
async def __fetch_alerts(self) -> list[MBTAPrediction]:
|
|
76
|
-
|
|
77
|
-
jounrey = next(iter(self.journeys.values()))
|
|
78
|
-
jounrey.trip.id
|
|
79
|
-
|
|
80
|
-
params = {
|
|
81
|
-
'filter[trip]': jounrey.trip.id,
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
alerts = await super()._fetch_alerts(params)
|
|
85
|
-
return alerts
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|