nta-gtfs 0.1.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.
@@ -0,0 +1,28 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ .eggs/
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # uv
17
+ uv.lock
18
+
19
+ # Testing / coverage
20
+ .pytest_cache/
21
+ .coverage
22
+ htmlcov/
23
+ coverage.xml
24
+
25
+ # Editor
26
+ .vscode/
27
+ .idea/
28
+ *.swp
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
+
7
+ ## [0.1.0] - 2026-07-11
8
+
9
+ ### Added
10
+
11
+ - Initial release
12
+ - `GtfsRtClient` for fetching real-time GTFS-RT trip updates from NTA feeds
13
+ - `StaticGtfsClient` for downloading and querying static GTFS schedule data
14
+ - Exception hierarchy: `NtaGtfsError`, `GtfsRtAuthError`, `GtfsRtFetchError`, `GtfsRtParseError`, `StaticGtfsLoadError`
nta_gtfs-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Niall O'Callaghan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,204 @@
1
+ Metadata-Version: 2.4
2
+ Name: nta-gtfs
3
+ Version: 0.1.0
4
+ Summary: Async Python client for NTA GTFS real-time and static feeds
5
+ Project-URL: Source Code, https://github.com/nocalla/nta-gtfs
6
+ Project-URL: Bug Tracker, https://github.com/nocalla/nta-gtfs/issues
7
+ Author-email: Niall O'Callaghan <niallwocallaghan@gmail.com>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2024 Niall O'Callaghan
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: aiohttp,async,gtfs,nta,realtime,transit
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Framework :: AsyncIO
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.12
37
+ Classifier: Programming Language :: Python :: 3.13
38
+ Classifier: Topic :: Software Development :: Libraries
39
+ Requires-Python: >=3.12
40
+ Requires-Dist: aiohttp>=3.9
41
+ Requires-Dist: gtfs-realtime-bindings>=2.1.0
42
+ Description-Content-Type: text/markdown
43
+
44
+ # nta-gtfs
45
+
46
+ Async Python client for the Irish National Transport Authority (NTA) GTFS feeds.
47
+
48
+ ## What it does
49
+
50
+ `nta-gtfs` provides two async clients for working with NTA transit data. `GtfsRtClient` fetches and parses real-time trip updates from the NTA GTFS-RT protobuf feed, returning typed dataclass objects for each trip and its stop-level delay information. `StaticGtfsClient` downloads the static GTFS schedule zip, parses it entirely in memory, and exposes synchronous queries to look up scheduled departures for a given stop, route, date, and direction.
51
+
52
+ Both clients accept a caller-supplied `aiohttp.ClientSession` and raise library-specific exceptions on all error conditions. The library is under active development.
53
+
54
+ ## Installation
55
+
56
+ ```
57
+ pip install nta-gtfs
58
+ ```
59
+
60
+ ## Prerequisites
61
+
62
+ **NTA API key** — required by the GTFS-RT feed. Register at [developer.nationaltransport.ie](https://developer.nationaltransport.ie/) to obtain one.
63
+
64
+ **Static GTFS feed URL** — the URL of the NTA static GTFS zip archive. This is also available via the NTA developer portal once you have an account.
65
+
66
+ ## Usage
67
+
68
+ Both clients take a caller-supplied `aiohttp.ClientSession` — the library never creates its own. The essentials:
69
+
70
+ ```python
71
+ client = GtfsRtClient(feed_url=FEED_URL, api_key=API_KEY, session=session)
72
+ updates = await client.async_fetch_trip_updates() # -> list[TripUpdate]
73
+
74
+ client = StaticGtfsClient(static_gtfs_url=STATIC_URL, session=session)
75
+ await client.async_load() # first use; async_refresh_if_stale() on later runs
76
+ departures = client.get_scheduled_departures(
77
+ stop_id="8250DB001234", route_id="46A", direction_id=0,
78
+ operator_id=None, target_date=date.today(),
79
+ ) # -> list[ScheduledDeparture]
80
+ ```
81
+
82
+ `async_fetch_trip_updates` returns typed dataclasses: each `TripUpdate` carries a list of `StopTimeUpdate` objects with per-stop delay and absolute time information. `async_load` downloads and parses the GTFS zip, offloading the CPU-intensive work to a thread so the event loop is not blocked; `async_refresh_if_stale` only re-downloads when the data is older than `refresh_hours` (default 24).
83
+
84
+ For complete, runnable programs see the [`examples/`](examples/) directory.
85
+
86
+ ## Running the examples
87
+
88
+ The scripts in `examples/` query the live NTA API. To run them locally:
89
+
90
+ ```bash
91
+ git clone https://github.com/nocalla/nta-gtfs.git
92
+ cd nta-gtfs
93
+ uv sync
94
+
95
+ # API key from https://developer.nationaltransport.ie/
96
+ export NTA_API_KEY=your-key
97
+
98
+ # Real-time trip updates (prints a summary of the first few trips)
99
+ uv run python examples/fetch_trip_updates.py
100
+
101
+ # Scheduled departures from the static GTFS feed
102
+ # (downloads a large zip on each run — expect a wait)
103
+ uv run python examples/scheduled_departures.py 8250DB001234 46A --direction 0
104
+ uv run python examples/scheduled_departures.py --help
105
+ ```
106
+
107
+ Environment variable overrides: `NTA_FEED_URL` (GTFS-RT feed URL) and `NTA_STATIC_GTFS_URL` (static zip URL). The static example needs no API key — the zip is publicly downloadable.
108
+
109
+ ## Error handling
110
+
111
+ All exceptions inherit from `NtaGtfsError`, so a single `except NtaGtfsError:` catches every library error; catch the specific subclasses when you need to distinguish auth failures (`GtfsRtAuthError`, HTTP 401) from other fetch or parse problems.
112
+
113
+ Exception hierarchy:
114
+
115
+ ```
116
+ NtaGtfsError
117
+ ├── GtfsRtAuthError — HTTP 401 from the GTFS-RT feed
118
+ ├── GtfsRtFetchError — other HTTP or network error from the GTFS-RT feed
119
+ ├── GtfsRtParseError — response is not a valid GTFS-RT protobuf FeedMessage
120
+ └── StaticGtfsLoadError — static GTFS zip download or parse failure
121
+ ```
122
+
123
+ ## API reference
124
+
125
+ ### `GtfsRtClient(feed_url, api_key, session)`
126
+
127
+ | Method | Returns | Description |
128
+ |---|---|---|
129
+ | `async_fetch_trip_updates()` | `list[TripUpdate]` | Fetch and parse the GTFS-RT trip updates feed. |
130
+
131
+ ### `TripUpdate`
132
+
133
+ Dataclass representing a real-time update for a single trip.
134
+
135
+ | Attribute | Type | Description |
136
+ |---|---|---|
137
+ | `trip_id` | `str` | GTFS trip identifier. |
138
+ | `route_id` | `str` | GTFS route identifier. |
139
+ | `direction_id` | `str \| None` | Direction (`"0"` or `"1"`); `None` if absent in feed. |
140
+ | `start_date` | `str \| None` | Service start date (YYYYMMDD); `None` if absent. |
141
+ | `stop_time_updates` | `list[StopTimeUpdate]` | Ordered list of per-stop updates. |
142
+
143
+ ### `StopTimeUpdate`
144
+
145
+ Dataclass representing a single stop-level real-time update.
146
+
147
+ | Attribute | Type | Description |
148
+ |---|---|---|
149
+ | `stop_id` | `str` | GTFS stop identifier. |
150
+ | `arrival_delay` | `int \| None` | Arrival delay in seconds. |
151
+ | `departure_delay` | `int \| None` | Departure delay in seconds. |
152
+ | `arrival_time` | `int \| None` | Absolute arrival time as a POSIX timestamp. |
153
+ | `departure_time` | `int \| None` | Absolute departure time as a POSIX timestamp. |
154
+
155
+ ### `StaticGtfsClient(static_gtfs_url, session, refresh_hours=24)`
156
+
157
+ | Method / Property | Returns | Description |
158
+ |---|---|---|
159
+ | `async_load()` | `None` | Download and parse the GTFS zip. |
160
+ | `async_refresh_if_stale()` | `None` | Reload only when data is absent or older than `refresh_hours`. |
161
+ | `get_scheduled_departures(stop_id, route_id, direction_id, operator_id, target_date)` | `list[ScheduledDeparture]` | Return departures sorted by time for the given stop, route, and date. |
162
+ | `available` | `bool` | `True` after at least one successful load. |
163
+ | `loaded_at` | `datetime \| None` | UTC datetime of the last successful load. |
164
+
165
+ ### `ScheduledDeparture`
166
+
167
+ Named tuple representing a single scheduled departure.
168
+
169
+ | Attribute | Type | Description |
170
+ |---|---|---|
171
+ | `trip_id` | `str` | GTFS trip identifier. |
172
+ | `departure_time` | `str` | Scheduled departure time in `HH:MM` format. |
173
+ | `route_name` | `str \| None` | Route short name. |
174
+
175
+ ## Data licence, attribution and fair usage
176
+
177
+ The GTFS data served by these feeds is provided by the
178
+ [National Transport Authority (NTA)](https://www.nationaltransport.ie/) under the
179
+ [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/)
180
+ licence, subject to the
181
+ [NTA GTFS fair usage policy](https://developer.nationaltransport.ie/usagepolicy).
182
+ This library's MIT licence covers the code only, not the data.
183
+
184
+ If you use this library in a public-facing application, presentation, or
185
+ publication, the policy requires you to:
186
+
187
+ - credit the NTA as the data provider,
188
+ - link to the GTFS data source or the [NTA website](https://www.nationaltransport.ie/), and
189
+ - state that the GTFS data is provided "as is" and that the NTA is not
190
+ responsible for any errors or inaccuracies in it.
191
+
192
+ The policy also limits each API token to **one GTFS-RT request every 60
193
+ seconds**. This library does not throttle requests itself — the polling
194
+ cadence is yours to control — so make sure your application calls
195
+ `async_fetch_trip_updates` no more than once per 60 seconds per token. The
196
+ static GTFS zip changes infrequently; the default `refresh_hours=24` is well
197
+ within fair usage, and you should avoid re-downloading it more often than
198
+ needed.
199
+
200
+ ## Requirements
201
+
202
+ - Python 3.12 or later
203
+ - [aiohttp](https://docs.aiohttp.org/) 3.9 or later
204
+ - [gtfs-realtime-bindings](https://pypi.org/project/gtfs-realtime-bindings/) 1.0 or later
@@ -0,0 +1,161 @@
1
+ # nta-gtfs
2
+
3
+ Async Python client for the Irish National Transport Authority (NTA) GTFS feeds.
4
+
5
+ ## What it does
6
+
7
+ `nta-gtfs` provides two async clients for working with NTA transit data. `GtfsRtClient` fetches and parses real-time trip updates from the NTA GTFS-RT protobuf feed, returning typed dataclass objects for each trip and its stop-level delay information. `StaticGtfsClient` downloads the static GTFS schedule zip, parses it entirely in memory, and exposes synchronous queries to look up scheduled departures for a given stop, route, date, and direction.
8
+
9
+ Both clients accept a caller-supplied `aiohttp.ClientSession` and raise library-specific exceptions on all error conditions. The library is under active development.
10
+
11
+ ## Installation
12
+
13
+ ```
14
+ pip install nta-gtfs
15
+ ```
16
+
17
+ ## Prerequisites
18
+
19
+ **NTA API key** — required by the GTFS-RT feed. Register at [developer.nationaltransport.ie](https://developer.nationaltransport.ie/) to obtain one.
20
+
21
+ **Static GTFS feed URL** — the URL of the NTA static GTFS zip archive. This is also available via the NTA developer portal once you have an account.
22
+
23
+ ## Usage
24
+
25
+ Both clients take a caller-supplied `aiohttp.ClientSession` — the library never creates its own. The essentials:
26
+
27
+ ```python
28
+ client = GtfsRtClient(feed_url=FEED_URL, api_key=API_KEY, session=session)
29
+ updates = await client.async_fetch_trip_updates() # -> list[TripUpdate]
30
+
31
+ client = StaticGtfsClient(static_gtfs_url=STATIC_URL, session=session)
32
+ await client.async_load() # first use; async_refresh_if_stale() on later runs
33
+ departures = client.get_scheduled_departures(
34
+ stop_id="8250DB001234", route_id="46A", direction_id=0,
35
+ operator_id=None, target_date=date.today(),
36
+ ) # -> list[ScheduledDeparture]
37
+ ```
38
+
39
+ `async_fetch_trip_updates` returns typed dataclasses: each `TripUpdate` carries a list of `StopTimeUpdate` objects with per-stop delay and absolute time information. `async_load` downloads and parses the GTFS zip, offloading the CPU-intensive work to a thread so the event loop is not blocked; `async_refresh_if_stale` only re-downloads when the data is older than `refresh_hours` (default 24).
40
+
41
+ For complete, runnable programs see the [`examples/`](examples/) directory.
42
+
43
+ ## Running the examples
44
+
45
+ The scripts in `examples/` query the live NTA API. To run them locally:
46
+
47
+ ```bash
48
+ git clone https://github.com/nocalla/nta-gtfs.git
49
+ cd nta-gtfs
50
+ uv sync
51
+
52
+ # API key from https://developer.nationaltransport.ie/
53
+ export NTA_API_KEY=your-key
54
+
55
+ # Real-time trip updates (prints a summary of the first few trips)
56
+ uv run python examples/fetch_trip_updates.py
57
+
58
+ # Scheduled departures from the static GTFS feed
59
+ # (downloads a large zip on each run — expect a wait)
60
+ uv run python examples/scheduled_departures.py 8250DB001234 46A --direction 0
61
+ uv run python examples/scheduled_departures.py --help
62
+ ```
63
+
64
+ Environment variable overrides: `NTA_FEED_URL` (GTFS-RT feed URL) and `NTA_STATIC_GTFS_URL` (static zip URL). The static example needs no API key — the zip is publicly downloadable.
65
+
66
+ ## Error handling
67
+
68
+ All exceptions inherit from `NtaGtfsError`, so a single `except NtaGtfsError:` catches every library error; catch the specific subclasses when you need to distinguish auth failures (`GtfsRtAuthError`, HTTP 401) from other fetch or parse problems.
69
+
70
+ Exception hierarchy:
71
+
72
+ ```
73
+ NtaGtfsError
74
+ ├── GtfsRtAuthError — HTTP 401 from the GTFS-RT feed
75
+ ├── GtfsRtFetchError — other HTTP or network error from the GTFS-RT feed
76
+ ├── GtfsRtParseError — response is not a valid GTFS-RT protobuf FeedMessage
77
+ └── StaticGtfsLoadError — static GTFS zip download or parse failure
78
+ ```
79
+
80
+ ## API reference
81
+
82
+ ### `GtfsRtClient(feed_url, api_key, session)`
83
+
84
+ | Method | Returns | Description |
85
+ |---|---|---|
86
+ | `async_fetch_trip_updates()` | `list[TripUpdate]` | Fetch and parse the GTFS-RT trip updates feed. |
87
+
88
+ ### `TripUpdate`
89
+
90
+ Dataclass representing a real-time update for a single trip.
91
+
92
+ | Attribute | Type | Description |
93
+ |---|---|---|
94
+ | `trip_id` | `str` | GTFS trip identifier. |
95
+ | `route_id` | `str` | GTFS route identifier. |
96
+ | `direction_id` | `str \| None` | Direction (`"0"` or `"1"`); `None` if absent in feed. |
97
+ | `start_date` | `str \| None` | Service start date (YYYYMMDD); `None` if absent. |
98
+ | `stop_time_updates` | `list[StopTimeUpdate]` | Ordered list of per-stop updates. |
99
+
100
+ ### `StopTimeUpdate`
101
+
102
+ Dataclass representing a single stop-level real-time update.
103
+
104
+ | Attribute | Type | Description |
105
+ |---|---|---|
106
+ | `stop_id` | `str` | GTFS stop identifier. |
107
+ | `arrival_delay` | `int \| None` | Arrival delay in seconds. |
108
+ | `departure_delay` | `int \| None` | Departure delay in seconds. |
109
+ | `arrival_time` | `int \| None` | Absolute arrival time as a POSIX timestamp. |
110
+ | `departure_time` | `int \| None` | Absolute departure time as a POSIX timestamp. |
111
+
112
+ ### `StaticGtfsClient(static_gtfs_url, session, refresh_hours=24)`
113
+
114
+ | Method / Property | Returns | Description |
115
+ |---|---|---|
116
+ | `async_load()` | `None` | Download and parse the GTFS zip. |
117
+ | `async_refresh_if_stale()` | `None` | Reload only when data is absent or older than `refresh_hours`. |
118
+ | `get_scheduled_departures(stop_id, route_id, direction_id, operator_id, target_date)` | `list[ScheduledDeparture]` | Return departures sorted by time for the given stop, route, and date. |
119
+ | `available` | `bool` | `True` after at least one successful load. |
120
+ | `loaded_at` | `datetime \| None` | UTC datetime of the last successful load. |
121
+
122
+ ### `ScheduledDeparture`
123
+
124
+ Named tuple representing a single scheduled departure.
125
+
126
+ | Attribute | Type | Description |
127
+ |---|---|---|
128
+ | `trip_id` | `str` | GTFS trip identifier. |
129
+ | `departure_time` | `str` | Scheduled departure time in `HH:MM` format. |
130
+ | `route_name` | `str \| None` | Route short name. |
131
+
132
+ ## Data licence, attribution and fair usage
133
+
134
+ The GTFS data served by these feeds is provided by the
135
+ [National Transport Authority (NTA)](https://www.nationaltransport.ie/) under the
136
+ [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/)
137
+ licence, subject to the
138
+ [NTA GTFS fair usage policy](https://developer.nationaltransport.ie/usagepolicy).
139
+ This library's MIT licence covers the code only, not the data.
140
+
141
+ If you use this library in a public-facing application, presentation, or
142
+ publication, the policy requires you to:
143
+
144
+ - credit the NTA as the data provider,
145
+ - link to the GTFS data source or the [NTA website](https://www.nationaltransport.ie/), and
146
+ - state that the GTFS data is provided "as is" and that the NTA is not
147
+ responsible for any errors or inaccuracies in it.
148
+
149
+ The policy also limits each API token to **one GTFS-RT request every 60
150
+ seconds**. This library does not throttle requests itself — the polling
151
+ cadence is yours to control — so make sure your application calls
152
+ `async_fetch_trip_updates` no more than once per 60 seconds per token. The
153
+ static GTFS zip changes infrequently; the default `refresh_hours=24` is well
154
+ within fair usage, and you should avoid re-downloading it more often than
155
+ needed.
156
+
157
+ ## Requirements
158
+
159
+ - Python 3.12 or later
160
+ - [aiohttp](https://docs.aiohttp.org/) 3.9 or later
161
+ - [gtfs-realtime-bindings](https://pypi.org/project/gtfs-realtime-bindings/) 1.0 or later
@@ -0,0 +1,93 @@
1
+ """Fetch live trip updates from the NTA GTFS-RT feed and print a summary.
2
+
3
+ Usage:
4
+ export NTA_API_KEY=your-key # from https://developer.nationaltransport.ie/
5
+ uv run python examples/fetch_trip_updates.py
6
+
7
+ Environment variables:
8
+ NTA_API_KEY: Required. NTA developer API key (sent only as a header).
9
+ NTA_FEED_URL: Optional. Overrides the default TripUpdates feed URL.
10
+ """
11
+
12
+ import asyncio
13
+ import os
14
+ import sys
15
+ from datetime import UTC, datetime
16
+
17
+ import aiohttp
18
+
19
+ from nta_gtfs import GtfsRtClient, TripUpdate
20
+
21
+ DEFAULT_FEED_URL = "https://api.nationaltransport.ie/gtfsr/v2/TripUpdates"
22
+ MAX_TRIPS_SHOWN = 5
23
+ MAX_STOPS_PER_TRIP = 3
24
+
25
+
26
+ def _format_timestamp(posix_time: int | None) -> str:
27
+ """Format a POSIX timestamp as a local HH:MM string.
28
+
29
+ Args:
30
+ posix_time: POSIX timestamp from the feed, or ``None``.
31
+
32
+ Returns:
33
+ Local ``HH:MM`` string, or ``"--:--"`` when the timestamp is absent.
34
+ """
35
+ if posix_time is None:
36
+ return "--:--"
37
+ return datetime.fromtimestamp(posix_time, tz=UTC).astimezone().strftime("%H:%M")
38
+
39
+
40
+ def _print_trip(trip: TripUpdate) -> None:
41
+ """Print a one-trip summary with its first few stop-level updates.
42
+
43
+ Args:
44
+ trip: Parsed trip update to summarise.
45
+ """
46
+ direction = trip.direction_id if trip.direction_id is not None else "?"
47
+ print(
48
+ f"Trip {trip.trip_id} | route {trip.route_id} | direction {direction}"
49
+ f" | {len(trip.stop_time_updates)} stop update(s)"
50
+ )
51
+ for stu in trip.stop_time_updates[:MAX_STOPS_PER_TRIP]:
52
+ delay = f"{stu.arrival_delay:+d}s" if stu.arrival_delay is not None else "n/a"
53
+ print(
54
+ f" stop {stu.stop_id}: arrival {_format_timestamp(stu.arrival_time)}"
55
+ f" (delay {delay})"
56
+ )
57
+ remaining = len(trip.stop_time_updates) - MAX_STOPS_PER_TRIP
58
+ if remaining > 0:
59
+ print(f" ... and {remaining} more stop(s)")
60
+
61
+
62
+ async def main() -> int:
63
+ """Fetch trip updates from the live feed and print a readable summary.
64
+
65
+ Returns:
66
+ Process exit code: ``0`` on success, ``1`` on configuration error.
67
+ """
68
+ api_key = os.environ.get("NTA_API_KEY")
69
+ if not api_key:
70
+ print(
71
+ "Error: the NTA_API_KEY environment variable is not set.\n"
72
+ "Get a key from https://developer.nationaltransport.ie/ and run:\n"
73
+ " export NTA_API_KEY=your-key",
74
+ file=sys.stderr,
75
+ )
76
+ return 1
77
+
78
+ feed_url = os.environ.get("NTA_FEED_URL", DEFAULT_FEED_URL)
79
+
80
+ async with aiohttp.ClientSession() as session:
81
+ client = GtfsRtClient(feed_url=feed_url, api_key=api_key, session=session)
82
+ updates = await client.async_fetch_trip_updates()
83
+
84
+ print(f"Fetched {len(updates)} trip update(s) from {feed_url}\n")
85
+ for trip in updates[:MAX_TRIPS_SHOWN]:
86
+ _print_trip(trip)
87
+ if len(updates) > MAX_TRIPS_SHOWN:
88
+ print(f"\n... and {len(updates) - MAX_TRIPS_SHOWN} more trip(s) not shown")
89
+ return 0
90
+
91
+
92
+ if __name__ == "__main__":
93
+ sys.exit(asyncio.run(main()))
@@ -0,0 +1,106 @@
1
+ """Load the NTA static GTFS schedule and print departures for a stop.
2
+
3
+ Usage:
4
+ uv run python examples/scheduled_departures.py 8250DB001234 46A --direction 0
5
+
6
+ The static GTFS zip is large (tens of MB); the first load takes a while.
7
+
8
+ Environment variables:
9
+ NTA_STATIC_GTFS_URL: Optional. Overrides the default static GTFS zip URL.
10
+ """
11
+
12
+ import argparse
13
+ import asyncio
14
+ import os
15
+ import sys
16
+ from datetime import date
17
+
18
+ import aiohttp
19
+
20
+ from nta_gtfs import StaticGtfsClient
21
+
22
+ DEFAULT_STATIC_URL = "https://www.transportforireland.ie/transitData/Data/GTFS_All.zip"
23
+
24
+
25
+ def _parse_args() -> argparse.Namespace:
26
+ """Parse command-line arguments for the scheduled departures query.
27
+
28
+ Returns:
29
+ Parsed arguments namespace with ``stop_id``, ``route_id``,
30
+ ``direction``, ``operator`` and ``date`` attributes.
31
+ """
32
+ parser = argparse.ArgumentParser(
33
+ description=(
34
+ "Download the NTA static GTFS schedule and print scheduled "
35
+ "departures for a stop and route."
36
+ ),
37
+ epilog=(
38
+ "Example: uv run python examples/scheduled_departures.py "
39
+ "8250DB001234 46A --direction 0"
40
+ ),
41
+ )
42
+ parser.add_argument("stop_id", help="GTFS stop ID, e.g. 8250DB001234")
43
+ parser.add_argument("route_id", help="route short name, e.g. 46A")
44
+ parser.add_argument(
45
+ "--direction",
46
+ type=int,
47
+ choices=(0, 1),
48
+ default=None,
49
+ help="GTFS direction_id filter (0 or 1); omit for both directions",
50
+ )
51
+ parser.add_argument(
52
+ "--operator",
53
+ default=None,
54
+ help="GTFS agency ID filter; omit for all operators",
55
+ )
56
+ parser.add_argument(
57
+ "--date",
58
+ type=date.fromisoformat,
59
+ default=None,
60
+ metavar="YYYY-MM-DD",
61
+ help="date to query (default: today)",
62
+ )
63
+ return parser.parse_args()
64
+
65
+
66
+ async def main() -> int:
67
+ """Load the static GTFS feed and print departures for the requested stop.
68
+
69
+ Returns:
70
+ Process exit code: ``0`` on success (including no matches found).
71
+ """
72
+ args = _parse_args()
73
+ static_url = os.environ.get("NTA_STATIC_GTFS_URL", DEFAULT_STATIC_URL)
74
+ target_date = args.date if args.date is not None else date.today()
75
+
76
+ print(f"Downloading and parsing static GTFS from {static_url} ...")
77
+ async with aiohttp.ClientSession() as session:
78
+ client = StaticGtfsClient(static_gtfs_url=static_url, session=session)
79
+ await client.async_load()
80
+
81
+ departures = client.get_scheduled_departures(
82
+ stop_id=args.stop_id,
83
+ route_id=args.route_id,
84
+ direction_id=args.direction,
85
+ operator_id=args.operator,
86
+ target_date=target_date,
87
+ )
88
+
89
+ if not departures:
90
+ print(
91
+ f"No departures found for stop {args.stop_id}, route {args.route_id} "
92
+ f"on {target_date.isoformat()}."
93
+ )
94
+ return 0
95
+
96
+ print(
97
+ f"\n{len(departures)} departure(s) for stop {args.stop_id}, "
98
+ f"route {args.route_id} on {target_date.isoformat()}:\n"
99
+ )
100
+ for dep in departures:
101
+ print(f" {dep.departure_time} route {dep.route_name} trip {dep.trip_id}")
102
+ return 0
103
+
104
+
105
+ if __name__ == "__main__":
106
+ sys.exit(asyncio.run(main()))
@@ -0,0 +1,32 @@
1
+ """NTA GTFS async client library."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("nta-gtfs")
7
+ except PackageNotFoundError:
8
+ __version__ = "unknown"
9
+
10
+ from nta_gtfs.exceptions import (
11
+ GtfsRtAuthError,
12
+ GtfsRtFetchError,
13
+ GtfsRtParseError,
14
+ NtaGtfsError,
15
+ StaticGtfsLoadError,
16
+ )
17
+ from nta_gtfs.gtfs_rt import GtfsRtClient, StopTimeUpdate, TripUpdate
18
+ from nta_gtfs.static_gtfs import ScheduledDeparture, StaticGtfsClient
19
+
20
+ __all__ = [
21
+ "GtfsRtAuthError",
22
+ "GtfsRtClient",
23
+ "GtfsRtFetchError",
24
+ "GtfsRtParseError",
25
+ "NtaGtfsError",
26
+ "ScheduledDeparture",
27
+ "StaticGtfsClient",
28
+ "StaticGtfsLoadError",
29
+ "StopTimeUpdate",
30
+ "TripUpdate",
31
+ "__version__",
32
+ ]
@@ -0,0 +1,21 @@
1
+ """Exception types for the nta_gtfs library."""
2
+
3
+
4
+ class NtaGtfsError(Exception):
5
+ """Base class for all nta_gtfs library errors."""
6
+
7
+
8
+ class GtfsRtAuthError(NtaGtfsError):
9
+ """Raised when the GTFS-RT feed returns HTTP 401."""
10
+
11
+
12
+ class GtfsRtFetchError(NtaGtfsError):
13
+ """Raised on a non-401 HTTP error from the GTFS-RT feed or a network failure."""
14
+
15
+
16
+ class GtfsRtParseError(NtaGtfsError):
17
+ """Raised when the GTFS-RT response cannot be parsed as a valid FeedMessage."""
18
+
19
+
20
+ class StaticGtfsLoadError(NtaGtfsError):
21
+ """Raised when the static GTFS zip cannot be downloaded or parsed."""