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.
- nta_gtfs-0.1.0/.gitignore +28 -0
- nta_gtfs-0.1.0/CHANGELOG.md +14 -0
- nta_gtfs-0.1.0/LICENSE +21 -0
- nta_gtfs-0.1.0/PKG-INFO +204 -0
- nta_gtfs-0.1.0/README.md +161 -0
- nta_gtfs-0.1.0/examples/fetch_trip_updates.py +93 -0
- nta_gtfs-0.1.0/examples/scheduled_departures.py +106 -0
- nta_gtfs-0.1.0/nta_gtfs/__init__.py +32 -0
- nta_gtfs-0.1.0/nta_gtfs/exceptions.py +21 -0
- nta_gtfs-0.1.0/nta_gtfs/gtfs_rt.py +219 -0
- nta_gtfs-0.1.0/nta_gtfs/py.typed +0 -0
- nta_gtfs-0.1.0/nta_gtfs/static_gtfs.py +431 -0
- nta_gtfs-0.1.0/pyproject.toml +57 -0
- nta_gtfs-0.1.0/tests/__init__.py +0 -0
- nta_gtfs-0.1.0/tests/test_gtfs_rt_client.py +547 -0
- nta_gtfs-0.1.0/tests/test_static_gtfs_client.py +921 -0
|
@@ -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.
|
nta_gtfs-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
nta_gtfs-0.1.0/README.md
ADDED
|
@@ -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."""
|