aiomobilitydatabase 0.3.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/PKG-INFO +159 -17
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/README.md +157 -15
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/const.py +1 -1
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/feeds/__init__.py +14 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/feeds/client.py +31 -7
- aiomobilitydatabase-0.5.0/src/aiomobilitydatabase/feeds/gbfs.py +642 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/feeds/geo.py +24 -11
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/feeds/models.py +221 -9
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/feeds/rt.py +313 -13
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/feeds/static_index.py +363 -65
- aiomobilitydatabase-0.5.0/src/aiomobilitydatabase/feeds/transit.py +1897 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/catalog/gbfs_feed.json +16 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gbfs/discovery_23.json +16 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gbfs/free_bike_status_23.json +6 -1
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gbfs/station_info_23.json +7 -1
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gbfs/station_status_23.json +7 -2
- aiomobilitydatabase-0.5.0/tests/feeds/data/gbfs/system_alerts.json +33 -0
- aiomobilitydatabase-0.5.0/tests/feeds/data/gbfs/system_info_23.json +22 -0
- aiomobilitydatabase-0.5.0/tests/feeds/data/gbfs/system_pricing_plans.json +19 -0
- aiomobilitydatabase-0.5.0/tests/feeds/data/gbfs/system_regions.json +15 -0
- aiomobilitydatabase-0.5.0/tests/feeds/data/gbfs/vehicle_types.json +24 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/fixtures.py +4 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_client.py +107 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_descriptive_attributes.py +16 -5
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_direct_urls.py +5 -5
- aiomobilitydatabase-0.5.0/tests/feeds/test_gbfs_stations.py +589 -0
- aiomobilitydatabase-0.5.0/tests/feeds/test_gbfs_vehicles.py +218 -0
- aiomobilitydatabase-0.5.0/tests/feeds/test_geo.py +90 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_models.py +109 -4
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_properties.py +3555 -435
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_rt.py +140 -6
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_sample_feed.py +17 -6
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_static_arrivals.py +34 -0
- aiomobilitydatabase-0.5.0/tests/feeds/test_static_build.py +731 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_static_trips.py +12 -0
- aiomobilitydatabase-0.5.0/tests/feeds/test_transit_arrivals.py +886 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_transit_frequencies.py +84 -8
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_transit_trips.py +136 -8
- aiomobilitydatabase-0.5.0/tests/feeds/test_transit_vehicles_alerts.py +725 -0
- aiomobilitydatabase-0.5.0/tests/test_catalog_properties.py +383 -0
- aiomobilitydatabase-0.3.0/src/aiomobilitydatabase/feeds/gbfs.py +0 -320
- aiomobilitydatabase-0.3.0/src/aiomobilitydatabase/feeds/transit.py +0 -1190
- aiomobilitydatabase-0.3.0/tests/feeds/data/gbfs/system_info_23.json +0 -10
- aiomobilitydatabase-0.3.0/tests/feeds/test_gbfs_stations.py +0 -205
- aiomobilitydatabase-0.3.0/tests/feeds/test_gbfs_vehicles.py +0 -80
- aiomobilitydatabase-0.3.0/tests/feeds/test_geo.py +0 -38
- aiomobilitydatabase-0.3.0/tests/feeds/test_static_build.py +0 -235
- aiomobilitydatabase-0.3.0/tests/feeds/test_transit_arrivals.py +0 -253
- aiomobilitydatabase-0.3.0/tests/feeds/test_transit_vehicles_alerts.py +0 -279
- aiomobilitydatabase-0.3.0/tests/test_catalog_properties.py +0 -211
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/.github/workflows/ci.yml +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/.github/workflows/publish.yml +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/.gitignore +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/.pre-commit-config.yaml +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/LICENSE +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/pyproject.toml +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/scripts/generate_rt_fixtures.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/__init__.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/client.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/exceptions.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/feeds/const.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/feeds/exceptions.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/models.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/py.typed +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/__init__.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/conftest.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/__init__.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/conftest.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/catalog/gtfs_feed.json +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/catalog/gtfs_rt_feed.json +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/catalog/token_response.json +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gbfs/discovery_30.json +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gbfs/system_info_30.json +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gbfs/vehicle_status_30.json +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gtfs/agency.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gtfs/calendar.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gtfs/calendar_dates.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gtfs/routes.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gtfs/stop_times.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gtfs/stops.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/gtfs/trips.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/added_trips_s1.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/alerts.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/alerts_trip_scoped.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_baseline.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_freq_bare_cancel.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_freq_canceled_tomorrow.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_freq_matched.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_freq_unmatched.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_t1_both_ends.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_t1_canceled.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_t1_canceled_tomorrow.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_t1_dated_tomorrow_delay.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_t1_delayed.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_t1_dest_arrival.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_t1_no_data_cut.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_t1_skip_s1.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_t1_skip_s2.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_t1_skip_s3.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/trip_updates_t1_trip_delay.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/vehicle_positions.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/rt/vehicle_positions_status.pb +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/README.md +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/agency.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/calendar.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/calendar_dates.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/fare_attributes.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/fare_rules.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/feed_info.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/frequencies.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/routes.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/shapes.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/stop_times.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/stops.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/transfers.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/translations.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/data/sample_feed/trips.txt +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_exceptions.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_extra_import_guard.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_static_frequencies.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_station_grouping.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/feeds/test_transit_resolution.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/fixtures.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/mock_server.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/test_client_request.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/test_client_session.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/test_client_token.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/test_endpoints.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/test_exceptions.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/test_models.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/test_params.py +0 -0
- {aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/tests/test_public_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: aiomobilitydatabase
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Async Python client for the Mobility Database catalog API
|
|
5
5
|
Project-URL: Source Code, https://github.com/raman325/aiomobilitydatabase
|
|
6
6
|
Project-URL: Bug Reports, https://github.com/raman325/aiomobilitydatabase/issues
|
|
@@ -104,7 +104,7 @@ aiomobilitydatabase[feeds]`.
|
|
|
104
104
|
```python
|
|
105
105
|
import asyncio
|
|
106
106
|
|
|
107
|
-
from aiomobilitydatabase.feeds import Circle, MobilityFeedsClient
|
|
107
|
+
from aiomobilitydatabase.feeds import ArrivalsQuery, Circle, MobilityFeedsClient
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
async def main() -> None:
|
|
@@ -137,11 +137,20 @@ async def main() -> None:
|
|
|
137
137
|
|
|
138
138
|
# Scheduled arrivals merged with realtime (delays, cancellations,
|
|
139
139
|
# RT-added trips) when the feed publishes GTFS-RT TripUpdates.
|
|
140
|
-
|
|
140
|
+
# Pass several ArrivalsQuery objects to get several boards (each
|
|
141
|
+
# with its own stops, route/headsign filters, and limit) from one
|
|
142
|
+
# realtime fetch; the result has one list per query, in order.
|
|
143
|
+
# Delayed trips stay listed until their prediction passes.
|
|
144
|
+
[arrivals] = await transit.get_arrivals(
|
|
145
|
+
[ArrivalsQuery([nearby_stops[0].id], limit=2)]
|
|
146
|
+
)
|
|
141
147
|
for arrival in arrivals:
|
|
142
148
|
when = arrival.predicted_departure or arrival.scheduled_departure
|
|
143
149
|
live = "live" if arrival.realtime else "scheduled"
|
|
144
|
-
|
|
150
|
+
# .stop and .route are the whole static records: platform codes,
|
|
151
|
+
# coordinates, route_type and route colours all come along.
|
|
152
|
+
route = arrival.route.display_name if arrival.route else arrival.route_id
|
|
153
|
+
print(f"{route} -> {arrival.headsign}: {when} ({live})")
|
|
145
154
|
|
|
146
155
|
# Origin→destination: the next departures from stop A on trips that
|
|
147
156
|
# later reach stop B, with the same realtime overlay. is_first/is_last
|
|
@@ -150,7 +159,8 @@ async def main() -> None:
|
|
|
150
159
|
nearby_stops[0].id, nearby_stops[1].id, limit=2
|
|
151
160
|
)
|
|
152
161
|
for trip in trips:
|
|
153
|
-
|
|
162
|
+
name = trip.route.display_name if trip.route else trip.route_id
|
|
163
|
+
print(name, trip.scheduled_departure, trip.is_last)
|
|
154
164
|
|
|
155
165
|
vehicles = await transit.get_vehicles()
|
|
156
166
|
alerts = await transit.get_alerts()
|
|
@@ -174,7 +184,7 @@ catalog methods, so everything above works unchanged:
|
|
|
174
184
|
```python
|
|
175
185
|
import asyncio
|
|
176
186
|
|
|
177
|
-
from aiomobilitydatabase.feeds import MobilityFeedsClient
|
|
187
|
+
from aiomobilitydatabase.feeds import ArrivalsQuery, MobilityFeedsClient
|
|
178
188
|
|
|
179
189
|
|
|
180
190
|
async def main() -> None:
|
|
@@ -186,7 +196,7 @@ async def main() -> None:
|
|
|
186
196
|
vehicle_positions_urls=["https://agency.example/gtfs-rt/positions.pb"],
|
|
187
197
|
headers={"Authorization": "Bearer PRODUCER_TOKEN"}, # optional
|
|
188
198
|
)
|
|
189
|
-
arrivals = await transit.get_arrivals([transit.stops[0].id])
|
|
199
|
+
[arrivals] = await transit.get_arrivals([ArrivalsQuery([transit.stops[0].id])])
|
|
190
200
|
|
|
191
201
|
bikes = await client.get_gbfs_feed_from_url(
|
|
192
202
|
"https://bikes.example/gbfs/gbfs.json"
|
|
@@ -226,6 +236,15 @@ Notes on direct mode:
|
|
|
226
236
|
work with or without realtime coverage; GTFS-RT TripUpdates overlay delays,
|
|
227
237
|
cancellations, and added trips when present, with cancellation always
|
|
228
238
|
winning over stale predictions.
|
|
239
|
+
- **Arrivals are batched and delay-aware**: `get_arrivals()` takes a sequence
|
|
240
|
+
of `ArrivalsQuery` and returns one board per query from a single schedule
|
|
241
|
+
read and a single realtime fetch, so several stops with different
|
|
242
|
+
route/headsign filters cost one round trip instead of one each. Each query's
|
|
243
|
+
filters apply *before* its `limit`, and `limit` caps that board as a whole
|
|
244
|
+
rather than per GTFS stop — so a narrow filter can never come back empty
|
|
245
|
+
because busier routes used up the limit. A `grace` window (default one hour)
|
|
246
|
+
keeps a delayed trip listed past its scheduled time until its prediction
|
|
247
|
+
passes, while a schedule-only departure still drops at its scheduled time.
|
|
229
248
|
- **frequencies.txt is materialized at build time**: headway-based trips
|
|
230
249
|
(common for metro/BRT) expand into concrete repetitions under synthetic
|
|
231
250
|
`{trip_id}#{start_secs}` ids, so every schedule query — arrivals,
|
|
@@ -238,6 +257,19 @@ Notes on direct mode:
|
|
|
238
257
|
information, `NO_DATA` makes a stop schedule-only, `SKIPPED` suppresses it
|
|
239
258
|
(and any origin→destination row boarding or alighting there), and the
|
|
240
259
|
trip-level delay covers stops no StopTimeUpdate reaches.
|
|
260
|
+
- **Rows carry whole records, not copied columns**: `StopArrival`,
|
|
261
|
+
`UpcomingTrip`, and `VehiclePosition` each expose the full `Stop` and
|
|
262
|
+
`Route` they refer to, so platform codes, coordinates, route colours and
|
|
263
|
+
`route_type` are reachable without a second lookup — use
|
|
264
|
+
`route.display_name` / `stop.name` for the human-readable strings. The raw
|
|
265
|
+
`stop_id`/`route_id` stay alongside, since those are what feeds reference
|
|
266
|
+
and what filters key on; a `route_id` naming a route absent from
|
|
267
|
+
`routes.txt` leaves `route` as `None` rather than inventing one.
|
|
268
|
+
- **Vehicle positions name their trip instance**: `VehiclePosition` carries
|
|
269
|
+
the parsed `trip_start_date`/`trip_start_secs`, the pair a GTFS-RT
|
|
270
|
+
`TripDescriptor` uses to address one run. That is what lets two buses out
|
|
271
|
+
on the same headway-based trip be told apart — without it, concurrent
|
|
272
|
+
repetitions of one template trip are indistinguishable.
|
|
241
273
|
- **The full descriptive surface is exposed, typed by shape**: closed GTFS
|
|
242
274
|
vocabularies are enums (`WheelchairAccess`, `BikesAllowed`,
|
|
243
275
|
`PickupDropOffType`, `StopLocationType`, alert cause/effect/severity,
|
|
@@ -245,25 +277,72 @@ Notes on direct mode:
|
|
|
245
277
|
absent-means-exact default, and open vocabularies (`route_type`,
|
|
246
278
|
`direction_id`) stay raw ints. Out-of-vocabulary values degrade to `None` —
|
|
247
279
|
descriptive metadata never fails a build. Consumers decide what to keep.
|
|
280
|
+
- **Numbers are ASCII-strict, and strictness follows the field's role**: a
|
|
281
|
+
GTFS number must be plain ASCII digits, so a Unicode decimal (`٨`), a sign
|
|
282
|
+
prefix, or Python's `1_0` underscore grouping is never quietly reinterpreted
|
|
283
|
+
as a plausible value. A malformed **structural** cell — `stop_times` times,
|
|
284
|
+
`stop_sequence`, `headway_secs`, `exception_type`, calendar weekday flags —
|
|
285
|
+
fails the build, because a wrong departure time or a wrong call order is
|
|
286
|
+
worse than no feed at all. A malformed **descriptive** cell still degrades
|
|
287
|
+
to `None`.
|
|
288
|
+
- **Detours are applied, not annotated**: a GTFS-RT `TripModifications`
|
|
289
|
+
entity REPLACES a span of a trip's calls, so a board that ignored it
|
|
290
|
+
would keep showing departures at stops the vehicle no longer serves, and
|
|
291
|
+
would miss `propagated_modification_delay` on every stop after the
|
|
292
|
+
detour. Replacement times are measured from the reference stop the spec
|
|
293
|
+
names, a replacement stop may be defined only by an RT `Stop` entity, and
|
|
294
|
+
a detour that routes a trip through a stop its static schedule never
|
|
295
|
+
served shows up there.
|
|
296
|
+
- **Schedule over a range**: `scheduled_departures(stops, start, end)` is
|
|
297
|
+
the calendar query — every departure between two instants, schedule only,
|
|
298
|
+
no limit and no grace, rather than `get_arrivals` stretched to days.
|
|
299
|
+
- **Service calendars are answerable**: `services_on(date)` returns the
|
|
300
|
+
service ids running on a GTFS service date — `calendar.txt` resolved
|
|
301
|
+
against `calendar_dates` exceptions, removals winning when a producer
|
|
302
|
+
lists both types for one date — and every row carries its `service_id`,
|
|
303
|
+
so "does this trip run next Tuesday" needs no second arrivals query.
|
|
304
|
+
- **GBFS ids resolve**: `get_vehicle_types()`, `get_pricing_plans()`,
|
|
305
|
+
`get_regions()`, and `get_system_alerts()` turn the `vehicle_type_id`,
|
|
306
|
+
`pricing_plan_id`, and `region_id` on stations and vehicles into records.
|
|
307
|
+
All four documents are optional in GBFS, so an unpublished one is `[]`
|
|
308
|
+
rather than an error.
|
|
248
309
|
- **Static metadata accessors**: `transit.agencies`, `transit.feed_info`
|
|
249
310
|
(publisher, version, validity dates — useful for staleness checks), and
|
|
250
311
|
`headsigns_serving()` alongside the stop/route helpers.
|
|
251
|
-
- **Alert scoping**: `ServiceAlert`
|
|
252
|
-
`
|
|
253
|
-
`
|
|
254
|
-
|
|
255
|
-
|
|
312
|
+
- **Alert scoping covers every selector GTFS-RT offers**: `ServiceAlert`
|
|
313
|
+
carries `agency_ids`, `route_ids`, `route_types`, `direction_ids`,
|
|
314
|
+
`stop_ids`, and `trip_ids` — an alert is feed-wide only when *all* of them
|
|
315
|
+
are empty. Collecting a subset is not a missing nicety but a correctness
|
|
316
|
+
bug: an alert scoped to one agency, or to a whole mode, would otherwise
|
|
317
|
+
read as applying everywhere. `is_active(at)` evaluates its active periods.
|
|
318
|
+
- **GBFS exposes what the documents carry**: the full station surface
|
|
319
|
+
(`is_installed` alongside `is_renting`/`is_returning` — a station removed
|
|
320
|
+
from the street is not "operational"; disabled counts, address, region,
|
|
321
|
+
virtual-station flag), free-floating vehicle docking and fuel state, and a
|
|
322
|
+
`SystemInfo` with the operator's contact, licensing, and terms URLs.
|
|
323
|
+
`last_reported` is exposed on both stations and vehicles so you can tell
|
|
324
|
+
fresh data from stale, and is parsed from either GBFS 2.x POSIX seconds or
|
|
325
|
+
3.0 RFC3339. Plus `rental_uris` deep links and TTL-based document caching.
|
|
256
326
|
- **`cache_dir` strongly recommended**: the static index is cached on disk
|
|
257
327
|
keyed by feed, validated against the dataset ID, so restarts are instant and
|
|
258
328
|
rebuilds only happen when the agency publishes a new dataset
|
|
259
329
|
(`await transit.refresh_static()` — call it daily; it swaps in the new index
|
|
260
|
-
only after it's built, so lookups never see a half-built database
|
|
261
|
-
|
|
330
|
+
only after it's built, so lookups never see a half-built database, and it is
|
|
331
|
+
safe to call while queries are in flight: the swap waits for in-flight reads
|
|
332
|
+
to finish, and two overlapping refreshes run back to back rather than
|
|
333
|
+
double-building). A corrupt or unreadable cache is treated as a cache miss
|
|
334
|
+
and rebuilt rather than raising. Without a `cache_dir` the index is built in
|
|
335
|
+
memory on every startup.
|
|
262
336
|
- **Pull, not push**: `TransitFeedHandle`/`GbfsFeedHandle` return snapshots on
|
|
263
337
|
demand — there's no built-in polling loop or scheduler. Bring your own (e.g.
|
|
264
338
|
Home Assistant's `DataUpdateCoordinator`).
|
|
265
339
|
- **`purge_cache()`** deletes a feed's cached static data (or all feeds' when
|
|
266
340
|
called with no argument) — call it on cleanup/removal of a configured feed.
|
|
341
|
+
A `feed_id` that is not a bare child name of the cache directory raises
|
|
342
|
+
`ValueError` instead of deleting anything, so an absolute path, a `..`
|
|
343
|
+
component, a symlinked entry, or a degenerate `""`/`"."` cannot take out
|
|
344
|
+
every feed's cache. Purging a feed with no cache directory is a no-op, so
|
|
345
|
+
repeated calls are idempotent.
|
|
267
346
|
`transit.close()` releases a handle's SQLite connection — call it when you
|
|
268
347
|
are done with a handle; the client's `close()` does not do it for you.
|
|
269
348
|
- **Producer authentication**: pass `api_key=` to `get_transit_feed()`; it is
|
|
@@ -274,6 +353,45 @@ Notes on direct mode:
|
|
|
274
353
|
design, with no server-side geo-query.
|
|
275
354
|
- **Timestamps are tz-aware UTC** throughout the feeds layer.
|
|
276
355
|
|
|
356
|
+
## What this does not expose
|
|
357
|
+
|
|
358
|
+
Deliberate omissions, so you can tell "not supported" from "not found yet".
|
|
359
|
+
|
|
360
|
+
**GTFS files not ingested.** The static index reads `agency.txt`, `stops.txt`,
|
|
361
|
+
`routes.txt`, `trips.txt`, `stop_times.txt`, `calendar.txt`,
|
|
362
|
+
`calendar_dates.txt`, `frequencies.txt`, and `feed_info.txt`. Everything else
|
|
363
|
+
in the zip is ignored:
|
|
364
|
+
|
|
365
|
+
- `shapes.txt` — route geometry. Nothing here draws a line on a map, so trips
|
|
366
|
+
carry no `shape_id`.
|
|
367
|
+
- `transfers.txt` — no transfer rules, minimum transfer times, or
|
|
368
|
+
trip-to-trip continuations, and no journey planning across a transfer.
|
|
369
|
+
- `pathways.txt` / `levels.txt` — no in-station walking graph.
|
|
370
|
+
- `fare_attributes.txt` / `fare_rules.txt` and all GTFS-Fares v2 files — no
|
|
371
|
+
fares, zone pricing, or rider categories. (`Stop.zone_id` is exposed as a
|
|
372
|
+
raw cell, but nothing interprets it.)
|
|
373
|
+
- `translations.txt` — text is returned as the feed wrote it.
|
|
374
|
+
|
|
375
|
+
Service calendars **are** answerable — `services_on(date)` resolves
|
|
376
|
+
`calendar.txt` against `calendar_dates` exceptions, and rows carry their
|
|
377
|
+
`service_id` — but the raw weekday pattern and validity range behind a
|
|
378
|
+
service are not exposed as a model. `scheduled_departures(stops, start, end)`
|
|
379
|
+
covers the range query a calendar view needs.
|
|
380
|
+
|
|
381
|
+
**GTFS-RT: trip shapes.** The RT `shape` entity (a detour's new geometry) is
|
|
382
|
+
not read, for the same reason `shapes.txt` is not: nothing here draws a line
|
|
383
|
+
on a map. Detours themselves ARE applied — see *Detours are applied, not
|
|
384
|
+
annotated* — and the `stop` entities they define are read.
|
|
385
|
+
|
|
386
|
+
**GBFS endpoints not fetched.** `geofencing_zones`, because it is GeoJSON
|
|
387
|
+
polygon geometry while zones here are deliberately circles — supporting it
|
|
388
|
+
means polygon containment, which is a geometry feature rather than another
|
|
389
|
+
document. And `system_hours`, which GBFS 3.0 removed in favour of
|
|
390
|
+
`system_information.opening_hours` (exposed as `SystemInfo.opening_hours`).
|
|
391
|
+
|
|
392
|
+
**Behaviour, not data.** There is no polling loop or scheduler (see *Pull, not
|
|
393
|
+
push*), no write access of any kind, and no cross-feed journey planning.
|
|
394
|
+
|
|
277
395
|
## Error handling
|
|
278
396
|
|
|
279
397
|
All errors derive from `MobilityDatabaseError`. The catalog and feeds layers
|
|
@@ -290,9 +408,17 @@ each define their own subtree beneath it, so you can catch broadly
|
|
|
290
408
|
| `MobilityFeedsError` | feeds | Base class for all feeds errors (subclasses `MobilityDatabaseError`) |
|
|
291
409
|
| `SourceConnectionError` | feeds | Producer/GBFS endpoint unreachable or errored (`.status` when HTTP) |
|
|
292
410
|
| `SourceAuthenticationError` | feeds | Producer rejected the feed's `api_key` |
|
|
293
|
-
| `FeedParseError` | feeds | Undecodable protobuf, malformed GBFS JSON
|
|
411
|
+
| `FeedParseError` | feeds | Undecodable protobuf, malformed GBFS JSON or envelope, unreadable GTFS zip, or a malformed scalar in a structural GTFS column |
|
|
294
412
|
| `StaticDataUnavailableError` | feeds | Feed has no usable hosted static dataset |
|
|
295
413
|
|
|
414
|
+
The GBFS and GTFS-RT parse paths are **total** with respect to this table:
|
|
415
|
+
any producer document, however malformed, either returns a typed result or
|
|
416
|
+
raises one of the exceptions above — never a bare `KeyError`, `TypeError`,
|
|
417
|
+
`ValueError` or `OverflowError` from inside the library. The one deliberate
|
|
418
|
+
exception is `purge_cache()`, which raises the builtin `ValueError` for a
|
|
419
|
+
`feed_id` it will not accept (see above), because that is a caller mistake
|
|
420
|
+
rather than a feed problem.
|
|
421
|
+
|
|
296
422
|
Note the two independent auth flows: a `MobilityDatabaseAuthenticationError`
|
|
297
423
|
means your **catalog refresh token** is invalid; a `SourceAuthenticationError`
|
|
298
424
|
means the **producer** rejected the feed-specific `api_key` you passed to
|
|
@@ -351,11 +477,27 @@ The suite combines example-based, property-based, and conformance testing:
|
|
|
351
477
|
resolver at volume), and `start_date`/`start_time` instance matching over
|
|
352
478
|
windows containing two service-day instances of the same trip;
|
|
353
479
|
- the circular-zone geometry (`in_circle`'s bbox prefilter is checked
|
|
354
|
-
against the exact
|
|
480
|
+
against the exact great-circle distance at generated boundary points);
|
|
355
481
|
- GBFS field parsing (`_localized`, `_version_key`, station/vehicle merging)
|
|
356
482
|
and GTFS-RT protobuf parsing, both checked for **totality** — every
|
|
357
483
|
generated input must produce a typed result or a documented
|
|
358
484
|
`MobilityFeedsError` subclass, never an unhandled exception;
|
|
485
|
+
- the batched arrivals contract: that batching is pure query factorization
|
|
486
|
+
(`get_arrivals([q1, q2])` equals the two single-query calls), that a batch
|
|
487
|
+
costs exactly one realtime fetch however many queries it carries, that
|
|
488
|
+
route and headsign filters apply before the limit, and that every returned
|
|
489
|
+
row's effective departure is still ahead of `now`;
|
|
490
|
+
- the origin→destination query against an oracle built from the stored
|
|
491
|
+
stop-call table: the self-join's direction rule (no return-trip rows) and
|
|
492
|
+
the earliest-destination-call rule for loop trips, neither of which any
|
|
493
|
+
test asserted before;
|
|
494
|
+
- window monotonicity — widening `lookahead` yields a prefix and widening
|
|
495
|
+
`grace` yields a superset — which guards the scan-window regression class
|
|
496
|
+
directly, without a hand-built oracle;
|
|
497
|
+
- the cache as a round trip: a built index and the same index reopened from
|
|
498
|
+
disk answer every query identically, including the stored timezone, and
|
|
499
|
+
`purge_cache` removes exactly the named feed and nothing else for any
|
|
500
|
+
generated `feed_id`;
|
|
359
501
|
- error-contract fuzzing for malformed GTFS zips, RT payloads, and GBFS
|
|
360
502
|
documents (garbage bytes, wrong types, out-of-range values) — the
|
|
361
503
|
contract under test is "raises `FeedParseError`/`SourceConnectionError`",
|
|
@@ -80,7 +80,7 @@ aiomobilitydatabase[feeds]`.
|
|
|
80
80
|
```python
|
|
81
81
|
import asyncio
|
|
82
82
|
|
|
83
|
-
from aiomobilitydatabase.feeds import Circle, MobilityFeedsClient
|
|
83
|
+
from aiomobilitydatabase.feeds import ArrivalsQuery, Circle, MobilityFeedsClient
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
async def main() -> None:
|
|
@@ -113,11 +113,20 @@ async def main() -> None:
|
|
|
113
113
|
|
|
114
114
|
# Scheduled arrivals merged with realtime (delays, cancellations,
|
|
115
115
|
# RT-added trips) when the feed publishes GTFS-RT TripUpdates.
|
|
116
|
-
|
|
116
|
+
# Pass several ArrivalsQuery objects to get several boards (each
|
|
117
|
+
# with its own stops, route/headsign filters, and limit) from one
|
|
118
|
+
# realtime fetch; the result has one list per query, in order.
|
|
119
|
+
# Delayed trips stay listed until their prediction passes.
|
|
120
|
+
[arrivals] = await transit.get_arrivals(
|
|
121
|
+
[ArrivalsQuery([nearby_stops[0].id], limit=2)]
|
|
122
|
+
)
|
|
117
123
|
for arrival in arrivals:
|
|
118
124
|
when = arrival.predicted_departure or arrival.scheduled_departure
|
|
119
125
|
live = "live" if arrival.realtime else "scheduled"
|
|
120
|
-
|
|
126
|
+
# .stop and .route are the whole static records: platform codes,
|
|
127
|
+
# coordinates, route_type and route colours all come along.
|
|
128
|
+
route = arrival.route.display_name if arrival.route else arrival.route_id
|
|
129
|
+
print(f"{route} -> {arrival.headsign}: {when} ({live})")
|
|
121
130
|
|
|
122
131
|
# Origin→destination: the next departures from stop A on trips that
|
|
123
132
|
# later reach stop B, with the same realtime overlay. is_first/is_last
|
|
@@ -126,7 +135,8 @@ async def main() -> None:
|
|
|
126
135
|
nearby_stops[0].id, nearby_stops[1].id, limit=2
|
|
127
136
|
)
|
|
128
137
|
for trip in trips:
|
|
129
|
-
|
|
138
|
+
name = trip.route.display_name if trip.route else trip.route_id
|
|
139
|
+
print(name, trip.scheduled_departure, trip.is_last)
|
|
130
140
|
|
|
131
141
|
vehicles = await transit.get_vehicles()
|
|
132
142
|
alerts = await transit.get_alerts()
|
|
@@ -150,7 +160,7 @@ catalog methods, so everything above works unchanged:
|
|
|
150
160
|
```python
|
|
151
161
|
import asyncio
|
|
152
162
|
|
|
153
|
-
from aiomobilitydatabase.feeds import MobilityFeedsClient
|
|
163
|
+
from aiomobilitydatabase.feeds import ArrivalsQuery, MobilityFeedsClient
|
|
154
164
|
|
|
155
165
|
|
|
156
166
|
async def main() -> None:
|
|
@@ -162,7 +172,7 @@ async def main() -> None:
|
|
|
162
172
|
vehicle_positions_urls=["https://agency.example/gtfs-rt/positions.pb"],
|
|
163
173
|
headers={"Authorization": "Bearer PRODUCER_TOKEN"}, # optional
|
|
164
174
|
)
|
|
165
|
-
arrivals = await transit.get_arrivals([transit.stops[0].id])
|
|
175
|
+
[arrivals] = await transit.get_arrivals([ArrivalsQuery([transit.stops[0].id])])
|
|
166
176
|
|
|
167
177
|
bikes = await client.get_gbfs_feed_from_url(
|
|
168
178
|
"https://bikes.example/gbfs/gbfs.json"
|
|
@@ -202,6 +212,15 @@ Notes on direct mode:
|
|
|
202
212
|
work with or without realtime coverage; GTFS-RT TripUpdates overlay delays,
|
|
203
213
|
cancellations, and added trips when present, with cancellation always
|
|
204
214
|
winning over stale predictions.
|
|
215
|
+
- **Arrivals are batched and delay-aware**: `get_arrivals()` takes a sequence
|
|
216
|
+
of `ArrivalsQuery` and returns one board per query from a single schedule
|
|
217
|
+
read and a single realtime fetch, so several stops with different
|
|
218
|
+
route/headsign filters cost one round trip instead of one each. Each query's
|
|
219
|
+
filters apply *before* its `limit`, and `limit` caps that board as a whole
|
|
220
|
+
rather than per GTFS stop — so a narrow filter can never come back empty
|
|
221
|
+
because busier routes used up the limit. A `grace` window (default one hour)
|
|
222
|
+
keeps a delayed trip listed past its scheduled time until its prediction
|
|
223
|
+
passes, while a schedule-only departure still drops at its scheduled time.
|
|
205
224
|
- **frequencies.txt is materialized at build time**: headway-based trips
|
|
206
225
|
(common for metro/BRT) expand into concrete repetitions under synthetic
|
|
207
226
|
`{trip_id}#{start_secs}` ids, so every schedule query — arrivals,
|
|
@@ -214,6 +233,19 @@ Notes on direct mode:
|
|
|
214
233
|
information, `NO_DATA` makes a stop schedule-only, `SKIPPED` suppresses it
|
|
215
234
|
(and any origin→destination row boarding or alighting there), and the
|
|
216
235
|
trip-level delay covers stops no StopTimeUpdate reaches.
|
|
236
|
+
- **Rows carry whole records, not copied columns**: `StopArrival`,
|
|
237
|
+
`UpcomingTrip`, and `VehiclePosition` each expose the full `Stop` and
|
|
238
|
+
`Route` they refer to, so platform codes, coordinates, route colours and
|
|
239
|
+
`route_type` are reachable without a second lookup — use
|
|
240
|
+
`route.display_name` / `stop.name` for the human-readable strings. The raw
|
|
241
|
+
`stop_id`/`route_id` stay alongside, since those are what feeds reference
|
|
242
|
+
and what filters key on; a `route_id` naming a route absent from
|
|
243
|
+
`routes.txt` leaves `route` as `None` rather than inventing one.
|
|
244
|
+
- **Vehicle positions name their trip instance**: `VehiclePosition` carries
|
|
245
|
+
the parsed `trip_start_date`/`trip_start_secs`, the pair a GTFS-RT
|
|
246
|
+
`TripDescriptor` uses to address one run. That is what lets two buses out
|
|
247
|
+
on the same headway-based trip be told apart — without it, concurrent
|
|
248
|
+
repetitions of one template trip are indistinguishable.
|
|
217
249
|
- **The full descriptive surface is exposed, typed by shape**: closed GTFS
|
|
218
250
|
vocabularies are enums (`WheelchairAccess`, `BikesAllowed`,
|
|
219
251
|
`PickupDropOffType`, `StopLocationType`, alert cause/effect/severity,
|
|
@@ -221,25 +253,72 @@ Notes on direct mode:
|
|
|
221
253
|
absent-means-exact default, and open vocabularies (`route_type`,
|
|
222
254
|
`direction_id`) stay raw ints. Out-of-vocabulary values degrade to `None` —
|
|
223
255
|
descriptive metadata never fails a build. Consumers decide what to keep.
|
|
256
|
+
- **Numbers are ASCII-strict, and strictness follows the field's role**: a
|
|
257
|
+
GTFS number must be plain ASCII digits, so a Unicode decimal (`٨`), a sign
|
|
258
|
+
prefix, or Python's `1_0` underscore grouping is never quietly reinterpreted
|
|
259
|
+
as a plausible value. A malformed **structural** cell — `stop_times` times,
|
|
260
|
+
`stop_sequence`, `headway_secs`, `exception_type`, calendar weekday flags —
|
|
261
|
+
fails the build, because a wrong departure time or a wrong call order is
|
|
262
|
+
worse than no feed at all. A malformed **descriptive** cell still degrades
|
|
263
|
+
to `None`.
|
|
264
|
+
- **Detours are applied, not annotated**: a GTFS-RT `TripModifications`
|
|
265
|
+
entity REPLACES a span of a trip's calls, so a board that ignored it
|
|
266
|
+
would keep showing departures at stops the vehicle no longer serves, and
|
|
267
|
+
would miss `propagated_modification_delay` on every stop after the
|
|
268
|
+
detour. Replacement times are measured from the reference stop the spec
|
|
269
|
+
names, a replacement stop may be defined only by an RT `Stop` entity, and
|
|
270
|
+
a detour that routes a trip through a stop its static schedule never
|
|
271
|
+
served shows up there.
|
|
272
|
+
- **Schedule over a range**: `scheduled_departures(stops, start, end)` is
|
|
273
|
+
the calendar query — every departure between two instants, schedule only,
|
|
274
|
+
no limit and no grace, rather than `get_arrivals` stretched to days.
|
|
275
|
+
- **Service calendars are answerable**: `services_on(date)` returns the
|
|
276
|
+
service ids running on a GTFS service date — `calendar.txt` resolved
|
|
277
|
+
against `calendar_dates` exceptions, removals winning when a producer
|
|
278
|
+
lists both types for one date — and every row carries its `service_id`,
|
|
279
|
+
so "does this trip run next Tuesday" needs no second arrivals query.
|
|
280
|
+
- **GBFS ids resolve**: `get_vehicle_types()`, `get_pricing_plans()`,
|
|
281
|
+
`get_regions()`, and `get_system_alerts()` turn the `vehicle_type_id`,
|
|
282
|
+
`pricing_plan_id`, and `region_id` on stations and vehicles into records.
|
|
283
|
+
All four documents are optional in GBFS, so an unpublished one is `[]`
|
|
284
|
+
rather than an error.
|
|
224
285
|
- **Static metadata accessors**: `transit.agencies`, `transit.feed_info`
|
|
225
286
|
(publisher, version, validity dates — useful for staleness checks), and
|
|
226
287
|
`headsigns_serving()` alongside the stop/route helpers.
|
|
227
|
-
- **Alert scoping**: `ServiceAlert`
|
|
228
|
-
`
|
|
229
|
-
`
|
|
230
|
-
|
|
231
|
-
|
|
288
|
+
- **Alert scoping covers every selector GTFS-RT offers**: `ServiceAlert`
|
|
289
|
+
carries `agency_ids`, `route_ids`, `route_types`, `direction_ids`,
|
|
290
|
+
`stop_ids`, and `trip_ids` — an alert is feed-wide only when *all* of them
|
|
291
|
+
are empty. Collecting a subset is not a missing nicety but a correctness
|
|
292
|
+
bug: an alert scoped to one agency, or to a whole mode, would otherwise
|
|
293
|
+
read as applying everywhere. `is_active(at)` evaluates its active periods.
|
|
294
|
+
- **GBFS exposes what the documents carry**: the full station surface
|
|
295
|
+
(`is_installed` alongside `is_renting`/`is_returning` — a station removed
|
|
296
|
+
from the street is not "operational"; disabled counts, address, region,
|
|
297
|
+
virtual-station flag), free-floating vehicle docking and fuel state, and a
|
|
298
|
+
`SystemInfo` with the operator's contact, licensing, and terms URLs.
|
|
299
|
+
`last_reported` is exposed on both stations and vehicles so you can tell
|
|
300
|
+
fresh data from stale, and is parsed from either GBFS 2.x POSIX seconds or
|
|
301
|
+
3.0 RFC3339. Plus `rental_uris` deep links and TTL-based document caching.
|
|
232
302
|
- **`cache_dir` strongly recommended**: the static index is cached on disk
|
|
233
303
|
keyed by feed, validated against the dataset ID, so restarts are instant and
|
|
234
304
|
rebuilds only happen when the agency publishes a new dataset
|
|
235
305
|
(`await transit.refresh_static()` — call it daily; it swaps in the new index
|
|
236
|
-
only after it's built, so lookups never see a half-built database
|
|
237
|
-
|
|
306
|
+
only after it's built, so lookups never see a half-built database, and it is
|
|
307
|
+
safe to call while queries are in flight: the swap waits for in-flight reads
|
|
308
|
+
to finish, and two overlapping refreshes run back to back rather than
|
|
309
|
+
double-building). A corrupt or unreadable cache is treated as a cache miss
|
|
310
|
+
and rebuilt rather than raising. Without a `cache_dir` the index is built in
|
|
311
|
+
memory on every startup.
|
|
238
312
|
- **Pull, not push**: `TransitFeedHandle`/`GbfsFeedHandle` return snapshots on
|
|
239
313
|
demand — there's no built-in polling loop or scheduler. Bring your own (e.g.
|
|
240
314
|
Home Assistant's `DataUpdateCoordinator`).
|
|
241
315
|
- **`purge_cache()`** deletes a feed's cached static data (or all feeds' when
|
|
242
316
|
called with no argument) — call it on cleanup/removal of a configured feed.
|
|
317
|
+
A `feed_id` that is not a bare child name of the cache directory raises
|
|
318
|
+
`ValueError` instead of deleting anything, so an absolute path, a `..`
|
|
319
|
+
component, a symlinked entry, or a degenerate `""`/`"."` cannot take out
|
|
320
|
+
every feed's cache. Purging a feed with no cache directory is a no-op, so
|
|
321
|
+
repeated calls are idempotent.
|
|
243
322
|
`transit.close()` releases a handle's SQLite connection — call it when you
|
|
244
323
|
are done with a handle; the client's `close()` does not do it for you.
|
|
245
324
|
- **Producer authentication**: pass `api_key=` to `get_transit_feed()`; it is
|
|
@@ -250,6 +329,45 @@ Notes on direct mode:
|
|
|
250
329
|
design, with no server-side geo-query.
|
|
251
330
|
- **Timestamps are tz-aware UTC** throughout the feeds layer.
|
|
252
331
|
|
|
332
|
+
## What this does not expose
|
|
333
|
+
|
|
334
|
+
Deliberate omissions, so you can tell "not supported" from "not found yet".
|
|
335
|
+
|
|
336
|
+
**GTFS files not ingested.** The static index reads `agency.txt`, `stops.txt`,
|
|
337
|
+
`routes.txt`, `trips.txt`, `stop_times.txt`, `calendar.txt`,
|
|
338
|
+
`calendar_dates.txt`, `frequencies.txt`, and `feed_info.txt`. Everything else
|
|
339
|
+
in the zip is ignored:
|
|
340
|
+
|
|
341
|
+
- `shapes.txt` — route geometry. Nothing here draws a line on a map, so trips
|
|
342
|
+
carry no `shape_id`.
|
|
343
|
+
- `transfers.txt` — no transfer rules, minimum transfer times, or
|
|
344
|
+
trip-to-trip continuations, and no journey planning across a transfer.
|
|
345
|
+
- `pathways.txt` / `levels.txt` — no in-station walking graph.
|
|
346
|
+
- `fare_attributes.txt` / `fare_rules.txt` and all GTFS-Fares v2 files — no
|
|
347
|
+
fares, zone pricing, or rider categories. (`Stop.zone_id` is exposed as a
|
|
348
|
+
raw cell, but nothing interprets it.)
|
|
349
|
+
- `translations.txt` — text is returned as the feed wrote it.
|
|
350
|
+
|
|
351
|
+
Service calendars **are** answerable — `services_on(date)` resolves
|
|
352
|
+
`calendar.txt` against `calendar_dates` exceptions, and rows carry their
|
|
353
|
+
`service_id` — but the raw weekday pattern and validity range behind a
|
|
354
|
+
service are not exposed as a model. `scheduled_departures(stops, start, end)`
|
|
355
|
+
covers the range query a calendar view needs.
|
|
356
|
+
|
|
357
|
+
**GTFS-RT: trip shapes.** The RT `shape` entity (a detour's new geometry) is
|
|
358
|
+
not read, for the same reason `shapes.txt` is not: nothing here draws a line
|
|
359
|
+
on a map. Detours themselves ARE applied — see *Detours are applied, not
|
|
360
|
+
annotated* — and the `stop` entities they define are read.
|
|
361
|
+
|
|
362
|
+
**GBFS endpoints not fetched.** `geofencing_zones`, because it is GeoJSON
|
|
363
|
+
polygon geometry while zones here are deliberately circles — supporting it
|
|
364
|
+
means polygon containment, which is a geometry feature rather than another
|
|
365
|
+
document. And `system_hours`, which GBFS 3.0 removed in favour of
|
|
366
|
+
`system_information.opening_hours` (exposed as `SystemInfo.opening_hours`).
|
|
367
|
+
|
|
368
|
+
**Behaviour, not data.** There is no polling loop or scheduler (see *Pull, not
|
|
369
|
+
push*), no write access of any kind, and no cross-feed journey planning.
|
|
370
|
+
|
|
253
371
|
## Error handling
|
|
254
372
|
|
|
255
373
|
All errors derive from `MobilityDatabaseError`. The catalog and feeds layers
|
|
@@ -266,9 +384,17 @@ each define their own subtree beneath it, so you can catch broadly
|
|
|
266
384
|
| `MobilityFeedsError` | feeds | Base class for all feeds errors (subclasses `MobilityDatabaseError`) |
|
|
267
385
|
| `SourceConnectionError` | feeds | Producer/GBFS endpoint unreachable or errored (`.status` when HTTP) |
|
|
268
386
|
| `SourceAuthenticationError` | feeds | Producer rejected the feed's `api_key` |
|
|
269
|
-
| `FeedParseError` | feeds | Undecodable protobuf, malformed GBFS JSON
|
|
387
|
+
| `FeedParseError` | feeds | Undecodable protobuf, malformed GBFS JSON or envelope, unreadable GTFS zip, or a malformed scalar in a structural GTFS column |
|
|
270
388
|
| `StaticDataUnavailableError` | feeds | Feed has no usable hosted static dataset |
|
|
271
389
|
|
|
390
|
+
The GBFS and GTFS-RT parse paths are **total** with respect to this table:
|
|
391
|
+
any producer document, however malformed, either returns a typed result or
|
|
392
|
+
raises one of the exceptions above — never a bare `KeyError`, `TypeError`,
|
|
393
|
+
`ValueError` or `OverflowError` from inside the library. The one deliberate
|
|
394
|
+
exception is `purge_cache()`, which raises the builtin `ValueError` for a
|
|
395
|
+
`feed_id` it will not accept (see above), because that is a caller mistake
|
|
396
|
+
rather than a feed problem.
|
|
397
|
+
|
|
272
398
|
Note the two independent auth flows: a `MobilityDatabaseAuthenticationError`
|
|
273
399
|
means your **catalog refresh token** is invalid; a `SourceAuthenticationError`
|
|
274
400
|
means the **producer** rejected the feed-specific `api_key` you passed to
|
|
@@ -327,11 +453,27 @@ The suite combines example-based, property-based, and conformance testing:
|
|
|
327
453
|
resolver at volume), and `start_date`/`start_time` instance matching over
|
|
328
454
|
windows containing two service-day instances of the same trip;
|
|
329
455
|
- the circular-zone geometry (`in_circle`'s bbox prefilter is checked
|
|
330
|
-
against the exact
|
|
456
|
+
against the exact great-circle distance at generated boundary points);
|
|
331
457
|
- GBFS field parsing (`_localized`, `_version_key`, station/vehicle merging)
|
|
332
458
|
and GTFS-RT protobuf parsing, both checked for **totality** — every
|
|
333
459
|
generated input must produce a typed result or a documented
|
|
334
460
|
`MobilityFeedsError` subclass, never an unhandled exception;
|
|
461
|
+
- the batched arrivals contract: that batching is pure query factorization
|
|
462
|
+
(`get_arrivals([q1, q2])` equals the two single-query calls), that a batch
|
|
463
|
+
costs exactly one realtime fetch however many queries it carries, that
|
|
464
|
+
route and headsign filters apply before the limit, and that every returned
|
|
465
|
+
row's effective departure is still ahead of `now`;
|
|
466
|
+
- the origin→destination query against an oracle built from the stored
|
|
467
|
+
stop-call table: the self-join's direction rule (no return-trip rows) and
|
|
468
|
+
the earliest-destination-call rule for loop trips, neither of which any
|
|
469
|
+
test asserted before;
|
|
470
|
+
- window monotonicity — widening `lookahead` yields a prefix and widening
|
|
471
|
+
`grace` yields a superset — which guards the scan-window regression class
|
|
472
|
+
directly, without a hand-built oracle;
|
|
473
|
+
- the cache as a round trip: a built index and the same index reopened from
|
|
474
|
+
disk answer every query identically, including the stored timezone, and
|
|
475
|
+
`purge_cache` removes exactly the named feed and nothing else for any
|
|
476
|
+
generated `feed_id`;
|
|
335
477
|
- error-contract fuzzing for malformed GTFS zips, RT payloads, and GBFS
|
|
336
478
|
documents (garbage bytes, wrong types, out-of-range values) — the
|
|
337
479
|
contract under test is "raises `FeedParseError`/`SourceConnectionError`",
|
{aiomobilitydatabase-0.3.0 → aiomobilitydatabase-0.5.0}/src/aiomobilitydatabase/feeds/__init__.py
RENAMED
|
@@ -26,13 +26,18 @@ from .models import (
|
|
|
26
26
|
Agency,
|
|
27
27
|
AlertCause,
|
|
28
28
|
AlertEffect,
|
|
29
|
+
AlertImage,
|
|
29
30
|
AlertSeverity,
|
|
31
|
+
ArrivalsQuery,
|
|
30
32
|
BikesAllowed,
|
|
33
|
+
CarriageDetail,
|
|
31
34
|
CongestionLevel,
|
|
32
35
|
FeedInfo,
|
|
36
|
+
GbfsAlert,
|
|
33
37
|
GbfsVehicle,
|
|
34
38
|
OccupancyStatus,
|
|
35
39
|
PickupDropOffType,
|
|
40
|
+
PricingPlan,
|
|
36
41
|
Route,
|
|
37
42
|
ServiceAlert,
|
|
38
43
|
StaticBuildProgress,
|
|
@@ -42,9 +47,11 @@ from .models import (
|
|
|
42
47
|
StopArrival,
|
|
43
48
|
StopLocationType,
|
|
44
49
|
SystemInfo,
|
|
50
|
+
SystemRegion,
|
|
45
51
|
UpcomingTrip,
|
|
46
52
|
VehiclePosition,
|
|
47
53
|
VehicleStopStatus,
|
|
54
|
+
VehicleType,
|
|
48
55
|
WheelchairAccess,
|
|
49
56
|
)
|
|
50
57
|
from .transit import TransitFeedHandle
|
|
@@ -53,18 +60,23 @@ __all__ = [
|
|
|
53
60
|
"Agency",
|
|
54
61
|
"AlertCause",
|
|
55
62
|
"AlertEffect",
|
|
63
|
+
"AlertImage",
|
|
56
64
|
"AlertSeverity",
|
|
65
|
+
"ArrivalsQuery",
|
|
57
66
|
"BikesAllowed",
|
|
67
|
+
"CarriageDetail",
|
|
58
68
|
"Circle",
|
|
59
69
|
"CongestionLevel",
|
|
60
70
|
"FeedInfo",
|
|
61
71
|
"FeedParseError",
|
|
72
|
+
"GbfsAlert",
|
|
62
73
|
"GbfsFeedHandle",
|
|
63
74
|
"GbfsVehicle",
|
|
64
75
|
"MobilityFeedsClient",
|
|
65
76
|
"MobilityFeedsError",
|
|
66
77
|
"OccupancyStatus",
|
|
67
78
|
"PickupDropOffType",
|
|
79
|
+
"PricingPlan",
|
|
68
80
|
"Route",
|
|
69
81
|
"ServiceAlert",
|
|
70
82
|
"SourceAuthenticationError",
|
|
@@ -77,9 +89,11 @@ __all__ = [
|
|
|
77
89
|
"StopArrival",
|
|
78
90
|
"StopLocationType",
|
|
79
91
|
"SystemInfo",
|
|
92
|
+
"SystemRegion",
|
|
80
93
|
"TransitFeedHandle",
|
|
81
94
|
"UpcomingTrip",
|
|
82
95
|
"VehiclePosition",
|
|
83
96
|
"VehicleStopStatus",
|
|
97
|
+
"VehicleType",
|
|
84
98
|
"WheelchairAccess",
|
|
85
99
|
]
|