gtfs-hub 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.
- gtfs_hub-0.1.0/.gitignore +10 -0
- gtfs_hub-0.1.0/CHANGELOG.md +14 -0
- gtfs_hub-0.1.0/LICENSE +21 -0
- gtfs_hub-0.1.0/PKG-INFO +146 -0
- gtfs_hub-0.1.0/README.md +120 -0
- gtfs_hub-0.1.0/pyproject.toml +75 -0
- gtfs_hub-0.1.0/scripts/check_agnostic.py +28 -0
- gtfs_hub-0.1.0/src/gtfs_hub/__init__.py +91 -0
- gtfs_hub-0.1.0/src/gtfs_hub/catalogue.py +290 -0
- gtfs_hub-0.1.0/src/gtfs_hub/departures.py +272 -0
- gtfs_hub-0.1.0/src/gtfs_hub/discovery.py +33 -0
- gtfs_hub-0.1.0/src/gtfs_hub/download.py +44 -0
- gtfs_hub-0.1.0/src/gtfs_hub/errors.py +29 -0
- gtfs_hub-0.1.0/src/gtfs_hub/generic.py +52 -0
- gtfs_hub-0.1.0/src/gtfs_hub/hub.py +432 -0
- gtfs_hub-0.1.0/src/gtfs_hub/importer.py +538 -0
- gtfs_hub-0.1.0/src/gtfs_hub/models.py +220 -0
- gtfs_hub-0.1.0/src/gtfs_hub/provider.py +106 -0
- gtfs_hub-0.1.0/src/gtfs_hub/py.typed +0 -0
- gtfs_hub-0.1.0/src/gtfs_hub/realtime.py +155 -0
- gtfs_hub-0.1.0/src/gtfs_hub/schema.py +152 -0
- gtfs_hub-0.1.0/src/gtfs_hub/shapes.py +91 -0
- gtfs_hub-0.1.0/src/gtfs_hub/snapshots.py +32 -0
- gtfs_hub-0.1.0/src/gtfs_hub/text.py +11 -0
- gtfs_hub-0.1.0/tests/conftest.py +132 -0
- gtfs_hub-0.1.0/tests/test_catalogue.py +220 -0
- gtfs_hub-0.1.0/tests/test_contract.py +123 -0
- gtfs_hub-0.1.0/tests/test_departures.py +225 -0
- gtfs_hub-0.1.0/tests/test_generic.py +49 -0
- gtfs_hub-0.1.0/tests/test_import.py +371 -0
- gtfs_hub-0.1.0/tests/test_package.py +5 -0
- gtfs_hub-0.1.0/tests/test_readme.py +52 -0
- gtfs_hub-0.1.0/tests/test_realtime.py +385 -0
- gtfs_hub-0.1.0/tests/test_shapes.py +314 -0
- gtfs_hub-0.1.0/tests/test_snapshots.py +397 -0
- gtfs_hub-0.1.0/tests/test_vehicles.py +174 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are listed here. Versions follow [Semantic Versioning](https://semver.org/); below 1.0, a breaking change to the API, the provider contract or the database schema bumps the minor version.
|
|
4
|
+
|
|
5
|
+
## 0.1.0 - 2026-11-11
|
|
6
|
+
|
|
7
|
+
First public release.
|
|
8
|
+
|
|
9
|
+
- Local SQLite copy of a GTFS schedule, sized by the selected lines, with snapshots, coverage checks and safe rebuilds.
|
|
10
|
+
- Departures joined with GTFS Realtime trip updates, including night service past 24:00, cancellations and skipped stops.
|
|
11
|
+
- Vehicle positions of selected lines, and service alerts.
|
|
12
|
+
- Route shapes of selected lines, and locating a position on a shape.
|
|
13
|
+
- `GenericProvider` for plain URLs, and the `gtfs_hub.providers` entry point group for provider packages.
|
|
14
|
+
- Typed errors deriving from `GtfsError`.
|
gtfs_hub-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 grozycki
|
|
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.
|
gtfs_hub-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: gtfs-hub
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Provider-agnostic GTFS schedule library for low-memory hardware
|
|
5
|
+
Project-URL: Homepage, https://github.com/grozycki/gtfs-hub
|
|
6
|
+
Project-URL: Issues, https://github.com/grozycki/gtfs-hub/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/grozycki/gtfs-hub/blob/main/CHANGELOG.md
|
|
8
|
+
Author: grozycki
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: gtfs,gtfs-realtime,public-transport,timetable,transit
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.13
|
|
23
|
+
Requires-Dist: aiohttp>=3.10
|
|
24
|
+
Requires-Dist: gtfs-realtime-bindings>=1.0
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# gtfs-hub
|
|
28
|
+
|
|
29
|
+
Provider-agnostic GTFS schedule library for low-memory hardware. It keeps a local SQLite copy of a schedule, sized by the lines you select rather than by the size of the feed.
|
|
30
|
+
|
|
31
|
+
Requires Python 3.13 or newer.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install gtfs-hub
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Versions follow [Semantic Versioning](https://semver.org/); below 1.0, a breaking change to the API, the provider contract or the database schema bumps the minor version. See the [changelog](CHANGELOG.md).
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
Any standard source works from plain URLs, through `GenericProvider`. Pass your own `aiohttp` session: the library never creates one.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from pathlib import Path
|
|
47
|
+
|
|
48
|
+
from aiohttp import ClientSession
|
|
49
|
+
|
|
50
|
+
from gtfs_hub import GenericProvider, GtfsHub, LineKey
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def main(url: str, database: Path) -> None:
|
|
54
|
+
async with ClientSession() as session:
|
|
55
|
+
hub = GtfsHub(GenericProvider([url]), database, session)
|
|
56
|
+
|
|
57
|
+
# The first sync with no lines fills the searchable catalogue.
|
|
58
|
+
await hub.async_sync([])
|
|
59
|
+
lines = await hub.async_search_lines("11")
|
|
60
|
+
|
|
61
|
+
# Timetables are stored only for selected lines.
|
|
62
|
+
selection = [line.key for line in lines]
|
|
63
|
+
await hub.async_sync(selection)
|
|
64
|
+
|
|
65
|
+
for key in selection:
|
|
66
|
+
stops = await hub.async_stops_of_line(key)
|
|
67
|
+
print(LineKey(key.route_type, key.short_name), [stop.name for stop in stops])
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Call `async_sync` with the full selection whenever the source may have changed or the selection did. Lines that are new are imported, lines that are gone are removed, and unchanged files are not downloaded. On failure it raises a `GtfsError`, and the previous database stays in use. The database is a cache, so it is safe to delete: the next sync rebuilds it.
|
|
71
|
+
|
|
72
|
+
## Departures
|
|
73
|
+
|
|
74
|
+
Fetch realtime once per poll, and pass it to each departures call. A failed fetch only means passing `None`: the schedule still answers.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from gtfs_hub import GtfsError, GtfsHub
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def show_departures(hub: GtfsHub, stop_key: str) -> None:
|
|
81
|
+
try:
|
|
82
|
+
realtime = await hub.async_fetch_realtime()
|
|
83
|
+
except GtfsError:
|
|
84
|
+
realtime = None
|
|
85
|
+
for departure in await hub.async_departures(stop_key, realtime=realtime, limit=5):
|
|
86
|
+
print(departure.line.short_name, departure.headsign, departure.expected, departure.status)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Departures cover the lines stored by the last sync, on every service date reaching the period, including night service past 24:00. Times are in UTC. `scheduled` is always set, while `predicted` and `delay` stay `None` without a usable update, so "no data" never reads as "on time". Stops a running trip has passed give no departure, and points where riders cannot board give none either. Realtime older than `max_age` (5 minutes by default) is ignored, and `RealtimeData.is_stale` tells a consumer so. When no snapshot covers today, `async_departures` raises `GtfsNoDataError`.
|
|
90
|
+
|
|
91
|
+
Trips in `frequencies.txt` are not supported and are left out, with a log line.
|
|
92
|
+
|
|
93
|
+
## Shapes
|
|
94
|
+
|
|
95
|
+
When a source publishes `shapes.txt`, the paths of selected lines are stored with their timetables. `async_line_shapes(key, service_date=None)` returns each distinct path of a line, with its points in order, its direction and the most common headsign of its trips. With a service date, only the snapshot serving that date counts, so other days' variants stay out. A broken `shapes.txt` costs only the paths, never the timetables. Vehicles carry the `shape` of their trip.
|
|
96
|
+
|
|
97
|
+
`locate(shape, latitude, longitude, after=None)` places a position on a path: metres along it, metres off it, and the bearing of the path there. A position more than 60 m off has no location. Pass the previous `along` of the same trip as `after`, so a path that runs one street both ways resolves to the pass ahead; without it, such a position has no location rather than a guess.
|
|
98
|
+
|
|
99
|
+
## Generic provider
|
|
100
|
+
|
|
101
|
+
| Argument | Meaning |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `schedule_urls` | One or more ZIP files, each treated as one snapshot |
|
|
104
|
+
| `trip_updates_url`, `vehicle_positions_url`, `alerts_url` | Optional realtime endpoints |
|
|
105
|
+
| `headers` | Optional headers for realtime requests |
|
|
106
|
+
| `stop_key` | `stop_id` by default, or `stop_code` when that is the stable field |
|
|
107
|
+
| `name`, `attribution` | Display name, and the licence text consumers must show |
|
|
108
|
+
|
|
109
|
+
It reports no capabilities and no vehicle identity until a source has been surveyed.
|
|
110
|
+
|
|
111
|
+
## Keys
|
|
112
|
+
|
|
113
|
+
- **Lines** are `LineKey(route_type, short_name)`. Store this, never a `route_id`.
|
|
114
|
+
- **Stops** are the value of the provider's `stop_key` field. One key can resolve to several `stop_id` values.
|
|
115
|
+
- `Line.url` and `Stop.url` carry `route_url` and `stop_url` when the source fills them.
|
|
116
|
+
- Empty results from `async_resolve_line` and `async_resolve_stop` mean the key is in no current snapshot.
|
|
117
|
+
|
|
118
|
+
## Coverage
|
|
119
|
+
|
|
120
|
+
`async_snapshot_for(date)` picks the newest snapshot for a service date. `async_missing_dates(start, end)` reports dates no snapshot covers, which differ from dates without service.
|
|
121
|
+
|
|
122
|
+
## Errors
|
|
123
|
+
|
|
124
|
+
| Error | Meaning |
|
|
125
|
+
|---|---|
|
|
126
|
+
| `GtfsConnectionError` | Temporary failure: retry later |
|
|
127
|
+
| `GtfsRateLimitError` | Slow down, with `retry_after` in seconds when known |
|
|
128
|
+
| `GtfsAuthError` | The source rejected the credentials |
|
|
129
|
+
| `GtfsFeedError` | Bad or unusable data |
|
|
130
|
+
| `GtfsNoDataError` | No usable database yet: run a sync |
|
|
131
|
+
|
|
132
|
+
All derive from `GtfsError`.
|
|
133
|
+
|
|
134
|
+
## Writing a provider
|
|
135
|
+
|
|
136
|
+
A package is needed only when finding or reading the data takes code. Subclass `BaseProvider`, implement `async_list_static_feeds`, and register a factory in the `gtfs_hub.providers` entry point group, with the provider key as its name.
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
uv sync
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
uv run pytest
|
|
146
|
+
```
|
gtfs_hub-0.1.0/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# gtfs-hub
|
|
2
|
+
|
|
3
|
+
Provider-agnostic GTFS schedule library for low-memory hardware. It keeps a local SQLite copy of a schedule, sized by the lines you select rather than by the size of the feed.
|
|
4
|
+
|
|
5
|
+
Requires Python 3.13 or newer.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install gtfs-hub
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Versions follow [Semantic Versioning](https://semver.org/); below 1.0, a breaking change to the API, the provider contract or the database schema bumps the minor version. See the [changelog](CHANGELOG.md).
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Any standard source works from plain URLs, through `GenericProvider`. Pass your own `aiohttp` session: the library never creates one.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from aiohttp import ClientSession
|
|
23
|
+
|
|
24
|
+
from gtfs_hub import GenericProvider, GtfsHub, LineKey
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def main(url: str, database: Path) -> None:
|
|
28
|
+
async with ClientSession() as session:
|
|
29
|
+
hub = GtfsHub(GenericProvider([url]), database, session)
|
|
30
|
+
|
|
31
|
+
# The first sync with no lines fills the searchable catalogue.
|
|
32
|
+
await hub.async_sync([])
|
|
33
|
+
lines = await hub.async_search_lines("11")
|
|
34
|
+
|
|
35
|
+
# Timetables are stored only for selected lines.
|
|
36
|
+
selection = [line.key for line in lines]
|
|
37
|
+
await hub.async_sync(selection)
|
|
38
|
+
|
|
39
|
+
for key in selection:
|
|
40
|
+
stops = await hub.async_stops_of_line(key)
|
|
41
|
+
print(LineKey(key.route_type, key.short_name), [stop.name for stop in stops])
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Call `async_sync` with the full selection whenever the source may have changed or the selection did. Lines that are new are imported, lines that are gone are removed, and unchanged files are not downloaded. On failure it raises a `GtfsError`, and the previous database stays in use. The database is a cache, so it is safe to delete: the next sync rebuilds it.
|
|
45
|
+
|
|
46
|
+
## Departures
|
|
47
|
+
|
|
48
|
+
Fetch realtime once per poll, and pass it to each departures call. A failed fetch only means passing `None`: the schedule still answers.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from gtfs_hub import GtfsError, GtfsHub
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def show_departures(hub: GtfsHub, stop_key: str) -> None:
|
|
55
|
+
try:
|
|
56
|
+
realtime = await hub.async_fetch_realtime()
|
|
57
|
+
except GtfsError:
|
|
58
|
+
realtime = None
|
|
59
|
+
for departure in await hub.async_departures(stop_key, realtime=realtime, limit=5):
|
|
60
|
+
print(departure.line.short_name, departure.headsign, departure.expected, departure.status)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Departures cover the lines stored by the last sync, on every service date reaching the period, including night service past 24:00. Times are in UTC. `scheduled` is always set, while `predicted` and `delay` stay `None` without a usable update, so "no data" never reads as "on time". Stops a running trip has passed give no departure, and points where riders cannot board give none either. Realtime older than `max_age` (5 minutes by default) is ignored, and `RealtimeData.is_stale` tells a consumer so. When no snapshot covers today, `async_departures` raises `GtfsNoDataError`.
|
|
64
|
+
|
|
65
|
+
Trips in `frequencies.txt` are not supported and are left out, with a log line.
|
|
66
|
+
|
|
67
|
+
## Shapes
|
|
68
|
+
|
|
69
|
+
When a source publishes `shapes.txt`, the paths of selected lines are stored with their timetables. `async_line_shapes(key, service_date=None)` returns each distinct path of a line, with its points in order, its direction and the most common headsign of its trips. With a service date, only the snapshot serving that date counts, so other days' variants stay out. A broken `shapes.txt` costs only the paths, never the timetables. Vehicles carry the `shape` of their trip.
|
|
70
|
+
|
|
71
|
+
`locate(shape, latitude, longitude, after=None)` places a position on a path: metres along it, metres off it, and the bearing of the path there. A position more than 60 m off has no location. Pass the previous `along` of the same trip as `after`, so a path that runs one street both ways resolves to the pass ahead; without it, such a position has no location rather than a guess.
|
|
72
|
+
|
|
73
|
+
## Generic provider
|
|
74
|
+
|
|
75
|
+
| Argument | Meaning |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `schedule_urls` | One or more ZIP files, each treated as one snapshot |
|
|
78
|
+
| `trip_updates_url`, `vehicle_positions_url`, `alerts_url` | Optional realtime endpoints |
|
|
79
|
+
| `headers` | Optional headers for realtime requests |
|
|
80
|
+
| `stop_key` | `stop_id` by default, or `stop_code` when that is the stable field |
|
|
81
|
+
| `name`, `attribution` | Display name, and the licence text consumers must show |
|
|
82
|
+
|
|
83
|
+
It reports no capabilities and no vehicle identity until a source has been surveyed.
|
|
84
|
+
|
|
85
|
+
## Keys
|
|
86
|
+
|
|
87
|
+
- **Lines** are `LineKey(route_type, short_name)`. Store this, never a `route_id`.
|
|
88
|
+
- **Stops** are the value of the provider's `stop_key` field. One key can resolve to several `stop_id` values.
|
|
89
|
+
- `Line.url` and `Stop.url` carry `route_url` and `stop_url` when the source fills them.
|
|
90
|
+
- Empty results from `async_resolve_line` and `async_resolve_stop` mean the key is in no current snapshot.
|
|
91
|
+
|
|
92
|
+
## Coverage
|
|
93
|
+
|
|
94
|
+
`async_snapshot_for(date)` picks the newest snapshot for a service date. `async_missing_dates(start, end)` reports dates no snapshot covers, which differ from dates without service.
|
|
95
|
+
|
|
96
|
+
## Errors
|
|
97
|
+
|
|
98
|
+
| Error | Meaning |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `GtfsConnectionError` | Temporary failure: retry later |
|
|
101
|
+
| `GtfsRateLimitError` | Slow down, with `retry_after` in seconds when known |
|
|
102
|
+
| `GtfsAuthError` | The source rejected the credentials |
|
|
103
|
+
| `GtfsFeedError` | Bad or unusable data |
|
|
104
|
+
| `GtfsNoDataError` | No usable database yet: run a sync |
|
|
105
|
+
|
|
106
|
+
All derive from `GtfsError`.
|
|
107
|
+
|
|
108
|
+
## Writing a provider
|
|
109
|
+
|
|
110
|
+
A package is needed only when finding or reading the data takes code. Subclass `BaseProvider`, implement `async_list_static_feeds`, and register a factory in the `gtfs_hub.providers` entry point group, with the provider key as its name.
|
|
111
|
+
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
uv sync
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
uv run pytest
|
|
120
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gtfs-hub"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Provider-agnostic GTFS schedule library for low-memory hardware"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.13"
|
|
12
|
+
authors = [{ name = "grozycki" }]
|
|
13
|
+
keywords = ["gtfs", "gtfs-realtime", "public-transport", "transit", "timetable"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Framework :: AsyncIO",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
23
|
+
"Topic :: Software Development :: Libraries",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
dependencies = ["aiohttp>=3.10", "gtfs-realtime-bindings>=1.0"]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/grozycki/gtfs-hub"
|
|
30
|
+
Issues = "https://github.com/grozycki/gtfs-hub/issues"
|
|
31
|
+
Changelog = "https://github.com/grozycki/gtfs-hub/blob/main/CHANGELOG.md"
|
|
32
|
+
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
dev = [
|
|
35
|
+
"mypy>=1.13",
|
|
36
|
+
"pytest>=8.3",
|
|
37
|
+
"pytest-asyncio>=0.24",
|
|
38
|
+
"ruff>=0.8",
|
|
39
|
+
"types-protobuf>=5.0",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/gtfs_hub"]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.sdist]
|
|
46
|
+
include = ["src", "tests", "scripts", "README.md", "CHANGELOG.md", "LICENSE"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
asyncio_mode = "auto"
|
|
50
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
51
|
+
testpaths = ["tests"]
|
|
52
|
+
|
|
53
|
+
[tool.ruff]
|
|
54
|
+
line-length = 100
|
|
55
|
+
src = ["src", "tests"]
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint]
|
|
58
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "RUF", "ANN", "TC", "PYI"]
|
|
59
|
+
|
|
60
|
+
[tool.mypy]
|
|
61
|
+
strict = true
|
|
62
|
+
files = ["src", "scripts", "tests"]
|
|
63
|
+
warn_unreachable = true
|
|
64
|
+
disallow_any_explicit = true
|
|
65
|
+
disallow_any_unimported = true
|
|
66
|
+
enable_error_code = [
|
|
67
|
+
"deprecated",
|
|
68
|
+
"explicit-override",
|
|
69
|
+
"ignore-without-code",
|
|
70
|
+
"possibly-undefined",
|
|
71
|
+
"redundant-expr",
|
|
72
|
+
"redundant-self",
|
|
73
|
+
"truthy-bool",
|
|
74
|
+
"unused-awaitable",
|
|
75
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Fail when the core package mentions a specific agency."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
FORBIDDEN = ("gzm", "ztm", "metropolia", "transportgzm", "otwartedane")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def find_violations(root: Path) -> list[str]:
|
|
10
|
+
"""Return one line per forbidden term found under root."""
|
|
11
|
+
violations = []
|
|
12
|
+
for path in sorted(root.rglob("*.py")):
|
|
13
|
+
text = path.read_text(encoding="utf-8")
|
|
14
|
+
for number, line in enumerate(text.splitlines(), start=1):
|
|
15
|
+
lowered = line.lower()
|
|
16
|
+
violations += [f"{path}:{number}: {term}" for term in FORBIDDEN if term in lowered]
|
|
17
|
+
return violations
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main() -> int:
|
|
21
|
+
root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("src/gtfs_hub")
|
|
22
|
+
violations = find_violations(root)
|
|
23
|
+
print("\n".join(violations) or f"{root}: no agency names")
|
|
24
|
+
return 1 if violations else 0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Provider-agnostic GTFS schedule library."""
|
|
2
|
+
|
|
3
|
+
from .discovery import (
|
|
4
|
+
async_create_provider,
|
|
5
|
+
async_list_provider_keys,
|
|
6
|
+
create_provider,
|
|
7
|
+
list_provider_keys,
|
|
8
|
+
)
|
|
9
|
+
from .download import raise_for_status
|
|
10
|
+
from .errors import (
|
|
11
|
+
GtfsAuthError,
|
|
12
|
+
GtfsConnectionError,
|
|
13
|
+
GtfsError,
|
|
14
|
+
GtfsFeedError,
|
|
15
|
+
GtfsNoDataError,
|
|
16
|
+
GtfsRateLimitError,
|
|
17
|
+
)
|
|
18
|
+
from .generic import GenericProvider
|
|
19
|
+
from .hub import GtfsHub
|
|
20
|
+
from .models import (
|
|
21
|
+
Capabilities,
|
|
22
|
+
Departure,
|
|
23
|
+
DepartureStatus,
|
|
24
|
+
Line,
|
|
25
|
+
LineKey,
|
|
26
|
+
Livery,
|
|
27
|
+
RealtimeEndpoints,
|
|
28
|
+
ResolvedId,
|
|
29
|
+
Shape,
|
|
30
|
+
ShapeId,
|
|
31
|
+
ShapeLocation,
|
|
32
|
+
Snapshot,
|
|
33
|
+
StaticFeedRef,
|
|
34
|
+
Stop,
|
|
35
|
+
StopChoice,
|
|
36
|
+
StopKey,
|
|
37
|
+
StopKeyField,
|
|
38
|
+
SyncResult,
|
|
39
|
+
TripDelay,
|
|
40
|
+
TripId,
|
|
41
|
+
Vehicle,
|
|
42
|
+
VehicleClass,
|
|
43
|
+
VehicleKey,
|
|
44
|
+
)
|
|
45
|
+
from .provider import BaseProvider, Provider, TableReader
|
|
46
|
+
from .realtime import RealtimeData
|
|
47
|
+
from .shapes import locate
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"BaseProvider",
|
|
51
|
+
"Capabilities",
|
|
52
|
+
"Departure",
|
|
53
|
+
"DepartureStatus",
|
|
54
|
+
"GenericProvider",
|
|
55
|
+
"GtfsAuthError",
|
|
56
|
+
"GtfsConnectionError",
|
|
57
|
+
"GtfsError",
|
|
58
|
+
"GtfsFeedError",
|
|
59
|
+
"GtfsHub",
|
|
60
|
+
"GtfsNoDataError",
|
|
61
|
+
"GtfsRateLimitError",
|
|
62
|
+
"Line",
|
|
63
|
+
"LineKey",
|
|
64
|
+
"Livery",
|
|
65
|
+
"Provider",
|
|
66
|
+
"RealtimeData",
|
|
67
|
+
"RealtimeEndpoints",
|
|
68
|
+
"ResolvedId",
|
|
69
|
+
"Shape",
|
|
70
|
+
"ShapeId",
|
|
71
|
+
"ShapeLocation",
|
|
72
|
+
"Snapshot",
|
|
73
|
+
"StaticFeedRef",
|
|
74
|
+
"Stop",
|
|
75
|
+
"StopChoice",
|
|
76
|
+
"StopKey",
|
|
77
|
+
"StopKeyField",
|
|
78
|
+
"SyncResult",
|
|
79
|
+
"TableReader",
|
|
80
|
+
"TripDelay",
|
|
81
|
+
"TripId",
|
|
82
|
+
"Vehicle",
|
|
83
|
+
"VehicleClass",
|
|
84
|
+
"VehicleKey",
|
|
85
|
+
"async_create_provider",
|
|
86
|
+
"async_list_provider_keys",
|
|
87
|
+
"create_provider",
|
|
88
|
+
"list_provider_keys",
|
|
89
|
+
"locate",
|
|
90
|
+
"raise_for_status",
|
|
91
|
+
]
|