MBTAclient 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/PKG-INFO +1 -1
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/pyproject.toml +1 -1
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/MBTAclient.egg-info/PKG-INFO +1 -1
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/base_handler.py +130 -98
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/journey.py +20 -4
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/journey_stop.py +23 -14
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/journeys_handler.py +13 -9
- mbtaclient-0.2.1/src/main.py +171 -0
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/mbta_alert.py +1 -1
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/mbta_client.py +6 -6
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/mbta_prediction.py +1 -1
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/mbta_schedule.py +1 -1
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/mbta_stop.py +1 -1
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/mbta_trip.py +1 -1
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/mbta_utils.py +2 -7
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/trip_handler.py +24 -30
- mbtaclient-0.2.0/src/main.py +0 -173
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/LICENSE +0 -0
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/README.md +0 -0
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/setup.cfg +0 -0
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/MBTAclient.egg-info/SOURCES.txt +0 -0
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/MBTAclient.egg-info/dependency_links.txt +0 -0
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/MBTAclient.egg-info/top_level.txt +0 -0
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/__init__.py +0 -0
- {mbtaclient-0.2.0 → mbtaclient-0.2.1}/src/mbta_route.py +0 -0
|
@@ -17,14 +17,22 @@ from mbta_alert import MBTAAlert
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class BaseHandler:
|
|
20
|
-
"""Base class for handling MBTA
|
|
20
|
+
"""Base class for handling MBTA journeys."""
|
|
21
21
|
|
|
22
|
-
def __init__(self, session: aiohttp.ClientSession,
|
|
23
|
-
|
|
22
|
+
def __init__(self, session: aiohttp.ClientSession, logger: logging.Logger, depart_from_name: str , arrive_at_name: str, api_key: str = None) -> None:
|
|
24
23
|
|
|
25
|
-
self.
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
self.depart_from = {
|
|
25
|
+
'name' : depart_from_name,
|
|
26
|
+
'stops' : None,
|
|
27
|
+
'ids' : None
|
|
28
|
+
}
|
|
29
|
+
self.arrive_at = {
|
|
30
|
+
'name' : arrive_at_name,
|
|
31
|
+
'stops' : None,
|
|
32
|
+
'ids' : None
|
|
33
|
+
}
|
|
34
|
+
self.mbta_client = MBTAClient(session,logger, api_key=api_key)
|
|
35
|
+
|
|
28
36
|
self.journeys: dict[str, Journey] = {}
|
|
29
37
|
|
|
30
38
|
# Caches
|
|
@@ -33,14 +41,24 @@ class BaseHandler:
|
|
|
33
41
|
self._schedules_cache_date: Optional[date] = None
|
|
34
42
|
self._trip_cache: dict[str, MBTATrip] = {}
|
|
35
43
|
self._route_cache: dict[str, MBTARoute] = {}
|
|
36
|
-
|
|
44
|
+
|
|
45
|
+
# Logger
|
|
46
|
+
self.logger: logging.Logger = logger
|
|
47
|
+
|
|
48
|
+
def __repr__(self) -> str:
|
|
49
|
+
return (f"BaseHandler(depart_from_name={self.depart_from['name']}, arrive_at_name={self.arrive_at['name']})")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def _async_init(self):
|
|
53
|
+
stops = await self.__fetch_stops()
|
|
54
|
+
self.__process_stops(stops)
|
|
37
55
|
|
|
38
|
-
async def
|
|
56
|
+
async def __fetch_stops(self, params: dict = None) -> list[MBTAStop]:
|
|
39
57
|
"""Retrieve and process stops with a non-expiring cache."""
|
|
58
|
+
self.logger.debug("Fetching MBTA stops")
|
|
40
59
|
|
|
41
60
|
# Check if stops are already cached
|
|
42
61
|
if self._stops_cache is not None:
|
|
43
|
-
logging.info("Returning cached stops")
|
|
44
62
|
return self._stops_cache
|
|
45
63
|
|
|
46
64
|
# Cache is empty, so we fetch the stops from the API
|
|
@@ -51,70 +69,68 @@ class BaseHandler:
|
|
|
51
69
|
|
|
52
70
|
try:
|
|
53
71
|
stops: list[MBTAStop] = await self.mbta_client.list_stops(base_params)
|
|
54
|
-
|
|
72
|
+
self.logger.debug("Updating cached stops")
|
|
55
73
|
self._stops_cache = stops
|
|
56
74
|
return stops
|
|
57
75
|
|
|
58
76
|
except Exception as e:
|
|
59
|
-
|
|
77
|
+
self.logger.error(f"Error fetching stops: {e}")
|
|
60
78
|
traceback.print_exc()
|
|
61
79
|
return []
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
async def __get_stops_ids_by_name(self, stop_name: str ) -> list[str]:
|
|
69
|
-
stops = await self.__get_stops_by_name(stop_name)
|
|
70
|
-
stop_ids = [stop.id for stop in stops]
|
|
71
|
-
return stop_ids
|
|
72
|
-
|
|
73
|
-
async def _get_stops(self) -> list['MBTAStop']:
|
|
74
|
-
departure_stops = await self.__get_stops_by_name(self.depart_from_name)
|
|
75
|
-
arrival_stops = await self.__get_stops_by_name(self.arrive_at_name)
|
|
76
|
-
return departure_stops + arrival_stops
|
|
77
|
-
|
|
78
|
-
async def __get_stops_by_name(self, stop_name: str) -> list[MBTAStop]:
|
|
79
|
-
# Check if the stops for the stop_name are already cached
|
|
80
|
-
if stop_name.lower() in self._stop_name_cache:
|
|
81
|
-
logging.info(f"Returning cached stops for stop_name: {stop_name}")
|
|
82
|
-
return self._stop_name_cache[stop_name.lower()]
|
|
83
|
-
|
|
84
|
-
# Fetch all stops (assuming _fetch_stops is asynchronous)
|
|
85
|
-
stops = await self._fetch_stops()
|
|
86
|
-
|
|
87
|
-
# Filter stops by the given name
|
|
88
|
-
matching_stops = [stop for stop in stops if stop.name.lower() == stop_name.lower()]
|
|
89
|
-
|
|
90
|
-
# Cache the result
|
|
91
|
-
self._stop_name_cache[stop_name.lower()] = matching_stops
|
|
92
|
-
|
|
93
|
-
return matching_stops
|
|
94
|
-
|
|
95
|
-
async def _get_stop_by_id(self, stop_id: str) -> MBTAStop:
|
|
96
|
-
|
|
97
|
-
# Fetch all stops (assuming _fetch_stops is asynchronous)
|
|
98
|
-
stops = await self._fetch_stops()
|
|
99
|
-
|
|
100
|
-
# Find the first stop that matches the given stop_id
|
|
80
|
+
|
|
81
|
+
def __process_stops(self, stops: list[MBTAStop]):
|
|
82
|
+
depart_from_stops = []
|
|
83
|
+
depart_from_ids = []
|
|
84
|
+
arrive_at_stops = []
|
|
85
|
+
arrive_at_ids = []
|
|
101
86
|
for stop in stops:
|
|
87
|
+
if stop.name.lower() == self.depart_from['name'].lower():
|
|
88
|
+
depart_from_stops.append(stop)
|
|
89
|
+
depart_from_ids.append(stop.id)
|
|
90
|
+
if stop.name.lower() == self.arrive_at['name'].lower():
|
|
91
|
+
arrive_at_stops.append(stop)
|
|
92
|
+
arrive_at_ids.append(stop.id)
|
|
93
|
+
|
|
94
|
+
if len(depart_from_stops) == 0:
|
|
95
|
+
self.logger.error(f"Error fetching MBTA stop data for {self.depart_from['name']}")
|
|
96
|
+
raise ValueError(f"Invalid stop name: {self.depart_from['name']}")
|
|
97
|
+
|
|
98
|
+
if len(arrive_at_stops) == 0:
|
|
99
|
+
self.logger.error(f"Error fetching MBTA stop data for {self.arrive_at['name']}")
|
|
100
|
+
raise ValueError(f"Invalid stop name: {self.arrive_at['name']}")
|
|
101
|
+
else:
|
|
102
|
+
self.depart_from['stops'] = depart_from_stops
|
|
103
|
+
self.depart_from['ids'] = depart_from_ids
|
|
104
|
+
self.arrive_at['stops'] = arrive_at_stops
|
|
105
|
+
self.arrive_at['ids'] = arrive_at_ids
|
|
106
|
+
|
|
107
|
+
def __get_stop_by_id(self, stop_id: str) -> Optional[MBTAStop]:
|
|
108
|
+
for stop in (self.depart_from['stops'] + self.arrive_at['stops']):
|
|
102
109
|
if stop.id == stop_id:
|
|
103
110
|
return stop
|
|
104
|
-
|
|
105
|
-
# Return None if no matching stop is found
|
|
106
111
|
return None
|
|
107
|
-
|
|
112
|
+
|
|
113
|
+
def _get_stops_ids(self) -> list[str]:
|
|
114
|
+
return self.depart_from['ids'] + self.arrive_at['ids']
|
|
115
|
+
|
|
116
|
+
def __get_stops_ids_by_stop_type(self, stop_type: str) -> Optional[list[str]]:
|
|
117
|
+
if stop_type == 'departure':
|
|
118
|
+
return self.depart_from['ids']
|
|
119
|
+
elif stop_type == 'arrival':
|
|
120
|
+
return self.arrive_at['ids']
|
|
121
|
+
return None
|
|
122
|
+
|
|
108
123
|
async def _fetch_schedules(self, params: Optional[dict] = None) -> list[MBTASchedule]:
|
|
109
124
|
"""Retrieve and process schedules for today."""
|
|
110
|
-
|
|
125
|
+
self.logger.debug("Fetching MBTA schedules")
|
|
126
|
+
|
|
111
127
|
# Check if the cache is outdated
|
|
112
128
|
if self._schedules_cache_date is not None and self._schedules_cache_date == date.today():
|
|
113
|
-
|
|
129
|
+
self.logger.debug("Returning cached schedules")
|
|
114
130
|
return self._schedules_cache
|
|
115
131
|
|
|
116
132
|
base_params = {
|
|
117
|
-
'filter[stop]': ','.join(
|
|
133
|
+
'filter[stop]': ','.join(self._get_stops_ids()),
|
|
118
134
|
'sort': 'departure_time'
|
|
119
135
|
}
|
|
120
136
|
if params is not None:
|
|
@@ -123,15 +139,17 @@ class BaseHandler:
|
|
|
123
139
|
try:
|
|
124
140
|
schedules: list[MBTASchedule] = await self.mbta_client.list_schedules(base_params)
|
|
125
141
|
# Update the cache with new data and timestamp
|
|
142
|
+
self.logger.debug("Updating cached schedules")
|
|
126
143
|
self._schedules_cache = schedules
|
|
127
144
|
self._schedules_cache_date = date.today()
|
|
128
145
|
return schedules
|
|
129
146
|
except Exception as e:
|
|
130
|
-
|
|
147
|
+
self.logger.error(f"Error fetching schedules: {e}")
|
|
131
148
|
traceback.print_exc()
|
|
132
149
|
return []
|
|
133
150
|
|
|
134
151
|
async def _process_schedules(self, schedules: list[MBTASchedule]):
|
|
152
|
+
self.logger.debug("Processing schedules")
|
|
135
153
|
|
|
136
154
|
for schedule in schedules:
|
|
137
155
|
|
|
@@ -142,21 +160,22 @@ class BaseHandler:
|
|
|
142
160
|
# add the journey to the journeys dict using the trip_id as key
|
|
143
161
|
self.journeys[schedule.trip_id] = journey
|
|
144
162
|
|
|
145
|
-
stop: MBTAStop =
|
|
146
|
-
|
|
147
|
-
|
|
163
|
+
stop: MBTAStop = self.__get_stop_by_id(schedule.stop_id)
|
|
164
|
+
departure_stops_ids = self.__get_stops_ids_by_stop_type('departure')
|
|
165
|
+
arrival_stops_ids = self.__get_stops_ids_by_stop_type('arrival')
|
|
148
166
|
|
|
149
|
-
if schedule.stop_id in
|
|
167
|
+
if schedule.stop_id in departure_stops_ids:
|
|
150
168
|
self.journeys[schedule.trip_id].add_stop('departure',schedule,stop,'SCHEDULED')
|
|
151
|
-
elif schedule.stop_id in
|
|
169
|
+
elif schedule.stop_id in arrival_stops_ids:
|
|
152
170
|
self.journeys[schedule.trip_id].add_stop('arrival',schedule, stop,'SCHEDULED')
|
|
153
171
|
|
|
154
172
|
|
|
155
173
|
async def _fetch_predictions(self, params: str = None) -> list[MBTAPrediction]:
|
|
156
174
|
"""Retrieve and process predictions based on the provided stop IDs and route ID."""
|
|
157
|
-
|
|
175
|
+
self.logger.debug("Fetching MBTA predictions")
|
|
176
|
+
|
|
158
177
|
base_params = {
|
|
159
|
-
'filter[stop]': ','.join(
|
|
178
|
+
'filter[stop]': ','.join(self._get_stops_ids()),
|
|
160
179
|
'filter[revenue]': 'REVENUE',
|
|
161
180
|
'sort': 'departure_time'
|
|
162
181
|
}
|
|
@@ -169,11 +188,12 @@ class BaseHandler:
|
|
|
169
188
|
return predictions
|
|
170
189
|
|
|
171
190
|
except Exception as e:
|
|
172
|
-
|
|
191
|
+
self.logger.error(f"Error fetching predictions: {e}")
|
|
173
192
|
traceback.print_exc()
|
|
174
193
|
|
|
175
194
|
async def _process_predictions (self, predictions: list[MBTAPrediction]):
|
|
176
|
-
|
|
195
|
+
self.logger.debug("Processing predictions")
|
|
196
|
+
|
|
177
197
|
for prediction in predictions:
|
|
178
198
|
|
|
179
199
|
# if the trip of the prediciton is not in the journeys dict
|
|
@@ -183,29 +203,27 @@ class BaseHandler:
|
|
|
183
203
|
# add the journey to the journeys dict using the trip_id as key
|
|
184
204
|
self.journeys[prediction.trip_id] = journey
|
|
185
205
|
|
|
186
|
-
stop: MBTAStop =
|
|
187
|
-
|
|
188
|
-
|
|
206
|
+
stop: MBTAStop = self.__get_stop_by_id(prediction.stop_id)
|
|
207
|
+
departure_stops_ids = self.__get_stops_ids_by_stop_type('departure')
|
|
208
|
+
arrival_stops_ids = self.__get_stops_ids_by_stop_type('arrival')
|
|
189
209
|
|
|
190
210
|
if prediction.schedule_relationship is None:
|
|
191
|
-
prediction.schedule_relationship = '
|
|
211
|
+
prediction.schedule_relationship = 'PREDICTED'
|
|
192
212
|
|
|
193
213
|
# if the prediction stop id is in the departure stop ids
|
|
194
|
-
if prediction.stop_id in
|
|
195
|
-
|
|
214
|
+
if prediction.stop_id in departure_stops_ids:
|
|
196
215
|
self.journeys[prediction.trip_id].add_stop('departure',prediction,stop,prediction.schedule_relationship)
|
|
197
|
-
|
|
198
216
|
# if the prediction stop id is in the arrival stop ids
|
|
199
|
-
if prediction.stop_id in
|
|
200
|
-
|
|
217
|
+
if prediction.stop_id in arrival_stops_ids:
|
|
201
218
|
self.journeys[prediction.trip_id].add_stop('arrival',prediction,stop,prediction.schedule_relationship)
|
|
202
219
|
|
|
203
220
|
async def _fetch_alerts(self,params: str = None) -> list[MBTAAlert]:
|
|
204
221
|
"""Retrieve and associate alerts with the relevant journeys."""
|
|
205
|
-
|
|
222
|
+
self.logger.debug("Fetching MBTA alerts")
|
|
223
|
+
|
|
206
224
|
# Prepare filter parameters
|
|
207
225
|
base_params = {
|
|
208
|
-
'filter[stop]': ','.join(
|
|
226
|
+
'filter[stop]': ','.join(self._get_stops_ids()),
|
|
209
227
|
'filter[activity]': 'BOARD,EXIT,RIDE',
|
|
210
228
|
'filter[datetime]': 'NOW'
|
|
211
229
|
}
|
|
@@ -217,23 +235,24 @@ class BaseHandler:
|
|
|
217
235
|
alerts: list[MBTAAlert] = await self.mbta_client.list_alerts(base_params)
|
|
218
236
|
return alerts
|
|
219
237
|
except Exception as e:
|
|
220
|
-
|
|
238
|
+
self.logger.error(f"Error fetching alerts: {e}")
|
|
221
239
|
traceback.print_exc()
|
|
222
240
|
|
|
223
241
|
def _process_alerts(self, alerts: list[MBTAAlert]):
|
|
242
|
+
self.logger.debug("Processing alerts")
|
|
224
243
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
244
|
+
for alert in alerts:
|
|
245
|
+
|
|
246
|
+
# Iterate through each journey and associate relevant alerts
|
|
247
|
+
for journey in self.journeys.values():
|
|
248
|
+
if alert in journey.alerts:
|
|
249
|
+
continue # Skip if alert is already associated
|
|
231
250
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
251
|
+
# Check if the alert is relevant to the journey
|
|
252
|
+
if self.__is_alert_relevant(alert, journey):
|
|
253
|
+
journey.alerts.append(alert)
|
|
235
254
|
|
|
236
|
-
def
|
|
255
|
+
def __is_alert_relevant(self, alert: MBTAAlert, journey: Journey) -> bool:
|
|
237
256
|
"""Check if an alert is relevant to a given journey."""
|
|
238
257
|
for informed_entity in alert.informed_entities:
|
|
239
258
|
# Check informed entity stop relevance
|
|
@@ -246,12 +265,12 @@ class BaseHandler:
|
|
|
246
265
|
if informed_entity.get('route') and informed_entity['route'] != journey.route.id:
|
|
247
266
|
continue
|
|
248
267
|
# Check activities relevance based on departure or arrival
|
|
249
|
-
if not self.
|
|
268
|
+
if not self.__is_alert_activity_relevant(informed_entity, journey):
|
|
250
269
|
continue
|
|
251
270
|
return True # Alert is relevant if all checks pass
|
|
252
271
|
return False # Alert is not relevant
|
|
253
272
|
|
|
254
|
-
def
|
|
273
|
+
def __is_alert_activity_relevant(self, informed_entity: dict, journey: Journey) -> bool:
|
|
255
274
|
"""Check if the activities of the informed entity are relevant to the journey."""
|
|
256
275
|
departure_stop_id = journey.get_stop_id('departure')
|
|
257
276
|
arrival_stop_id = journey.get_stop_id('arrival')
|
|
@@ -262,42 +281,55 @@ class BaseHandler:
|
|
|
262
281
|
return False
|
|
263
282
|
return True
|
|
264
283
|
|
|
265
|
-
async def _fetch_trip(self, trip_id: str, params: dict = None) -> MBTATrip:
|
|
284
|
+
async def _fetch_trip(self, trip_id: str, params: dict = None) -> Optional[MBTATrip]:
|
|
266
285
|
"""Retrieve and process a trip with a non-expiring cache based on trip_id."""
|
|
286
|
+
self.logger.debug(f"Fetching MBTA trip: {trip_id} ")
|
|
267
287
|
|
|
268
288
|
# Check if the trip is already cached
|
|
269
289
|
if trip_id in self._trip_cache:
|
|
270
|
-
|
|
290
|
+
self.logger.debug(f"Returning cached trip: {trip_id}")
|
|
271
291
|
return self._trip_cache[trip_id]
|
|
272
292
|
|
|
273
293
|
# Trip is not in the cache, so fetch it from the API
|
|
274
294
|
try:
|
|
275
295
|
trip: MBTATrip = await self.mbta_client.get_trip(trip_id, params)
|
|
276
|
-
|
|
296
|
+
self.logger.debug(f"Updating cached trip: {trip_id}")
|
|
277
297
|
self._trip_cache[trip_id] = trip
|
|
278
298
|
return trip
|
|
279
299
|
|
|
280
300
|
except Exception as e:
|
|
281
|
-
|
|
301
|
+
self.logger.error(f"Error fetching trip: {e}")
|
|
282
302
|
traceback.print_exc()
|
|
283
303
|
return None
|
|
284
304
|
|
|
285
|
-
async def _fetch_route(self, route_id: str, params: dict = None) -> MBTARoute:
|
|
305
|
+
async def _fetch_route(self, route_id: str, params: dict = None) -> Optional[MBTARoute]:
|
|
286
306
|
"""Retrieve and process a route with a non-expiring cache based on route_id."""
|
|
307
|
+
self.logger.debug(f"Fetching MBTA route: {route_id} ")
|
|
287
308
|
|
|
288
309
|
# Check if the trip is already cached
|
|
289
310
|
if route_id in self._route_cache:
|
|
290
|
-
logging.info(f"Returning cached trip for route_id: {route_id}")
|
|
291
311
|
return self._route_cache[route_id]
|
|
292
312
|
|
|
293
313
|
# Trip is not in the cache, so fetch it from the API
|
|
294
314
|
try:
|
|
295
315
|
route: MBTARoute = await self.mbta_client.get_route(route_id, params)
|
|
296
316
|
# Update the cache
|
|
317
|
+
self.logger.debug(f"Updating cached route: {route_id}")
|
|
297
318
|
self._route_cache[route_id] = route
|
|
298
319
|
return route
|
|
299
320
|
|
|
300
321
|
except Exception as e:
|
|
301
|
-
|
|
322
|
+
self.logger.error(f"Error fetching route: {e}")
|
|
302
323
|
traceback.print_exc()
|
|
303
|
-
return None
|
|
324
|
+
return None
|
|
325
|
+
|
|
326
|
+
async def _fetch_trips(self, params: dict = None) -> Optional[MBTARoute]:
|
|
327
|
+
self.logger.debug("Fetching MBTA trips")
|
|
328
|
+
try:
|
|
329
|
+
trips: list[MBTATrip] = await self.mbta_client.list_trips(params)
|
|
330
|
+
return trips
|
|
331
|
+
except Exception as e:
|
|
332
|
+
self.logger.error(f"Error fetching route: {e}")
|
|
333
|
+
traceback.print_exc()
|
|
334
|
+
return None
|
|
335
|
+
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Union, Optional
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
|
|
4
4
|
from journey_stop import JourneyStop
|
|
5
|
+
from mbta_schedule import MBTASchedule
|
|
6
|
+
from mbta_prediction import MBTAPrediction
|
|
5
7
|
from mbta_stop import MBTAStop
|
|
6
8
|
from mbta_route import MBTARoute
|
|
7
9
|
from mbta_trip import MBTATrip
|
|
8
10
|
from mbta_alert import MBTAAlert
|
|
11
|
+
from mbta_utils import MBTAUtils
|
|
9
12
|
|
|
10
13
|
class Journey:
|
|
11
14
|
"""A class to manage a journey with multiple stops."""
|
|
@@ -15,6 +18,7 @@ class Journey:
|
|
|
15
18
|
Initialize a Journey with optional route, trip, and alert information.
|
|
16
19
|
Departure and arrival stops are also initialized as None.
|
|
17
20
|
"""
|
|
21
|
+
self.duration = None
|
|
18
22
|
self.route: Optional[MBTARoute] = None
|
|
19
23
|
self.trip: Optional[MBTATrip] = None
|
|
20
24
|
self.alerts: list[MBTAAlert] = []
|
|
@@ -24,9 +28,9 @@ class Journey:
|
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
def __repr__(self) -> str:
|
|
27
|
-
return f"Journey(
|
|
31
|
+
return f"Journey(depart_from={self.stops['departure']}, arrive_at={self.stops['arrival']})"
|
|
28
32
|
|
|
29
|
-
def add_stop(self, stop_type: str, scheduling_data, stop: MBTAStop, status) -> None:
|
|
33
|
+
def add_stop(self, stop_type: str, scheduling_data: Union[MBTASchedule,MBTAPrediction], stop: MBTAStop, status) -> None:
|
|
30
34
|
"""Add or update a stop to the journey."""
|
|
31
35
|
|
|
32
36
|
if self.stops[stop_type] is None:
|
|
@@ -52,7 +56,9 @@ class Journey:
|
|
|
52
56
|
status
|
|
53
57
|
)
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
if self.stops['departure'] and self.stops['arrival']:
|
|
60
|
+
self.duration = MBTAUtils.calculate_time_difference(self.stops['arrival'].get_time(),self.stops['departure'].get_time())
|
|
61
|
+
|
|
56
62
|
def get_stop(self, stop_type: str) -> Optional[JourneyStop]:
|
|
57
63
|
"""Return the specified stop or None if not set."""
|
|
58
64
|
if stop_type in self.stops:
|
|
@@ -107,6 +113,11 @@ class Journey:
|
|
|
107
113
|
trip_direction = self.trip.direction_id
|
|
108
114
|
return self.route.direction_names[trip_direction]
|
|
109
115
|
return None
|
|
116
|
+
|
|
117
|
+
def get_trip_duration(self) -> Optional[str]:
|
|
118
|
+
if self.duration:
|
|
119
|
+
return self.duration
|
|
120
|
+
return None
|
|
110
121
|
|
|
111
122
|
def get_stop_name(self, stop_type: str) -> Optional[str]:
|
|
112
123
|
"""Return the stop name for the specified stop type."""
|
|
@@ -128,6 +139,11 @@ class Journey:
|
|
|
128
139
|
stop = self.get_stop(stop_type)
|
|
129
140
|
return stop.get_delay() if stop else None
|
|
130
141
|
|
|
142
|
+
def get_stop_status(self, stop_type: str) -> Optional[float]:
|
|
143
|
+
"""Return the stop delay for the specified stop type."""
|
|
144
|
+
stop = self.get_stop(stop_type)
|
|
145
|
+
return stop.status if stop else None
|
|
146
|
+
|
|
131
147
|
def get_stop_time_to(self, stop_type: str) -> Optional[float]:
|
|
132
148
|
"""Return the time to for the specified stop type."""
|
|
133
149
|
stop = self.get_stop(stop_type)
|
|
@@ -15,32 +15,41 @@ class JourneyStop:
|
|
|
15
15
|
|
|
16
16
|
self.arrival_time = MBTAUtils.parse_datetime(arrival_time)
|
|
17
17
|
self.real_arrival_time = None
|
|
18
|
-
self.arrival_delay =
|
|
18
|
+
self.arrival_delay = None
|
|
19
19
|
|
|
20
20
|
self.departure_time = MBTAUtils.parse_datetime(departure_time)
|
|
21
21
|
self.real_departure_time = None
|
|
22
|
-
self.departure_delay =
|
|
22
|
+
self.departure_delay = None
|
|
23
23
|
|
|
24
24
|
self.status = status
|
|
25
25
|
self.stop_sequence = stop_sequence
|
|
26
26
|
|
|
27
27
|
def __repr__(self) -> str:
|
|
28
|
-
return (f"JourneyStop(stop={self.stop.
|
|
28
|
+
return (f"JourneyStop(stop={self.stop.name})")
|
|
29
29
|
|
|
30
|
-
def update_stop(self, stop: MBTAStop, arrival_time: str, departure_time: str, stop_sequence:str, status: str) -> None:
|
|
30
|
+
def update_stop(self, stop: MBTAStop, arrival_time: str, departure_time: str, stop_sequence: str, status: str) -> None:
|
|
31
31
|
"""Update the stop details, including real arrival and departure times, uncertainties, and delays."""
|
|
32
|
-
|
|
33
|
-
self.stop
|
|
32
|
+
|
|
33
|
+
self.stop = stop
|
|
34
34
|
self.stop_sequence = stop_sequence
|
|
35
35
|
self.status = status
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
self.
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
|
|
37
|
+
if arrival_time is None and departure_time is None:
|
|
38
|
+
self.arrival_time = None
|
|
39
|
+
self.real_arrival_time = None
|
|
40
|
+
self.arrival_delay = None
|
|
41
|
+
self.departure_time = None
|
|
42
|
+
self.real_departure_time = None
|
|
43
|
+
self.departure_delay = None
|
|
44
|
+
else:
|
|
45
|
+
if arrival_time is not None:
|
|
46
|
+
self.real_arrival_time = MBTAUtils.parse_datetime(arrival_time)
|
|
47
|
+
if self.arrival_time is not None:
|
|
48
|
+
self.arrival_delay = MBTAUtils.calculate_time_difference(self.real_arrival_time, self.arrival_time)
|
|
49
|
+
if departure_time is not None:
|
|
50
|
+
self.real_departure_time = MBTAUtils.parse_datetime(departure_time)
|
|
51
|
+
if self.departure_time is not None:
|
|
52
|
+
self.departure_delay = MBTAUtils.calculate_time_difference(self.real_departure_time, self.departure_time)
|
|
44
53
|
|
|
45
54
|
def get_time(self) -> Optional[datetime]:
|
|
46
55
|
"""Return the most relevant time for the stop."""
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import aiohttp
|
|
2
|
+
import logging
|
|
2
3
|
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
|
|
@@ -12,17 +13,20 @@ from mbta_schedule import MBTASchedule
|
|
|
12
13
|
class JourneysHandler(BaseHandler):
|
|
13
14
|
"""Handler for managing a specific journey."""
|
|
14
15
|
|
|
15
|
-
def __init__(self, session: aiohttp.ClientSession,
|
|
16
|
-
super().__init__(session,
|
|
16
|
+
def __init__(self, session: aiohttp.ClientSession, logger: logging.Logger, depart_from_name: str, arrive_at_name: str, max_journeys: int, api_key:str = None) :
|
|
17
|
+
super().__init__(session, logger, depart_from_name,arrive_at_name, api_key)
|
|
17
18
|
self.max_journeys = max_journeys
|
|
18
19
|
|
|
19
|
-
async def
|
|
20
|
+
async def async_init(self):
|
|
21
|
+
await super()._async_init()
|
|
22
|
+
|
|
23
|
+
async def update(self) -> list[Journey]:
|
|
20
24
|
|
|
21
25
|
schedules = await self.__fetch_schedules()
|
|
22
|
-
await
|
|
26
|
+
await super()._process_schedules(schedules)
|
|
23
27
|
|
|
24
28
|
predictions = await self._fetch_predictions()
|
|
25
|
-
await
|
|
29
|
+
await super()._process_predictions(predictions)
|
|
26
30
|
|
|
27
31
|
self.__sort_and_clean()
|
|
28
32
|
|
|
@@ -31,7 +35,7 @@ class JourneysHandler(BaseHandler):
|
|
|
31
35
|
await self.__fetch_routes()
|
|
32
36
|
|
|
33
37
|
alerts = await self._fetch_alerts()
|
|
34
|
-
|
|
38
|
+
super()._process_alerts(alerts)
|
|
35
39
|
|
|
36
40
|
return list(self.journeys.values())
|
|
37
41
|
|
|
@@ -40,7 +44,7 @@ class JourneysHandler(BaseHandler):
|
|
|
40
44
|
now = datetime.now().astimezone()
|
|
41
45
|
|
|
42
46
|
params = {
|
|
43
|
-
'filter[stop]': ','.join(
|
|
47
|
+
'filter[stop]': ','.join(super()._get_stops_ids()),
|
|
44
48
|
'filter[min_time]': now.strftime('%H:%M'),
|
|
45
49
|
}
|
|
46
50
|
|
|
@@ -74,12 +78,12 @@ class JourneysHandler(BaseHandler):
|
|
|
74
78
|
async def __fetch_trips(self):
|
|
75
79
|
"""Retrieve trip details for each journey."""
|
|
76
80
|
for trip_id, journey in self.journeys.items():
|
|
77
|
-
trip: MBTATrip = await
|
|
81
|
+
trip: MBTATrip = await super()._fetch_trip(trip_id)
|
|
78
82
|
journey.trip = trip
|
|
79
83
|
|
|
80
84
|
async def __fetch_routes(self):
|
|
81
85
|
"""Retrieve route details for each journey."""
|
|
82
86
|
for journey in self.journeys.values():
|
|
83
87
|
if journey.trip and journey.trip.route_id:
|
|
84
|
-
route: MBTARoute = await
|
|
88
|
+
route: MBTARoute = await super()._fetch_route(journey.trip.route_id)
|
|
85
89
|
journey.route = route
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
import logging
|
|
3
|
+
from trip_handler import TripHandler
|
|
4
|
+
from journeys_handler import JourneysHandler
|
|
5
|
+
from journey import Journey
|
|
6
|
+
|
|
7
|
+
_LOGGER = logging.getLogger("MBTAClient")
|
|
8
|
+
|
|
9
|
+
logging.basicConfig(level=logging.INFO, # Set the logging level to DEBUG
|
|
10
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
11
|
+
|
|
12
|
+
API_KEY = ''
|
|
13
|
+
MAX_JOURNEYS = 1
|
|
14
|
+
|
|
15
|
+
# DEPART_FROM = 'South Station'
|
|
16
|
+
# ARRIVE_AT = 'Wellesley Square'
|
|
17
|
+
|
|
18
|
+
# DEPART_FROM = 'Wellesley Square'
|
|
19
|
+
# ARRIVE_AT = 'South Station'
|
|
20
|
+
|
|
21
|
+
# DEPART_FROM = 'South Station'
|
|
22
|
+
# ARRIVE_AT = 'Braintree'
|
|
23
|
+
|
|
24
|
+
# DEPART_FROM = 'Copley'
|
|
25
|
+
# ARRIVE_AT = 'Park Street'
|
|
26
|
+
|
|
27
|
+
# DEPART_FROM = 'North Station'
|
|
28
|
+
# ARRIVE_AT = 'Swampscott'
|
|
29
|
+
|
|
30
|
+
# DEPART_FROM = 'Dorchester Ave @ Valley Rd'
|
|
31
|
+
# ARRIVE_AT = 'River St @ Standard St'
|
|
32
|
+
|
|
33
|
+
# DEPART_FROM = 'Back Bay'
|
|
34
|
+
# ARRIVE_AT = 'Huntington Ave @ Opera Pl'
|
|
35
|
+
|
|
36
|
+
# DEPART_FROM = 'Charlestown Navy Yard'
|
|
37
|
+
# ARRIVE_AT = 'Long Wharf (South)'
|
|
38
|
+
|
|
39
|
+
# DEPART_FROM = 'North Billerica'
|
|
40
|
+
# ARRIVE_AT = 'North Station'
|
|
41
|
+
|
|
42
|
+
# DEPART_FROM = 'Back Bay'
|
|
43
|
+
# ARRIVE_AT = 'South Station'
|
|
44
|
+
|
|
45
|
+
# DEPART_FROM = 'Pemberton Point'
|
|
46
|
+
# ARRIVE_AT = 'Summer St from Cushing Way to Water St (FLAG)'
|
|
47
|
+
|
|
48
|
+
TRIP = '518'
|
|
49
|
+
DEPART_FROM = 'Wellesley Square'
|
|
50
|
+
ARRIVE_AT = 'South Station'
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def print_journey(journey: Journey):
|
|
54
|
+
route_type = journey.get_route_type()
|
|
55
|
+
|
|
56
|
+
# if subway or ferry
|
|
57
|
+
if route_type in [0, 1, 4]:
|
|
58
|
+
|
|
59
|
+
_LOGGER.info("###########")
|
|
60
|
+
_LOGGER.info("Line: %s", journey.get_route_long_name())
|
|
61
|
+
_LOGGER.info("Type: %s", journey.get_route_description())
|
|
62
|
+
_LOGGER.info("Color: %s", journey.get_route_color())
|
|
63
|
+
_LOGGER.info("**********")
|
|
64
|
+
_LOGGER.info("Direction: %s to %s", journey.get_trip_direction(), journey.get_trip_destination())
|
|
65
|
+
_LOGGER.info("Destination: %s", journey.get_trip_headsign())
|
|
66
|
+
_LOGGER.info("Duration: %s", journey.get_trip_duration())
|
|
67
|
+
_LOGGER.info("**********")
|
|
68
|
+
_LOGGER.info("Departure Station: %s", journey.get_stop_name('departure'))
|
|
69
|
+
_LOGGER.info("Departure Platform: %s", journey.get_platform_name('departure'))
|
|
70
|
+
_LOGGER.info("Departure Time: %s", journey.get_stop_time('departure'))
|
|
71
|
+
_LOGGER.info("Departure Delay: %s", journey.get_stop_delay('departure'))
|
|
72
|
+
_LOGGER.info("Departure Time To: %s", journey.get_stop_time_to('departure'))
|
|
73
|
+
_LOGGER.info("%s", journey.get_stop_status('departure'))
|
|
74
|
+
_LOGGER.info("**********")
|
|
75
|
+
_LOGGER.info("Arrival Station: %s", journey.get_stop_name('arrival'))
|
|
76
|
+
_LOGGER.info("Arrival Platform: %s", journey.get_platform_name('arrival'))
|
|
77
|
+
_LOGGER.info("Arrival Time: %s", journey.get_stop_time('arrival'))
|
|
78
|
+
_LOGGER.info("Arrival Delay: %s", journey.get_stop_delay('arrival'))
|
|
79
|
+
_LOGGER.info("Arrival Time To: %s", journey.get_stop_time_to('arrival'))
|
|
80
|
+
_LOGGER.info("%s", journey.get_stop_status('arrival'))
|
|
81
|
+
_LOGGER.info("**********")
|
|
82
|
+
for j in range(len(journey.alerts)):
|
|
83
|
+
_LOGGER.info("Alert: %s", journey.get_alert_header(j))
|
|
84
|
+
|
|
85
|
+
# if train
|
|
86
|
+
elif route_type == 2:
|
|
87
|
+
|
|
88
|
+
_LOGGER.info("###########")
|
|
89
|
+
_LOGGER.info("Line: %s", journey.get_route_long_name())
|
|
90
|
+
_LOGGER.info("Type: %s", journey.get_route_description())
|
|
91
|
+
_LOGGER.info("Color: %s", journey.get_route_color())
|
|
92
|
+
_LOGGER.info("Train Number: %s", journey.get_trip_name())
|
|
93
|
+
_LOGGER.info("**********")
|
|
94
|
+
_LOGGER.info("Direction: %s to %s", journey.get_trip_direction(), journey.get_trip_destination())
|
|
95
|
+
_LOGGER.info("Destination: %s", journey.get_trip_headsign())
|
|
96
|
+
_LOGGER.info("Duration: %s", journey.get_trip_duration())
|
|
97
|
+
_LOGGER.info("**********")
|
|
98
|
+
_LOGGER.info("Departure Station: %s", journey.get_stop_name('departure'))
|
|
99
|
+
_LOGGER.info("Departure Platform: %s", journey.get_platform_name('departure'))
|
|
100
|
+
_LOGGER.info("Departure Time: %s", journey.get_stop_time('departure'))
|
|
101
|
+
_LOGGER.info("Departure Delay: %s", journey.get_stop_delay('departure'))
|
|
102
|
+
_LOGGER.info("Departure Time To: %s", journey.get_stop_time_to('departure'))
|
|
103
|
+
_LOGGER.info("%s", journey.get_stop_status('departure'))
|
|
104
|
+
_LOGGER.info("**********")
|
|
105
|
+
_LOGGER.info("Arrival Station: %s", journey.get_stop_name('arrival'))
|
|
106
|
+
_LOGGER.info("Arrival Platform: %s", journey.get_platform_name('arrival'))
|
|
107
|
+
_LOGGER.info("Arrival Time: %s", journey.get_stop_time('arrival'))
|
|
108
|
+
_LOGGER.info("Arrival Delay: %s", journey.get_stop_delay('arrival'))
|
|
109
|
+
_LOGGER.info("Arrival Time To: %s", journey.get_stop_time_to('arrival'))
|
|
110
|
+
_LOGGER.info("%s", journey.get_stop_status('arrival'))
|
|
111
|
+
_LOGGER.info("**********")
|
|
112
|
+
for j in range(len(journey.alerts)):
|
|
113
|
+
_LOGGER.info("Alert: %s", journey.get_alert_header(j))
|
|
114
|
+
|
|
115
|
+
# if bus
|
|
116
|
+
elif route_type == 3:
|
|
117
|
+
|
|
118
|
+
_LOGGER.info("###########")
|
|
119
|
+
_LOGGER.info("Line: %s", journey.get_route_short_name())
|
|
120
|
+
_LOGGER.info("Type: %s", journey.get_route_description())
|
|
121
|
+
_LOGGER.info("Color: %s", journey.get_route_color())
|
|
122
|
+
_LOGGER.info("**********")
|
|
123
|
+
_LOGGER.info("Direction: %s to %s", journey.get_trip_direction(), journey.get_trip_destination())
|
|
124
|
+
_LOGGER.info("Destination: %s", journey.get_trip_headsign())
|
|
125
|
+
_LOGGER.info("Duration: %s", journey.get_trip_duration())
|
|
126
|
+
_LOGGER.info("**********")
|
|
127
|
+
_LOGGER.info("Departure Stop: %s", journey.get_stop_name('departure'))
|
|
128
|
+
_LOGGER.info("Departure Time: %s", journey.get_stop_time('departure'))
|
|
129
|
+
_LOGGER.info("Departure Delay: %s", journey.get_stop_delay('departure'))
|
|
130
|
+
_LOGGER.info("Departure Time To: %s", journey.get_stop_time_to('departure'))
|
|
131
|
+
_LOGGER.info("%s", journey.get_stop_status('departure'))
|
|
132
|
+
_LOGGER.info("**********")
|
|
133
|
+
_LOGGER.info("Arrival Stop: %s", journey.get_stop_name('arrival'))
|
|
134
|
+
_LOGGER.info("Arrival Time: %s", journey.get_stop_time('arrival'))
|
|
135
|
+
_LOGGER.info("Arrival Delay: %s", journey.get_stop_delay('arrival'))
|
|
136
|
+
_LOGGER.info("Arrival Time To: %s", journey.get_stop_time_to('arrival'))
|
|
137
|
+
_LOGGER.info("%s", journey.get_stop_status('arrival'))
|
|
138
|
+
_LOGGER.info("**********")
|
|
139
|
+
for j in range(len(journey.alerts)):
|
|
140
|
+
_LOGGER.info("Alert: %s", journey.get_alert_header(j))
|
|
141
|
+
|
|
142
|
+
else:
|
|
143
|
+
_LOGGER.error('ARGH!')
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def main():
|
|
147
|
+
async with aiohttp.ClientSession() as session:
|
|
148
|
+
|
|
149
|
+
trip_hadler = TripHandler(session, _LOGGER, DEPART_FROM, ARRIVE_AT, TRIP, API_KEY)
|
|
150
|
+
|
|
151
|
+
await trip_hadler.async_init()
|
|
152
|
+
|
|
153
|
+
trips = await trip_hadler.update()
|
|
154
|
+
|
|
155
|
+
for trip in trips:
|
|
156
|
+
print_journey(trip)
|
|
157
|
+
|
|
158
|
+
journeys_handler = JourneysHandler(session, _LOGGER, DEPART_FROM, ARRIVE_AT, MAX_JOURNEYS, API_KEY)
|
|
159
|
+
|
|
160
|
+
await journeys_handler.async_init()
|
|
161
|
+
|
|
162
|
+
journeys = await journeys_handler.update()
|
|
163
|
+
|
|
164
|
+
for journey in journeys:
|
|
165
|
+
print_journey(journey)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# Run the main function
|
|
170
|
+
import asyncio
|
|
171
|
+
asyncio.run(main())
|
|
@@ -32,7 +32,7 @@ class MBTAAlert:
|
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
def __repr__(self) -> str:
|
|
35
|
-
return (f"MBTAalert(id={self.alert_id}
|
|
35
|
+
return (f"MBTAalert(id={self.alert_id})")
|
|
36
36
|
|
|
37
37
|
def get_informed_stops(self) -> list[str]:
|
|
38
38
|
"""Retrieve a list of unique stops from informed entities."""
|
|
@@ -23,10 +23,10 @@ ENDPOINTS = {
|
|
|
23
23
|
class MBTAClient:
|
|
24
24
|
"""Class to interact with the MBTA v3 API."""
|
|
25
25
|
|
|
26
|
-
def __init__(self, session: aiohttp.ClientSession, api_key: Optional[str] = None)
|
|
26
|
+
def __init__(self, session: aiohttp.ClientSession, logger: logging.Logger, api_key: Optional[str] = None)-> None:
|
|
27
27
|
self._session = session
|
|
28
28
|
self._api_key = api_key
|
|
29
|
-
|
|
29
|
+
self.logger: logging.Logger = logger
|
|
30
30
|
|
|
31
31
|
async def get_route(self, id: str, params: Optional[dict[str, Any]] = None) -> MBTARoute:
|
|
32
32
|
"""Get a route by its ID."""
|
|
@@ -82,7 +82,7 @@ class MBTAClient:
|
|
|
82
82
|
raise ValueError("Unexpected response format")
|
|
83
83
|
return data
|
|
84
84
|
except Exception as error:
|
|
85
|
-
|
|
85
|
+
self.logger.error(f"Error fetching data: {error}")
|
|
86
86
|
raise
|
|
87
87
|
|
|
88
88
|
async def request(
|
|
@@ -106,13 +106,13 @@ class MBTAClient:
|
|
|
106
106
|
return response
|
|
107
107
|
|
|
108
108
|
except ClientConnectionError as error:
|
|
109
|
-
|
|
109
|
+
self.logger.error(f"Connection error: {error}")
|
|
110
110
|
raise
|
|
111
111
|
except ClientResponseError as error:
|
|
112
|
-
|
|
112
|
+
self.logger.error(f"Client response error: {error.status} - {str(error)}")
|
|
113
113
|
raise
|
|
114
114
|
except Exception as error:
|
|
115
|
-
|
|
115
|
+
self.logger.error(f"An unexpected error occurred: {error}")
|
|
116
116
|
raise
|
|
117
117
|
|
|
118
118
|
|
|
@@ -27,6 +27,6 @@ class MBTAPrediction:
|
|
|
27
27
|
self.trip_id: str = prediction.get('relationships', {}).get('trip', {}).get('data', {}).get('id', '')
|
|
28
28
|
|
|
29
29
|
def __repr__(self) -> str:
|
|
30
|
-
return (f"MBTAprediction(id={self.id}
|
|
30
|
+
return (f"MBTAprediction(id={self.id})")
|
|
31
31
|
|
|
32
32
|
|
|
@@ -22,5 +22,5 @@ class MBTASchedule:
|
|
|
22
22
|
self.trip_id: str = relationships.get('trip', {}).get('data', {}).get('id', '')
|
|
23
23
|
|
|
24
24
|
def __repr__(self) -> str:
|
|
25
|
-
return (f"MBTAschedule(id={self.id}
|
|
25
|
+
return (f"MBTAschedule(id={self.id})")
|
|
26
26
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
import logging
|
|
3
2
|
from typing import Optional
|
|
4
3
|
|
|
5
4
|
class MBTAUtils:
|
|
@@ -46,10 +45,6 @@ class MBTAUtils:
|
|
|
46
45
|
@staticmethod
|
|
47
46
|
def parse_datetime(time_str: str) -> Optional[datetime]:
|
|
48
47
|
"""Parse a string in ISO 8601 format to a datetime object."""
|
|
49
|
-
if time_str
|
|
48
|
+
if not isinstance(time_str, str):
|
|
50
49
|
return None
|
|
51
|
-
|
|
52
|
-
return datetime.fromisoformat(time_str)
|
|
53
|
-
except ValueError as e:
|
|
54
|
-
logging.error(f"Error parsing datetime: {e}")
|
|
55
|
-
return None
|
|
50
|
+
return datetime.fromisoformat(time_str)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
import traceback
|
|
1
3
|
import aiohttp
|
|
2
4
|
|
|
3
5
|
from base_handler import BaseHandler
|
|
@@ -10,46 +12,41 @@ from mbta_prediction import MBTAPrediction
|
|
|
10
12
|
class TripHandler(BaseHandler):
|
|
11
13
|
"""Handler for managing a specific trip."""
|
|
12
14
|
|
|
13
|
-
def __init__(self, session: aiohttp.ClientSession,
|
|
14
|
-
super().__init__(session,
|
|
15
|
+
def __init__(self, session: aiohttp.ClientSession, logger, depart_from_name: str, arrive_at_name: str, trip_name: str, api_key:str = None ) :
|
|
16
|
+
super().__init__(session, logger, depart_from_name, arrive_at_name, api_key)
|
|
15
17
|
self.trip_name = trip_name
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
await
|
|
20
|
-
|
|
21
|
-
schedules = await self.__fetch_schedules()
|
|
22
|
-
await self._process_schedules(schedules)
|
|
23
|
-
|
|
24
|
-
predictions = await self.__fetch_predictions()
|
|
25
|
-
await self._process_predictions(predictions)
|
|
26
|
-
|
|
27
|
-
alerts = await self.__fetch_alerts()
|
|
28
|
-
self._process_alerts(alerts)
|
|
29
|
-
|
|
30
|
-
return next(iter(self.journeys.values()))
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
async def trip_init(self):
|
|
34
|
-
|
|
19
|
+
|
|
20
|
+
async def async_init(self):
|
|
21
|
+
await super()._async_init()
|
|
22
|
+
|
|
35
23
|
params = {
|
|
36
24
|
'filter[revenue]' :'REVENUE',
|
|
37
25
|
'filter[name]' : self.trip_name
|
|
38
26
|
}
|
|
39
|
-
|
|
40
|
-
trips: list[MBTATrip] = await self.mbta_client.list_trips(params)
|
|
27
|
+
trips: list[MBTATrip] = await super()._fetch_trips(params)
|
|
41
28
|
|
|
42
29
|
journey = Journey()
|
|
43
|
-
|
|
44
30
|
journey.trip = trips[0]
|
|
45
31
|
|
|
46
|
-
route: MBTARoute = await
|
|
32
|
+
route: MBTARoute = await super()._fetch_route(journey.trip.route_id)
|
|
47
33
|
|
|
48
34
|
journey.route = route
|
|
49
|
-
|
|
50
35
|
self.journeys[trips[0].id] = journey
|
|
51
|
-
|
|
52
|
-
|
|
36
|
+
|
|
37
|
+
async def update(self) -> list[Journey]:
|
|
38
|
+
|
|
39
|
+
schedules = await self.__fetch_schedules()
|
|
40
|
+
await super()._process_schedules(schedules)
|
|
41
|
+
|
|
42
|
+
predictions = await self.__fetch_predictions()
|
|
43
|
+
await super()._process_predictions(predictions)
|
|
44
|
+
|
|
45
|
+
alerts = await self.__fetch_alerts()
|
|
46
|
+
super()._process_alerts(alerts)
|
|
47
|
+
return list(self.journeys.values())
|
|
48
|
+
|
|
49
|
+
|
|
53
50
|
async def __fetch_schedules(self) -> list[MBTASchedule]:
|
|
54
51
|
|
|
55
52
|
jounrey = next(iter(self.journeys.values()))
|
|
@@ -60,7 +57,6 @@ class TripHandler(BaseHandler):
|
|
|
60
57
|
}
|
|
61
58
|
|
|
62
59
|
schedules = await super()._fetch_schedules(params)
|
|
63
|
-
|
|
64
60
|
return schedules
|
|
65
61
|
|
|
66
62
|
|
|
@@ -74,7 +70,6 @@ class TripHandler(BaseHandler):
|
|
|
74
70
|
}
|
|
75
71
|
|
|
76
72
|
predictions = await super()._fetch_predictions(params)
|
|
77
|
-
|
|
78
73
|
return predictions
|
|
79
74
|
|
|
80
75
|
|
|
@@ -88,5 +83,4 @@ class TripHandler(BaseHandler):
|
|
|
88
83
|
}
|
|
89
84
|
|
|
90
85
|
alerts = await super()._fetch_alerts(params)
|
|
91
|
-
|
|
92
86
|
return alerts
|
mbtaclient-0.2.0/src/main.py
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import aiohttp
|
|
2
|
-
from trip_handler import TripHandler
|
|
3
|
-
from journeys_handler import JourneysHandler
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
API_KEY = ''
|
|
7
|
-
MAX_JOURNEYS = 5
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# DEPART_FROM = 'South Station'
|
|
11
|
-
# ARRIVE_AT = 'Wellesley Square'
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# DEPART_FROM = 'Wellesley Square'
|
|
15
|
-
# ARRIVE_AT = 'South Station'
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# DEPART_FROM = 'South Station'
|
|
19
|
-
# ARRIVE_AT = 'Braintree'
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# DEPART_FROM = 'Copley'
|
|
23
|
-
# ARRIVE_AT = 'Park Street'
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# DEPART_FROM = 'North Station'
|
|
27
|
-
# ARRIVE_AT = 'Swampscott'
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# DEPART_FROM = 'Dorchester Ave @ Valley Rd'
|
|
31
|
-
# ARRIVE_AT = 'River St @ Standard St'
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# DEPART_FROM = 'Back Bay'
|
|
35
|
-
# ARRIVE_AT = 'Huntington Ave @ Opera Pl'
|
|
36
|
-
|
|
37
|
-
# DEPART_FROM = 'Charlestown Navy Yard'
|
|
38
|
-
# ARRIVE_AT = 'Long Wharf (South)'
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# DEPART_FROM = 'North Billerica'
|
|
43
|
-
# ARRIVE_AT = 'North Station'
|
|
44
|
-
|
|
45
|
-
# DEPART_FROM = 'Back Bay'
|
|
46
|
-
# ARRIVE_AT = 'South Station'
|
|
47
|
-
|
|
48
|
-
# DEPART_FROM = 'Pemberton Point'
|
|
49
|
-
# ARRIVE_AT = 'Summer St from Cushing Way to Water St (FLAG)'
|
|
50
|
-
|
|
51
|
-
TRIP = '518'
|
|
52
|
-
DEPART_FROM = 'Wellesley Square'
|
|
53
|
-
ARRIVE_AT = 'South Station'
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def print_journey(journey):
|
|
57
|
-
route_type = journey.get_route_type()
|
|
58
|
-
|
|
59
|
-
# if subway or ferry
|
|
60
|
-
if route_type == 0 or route_type == 1 or route_type == 4:
|
|
61
|
-
|
|
62
|
-
print("###########")
|
|
63
|
-
print()
|
|
64
|
-
print("Line:", journey.get_route_long_name())
|
|
65
|
-
print("Type:", journey.get_route_description())
|
|
66
|
-
print("Color:", journey.get_route_color())
|
|
67
|
-
print()
|
|
68
|
-
print("Direction:", journey.get_trip_direction()+" to "+journey.get_trip_destination())
|
|
69
|
-
print("Destination:", journey.get_trip_headsign())
|
|
70
|
-
print()
|
|
71
|
-
# Print departure information
|
|
72
|
-
print("Departure Station:", journey.get_stop_name('departure'))
|
|
73
|
-
print("Departure Platform:", journey.get_platform_name('departure'))
|
|
74
|
-
print("Departure Time:", journey.get_stop_time('departure'))
|
|
75
|
-
print("Departure Delay:", journey.get_stop_delay('departure'))
|
|
76
|
-
print("Departure Time To:", journey.get_stop_time_to('departure'))
|
|
77
|
-
print()
|
|
78
|
-
# Print arrival information
|
|
79
|
-
print("Arrival Station:", journey.get_stop_name('arrival'))
|
|
80
|
-
print("Arrival Platform:", journey.get_platform_name('arrival'))
|
|
81
|
-
print("Arrival Time:", journey.get_stop_time('arrival'))
|
|
82
|
-
print("Arrival Delay:", journey.get_stop_delay('arrival'))
|
|
83
|
-
print("Arrival Time To:", journey.get_stop_time_to('arrival'))
|
|
84
|
-
print()
|
|
85
|
-
for j in range(len(journey.alerts)):
|
|
86
|
-
print("Alert:" , journey.get_alert_header(j))
|
|
87
|
-
print()
|
|
88
|
-
|
|
89
|
-
# if train
|
|
90
|
-
elif route_type == 2:
|
|
91
|
-
|
|
92
|
-
print("###########")
|
|
93
|
-
print()
|
|
94
|
-
print("Line:", journey.get_route_long_name())
|
|
95
|
-
print("Type:", journey.get_route_description())
|
|
96
|
-
print("Color:", journey.get_route_color())
|
|
97
|
-
print()
|
|
98
|
-
print("Train Number:", journey.get_trip_name())
|
|
99
|
-
print("Direction:", journey.get_trip_direction()+" to "+journey.get_trip_destination())
|
|
100
|
-
print("Destination:", journey.get_trip_headsign())
|
|
101
|
-
print()
|
|
102
|
-
# Print departure information
|
|
103
|
-
print("Departure Station:", journey.get_stop_name('departure'))
|
|
104
|
-
print("Departure Platform:", journey.get_platform_name('departure'))
|
|
105
|
-
print("Departure Time:", journey.get_stop_time('departure'))
|
|
106
|
-
print("Departure Delay:", journey.get_stop_delay('departure'))
|
|
107
|
-
print("Departure Time To:", journey.get_stop_time_to('departure'))
|
|
108
|
-
print()
|
|
109
|
-
# Print arrival information
|
|
110
|
-
print("Arrival Station:", journey.get_stop_name('arrival'))
|
|
111
|
-
print("Arrival Platform:", journey.get_platform_name('arrival'))
|
|
112
|
-
print("Arrival Time:", journey.get_stop_time('arrival'))
|
|
113
|
-
print("Arrival Delay:", journey.get_stop_delay('arrival'))
|
|
114
|
-
print("Arrival Time To:", journey.get_stop_time_to('arrival'))
|
|
115
|
-
print()
|
|
116
|
-
|
|
117
|
-
for j in range(len(journey.alerts)):
|
|
118
|
-
print("Alert:" , journey.get_alert_header(j))
|
|
119
|
-
print()
|
|
120
|
-
|
|
121
|
-
#if bus
|
|
122
|
-
elif route_type == 3:
|
|
123
|
-
|
|
124
|
-
print("###########")
|
|
125
|
-
print()
|
|
126
|
-
print("Line:", journey.get_route_short_name())
|
|
127
|
-
print("Type:", journey.get_route_description())
|
|
128
|
-
print("Color:", journey.get_route_color())
|
|
129
|
-
print()
|
|
130
|
-
print("Direction:", journey.get_trip_direction()+" to "+journey.get_trip_destination())
|
|
131
|
-
print("Destination:", journey.get_trip_headsign())
|
|
132
|
-
# Print departure information
|
|
133
|
-
print("Departure Stop:", journey.get_stop_name('departure'))
|
|
134
|
-
print("Departure Time:", journey.get_stop_time('departure'))
|
|
135
|
-
print("Departure Delay:", journey.get_stop_delay('departure'))
|
|
136
|
-
print("Departure Time To:", journey.get_stop_time_to('departure'))
|
|
137
|
-
print()
|
|
138
|
-
# Print arrival information
|
|
139
|
-
print("Arrival Stop:", journey.get_stop_name('arrival'))
|
|
140
|
-
print("Arrival Time:", journey.get_stop_time('arrival'))
|
|
141
|
-
print("Arrival Delay:", journey.get_stop_delay('arrival'))
|
|
142
|
-
print("Arrival Time To:", journey.get_stop_time_to('arrival'))
|
|
143
|
-
print()
|
|
144
|
-
for j in range(len(journey.alerts)):
|
|
145
|
-
print("Alert:" , journey.get_alert_header(j))
|
|
146
|
-
print()
|
|
147
|
-
|
|
148
|
-
else:
|
|
149
|
-
|
|
150
|
-
print('ARGH!')
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
async def main():
|
|
154
|
-
async with aiohttp.ClientSession() as session:
|
|
155
|
-
|
|
156
|
-
trip_hadler = TripHandler(session, API_KEY, DEPART_FROM, ARRIVE_AT, TRIP)
|
|
157
|
-
|
|
158
|
-
trip = await trip_hadler.fetch_trip()
|
|
159
|
-
|
|
160
|
-
print_journey(trip)
|
|
161
|
-
|
|
162
|
-
journeys_handler = JourneysHandler(session, API_KEY, DEPART_FROM, ARRIVE_AT, MAX_JOURNEYS)
|
|
163
|
-
|
|
164
|
-
journeys = await journeys_handler.fetch_journeys()
|
|
165
|
-
|
|
166
|
-
for journey in journeys:
|
|
167
|
-
print_journey(journey)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
# Run the main function
|
|
172
|
-
import asyncio
|
|
173
|
-
asyncio.run(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|