python-openpublictransport 0.1.12__tar.gz → 0.1.14__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.
Files changed (67) hide show
  1. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/PKG-INFO +1 -1
  2. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/pyproject.toml +1 -1
  3. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/const.py +7 -0
  4. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/__init__.py +21 -0
  5. python_openpublictransport-0.1.14/src/openpublictransport/providers/bart.py +26 -0
  6. python_openpublictransport-0.1.14/src/openpublictransport/providers/dart.py +29 -0
  7. python_openpublictransport-0.1.14/src/openpublictransport/providers/entur.py +207 -0
  8. python_openpublictransport-0.1.14/src/openpublictransport/providers/hafas_base.py +362 -0
  9. python_openpublictransport-0.1.14/src/openpublictransport/providers/hafas_mgate_base.py +303 -0
  10. python_openpublictransport-0.1.14/src/openpublictransport/providers/irishrail.py +32 -0
  11. python_openpublictransport-0.1.14/src/openpublictransport/providers/mobiliteit_lu.py +24 -0
  12. python_openpublictransport-0.1.14/src/openpublictransport/providers/ns.py +25 -0
  13. python_openpublictransport-0.1.14/src/openpublictransport/providers/oebb.py +43 -0
  14. python_openpublictransport-0.1.14/src/openpublictransport/providers/tpg.py +26 -0
  15. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/python_openpublictransport.egg-info/PKG-INFO +1 -1
  16. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/python_openpublictransport.egg-info/SOURCES.txt +12 -0
  17. python_openpublictransport-0.1.14/tests/test_entur.py +86 -0
  18. python_openpublictransport-0.1.14/tests/test_hafas_base.py +226 -0
  19. python_openpublictransport-0.1.14/tests/test_hafas_mgate.py +97 -0
  20. python_openpublictransport-0.1.12/src/openpublictransport/providers/oebb.py +0 -182
  21. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/setup.cfg +0 -0
  22. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/__init__.py +0 -0
  23. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/data/uk_stations.json +0 -0
  24. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/exceptions.py +0 -0
  25. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/models.py +0 -0
  26. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/parsers.py +0 -0
  27. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/avv.py +0 -0
  28. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/base.py +0 -0
  29. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/beg.py +0 -0
  30. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/bsvg.py +0 -0
  31. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/bvg.py +0 -0
  32. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/db.py +0 -0
  33. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/ding.py +0 -0
  34. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/efa_base.py +0 -0
  35. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/fptf_base.py +0 -0
  36. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/gtfsde.py +0 -0
  37. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/hvv.py +0 -0
  38. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/kvv.py +0 -0
  39. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/mvv.py +0 -0
  40. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/national_rail.py +0 -0
  41. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/nta.py +0 -0
  42. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/nvbw.py +0 -0
  43. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/nwl.py +0 -0
  44. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/otp.py +0 -0
  45. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/otp_base.py +0 -0
  46. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/otp_custom.py +0 -0
  47. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/rejseplanen.py +0 -0
  48. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/rmv.py +0 -0
  49. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/rvv.py +0 -0
  50. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/sbb.py +0 -0
  51. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/trafiklab.py +0 -0
  52. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/transitous.py +0 -0
  53. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/trias_base.py +0 -0
  54. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/vagfr.py +0 -0
  55. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/vbn.py +0 -0
  56. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/vgn.py +0 -0
  57. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/vrn.py +0 -0
  58. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/vrr.py +0 -0
  59. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/vvo.py +0 -0
  60. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/providers/vvs.py +0 -0
  61. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/openpublictransport/py.typed +0 -0
  62. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/python_openpublictransport.egg-info/dependency_links.txt +0 -0
  63. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/python_openpublictransport.egg-info/requires.txt +0 -0
  64. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/src/python_openpublictransport.egg-info/top_level.txt +0 -0
  65. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/tests/test_national_rail.py +0 -0
  66. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/tests/test_national_rail_parse.py +0 -0
  67. {python_openpublictransport-0.1.12 → python_openpublictransport-0.1.14}/tests/test_national_rail_static.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-openpublictransport
3
- Version: 0.1.12
3
+ Version: 0.1.14
4
4
  Summary: Python library for public transport APIs (EFA, OTP2, TRIAS, FPTF, HAFAS, GTFS-RT)
5
5
  License: MIT
6
6
  Requires-Python: >=3.11
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-openpublictransport"
7
- version = "0.1.12"
7
+ version = "0.1.14"
8
8
  description = "Python library for public transport APIs (EFA, OTP2, TRIAS, FPTF, HAFAS, GTFS-RT)"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -31,6 +31,13 @@ PROVIDER_OPT = "openpublictransport"
31
31
  PROVIDER_OTP_CUSTOM = "otp_custom"
32
32
  PROVIDER_NATIONAL_RAIL = "national_rail"
33
33
  PROVIDER_REJSEPLANEN = "rejseplanen"
34
+ PROVIDER_NS_NL = "ns_nl"
35
+ PROVIDER_MOBILITEIT_LU = "mobiliteit_lu"
36
+ PROVIDER_ENTUR_NO = "entur_no"
37
+ PROVIDER_BART_US = "bart_us"
38
+ PROVIDER_DART_US = "dart_us"
39
+ PROVIDER_IRISHRAIL_IE = "irishrail_ie"
40
+ PROVIDER_TPG_CH = "tpg_ch"
34
41
 
35
42
  # API base URLs
36
43
  API_BASE_URL_VRR = "https://openservice-test.vrr.de/static03/XML_DM_REQUEST"
@@ -34,6 +34,13 @@ from ..const import (
34
34
  PROVIDER_VVS,
35
35
  PROVIDER_NATIONAL_RAIL,
36
36
  PROVIDER_REJSEPLANEN,
37
+ PROVIDER_NS_NL,
38
+ PROVIDER_MOBILITEIT_LU,
39
+ PROVIDER_ENTUR_NO,
40
+ PROVIDER_BART_US,
41
+ PROVIDER_DART_US,
42
+ PROVIDER_IRISHRAIL_IE,
43
+ PROVIDER_TPG_CH,
37
44
  )
38
45
  from .avv import AVVProvider
39
46
  from .base import BaseProvider
@@ -65,6 +72,13 @@ from .vvo import VVOProvider
65
72
  from .vvs import VVSProvider
66
73
  from .national_rail import NationalRailProvider
67
74
  from .rejseplanen import RejseplanenProvider
75
+ from .ns import NSProvider
76
+ from .mobiliteit_lu import MobiliteitLuProvider
77
+ from .entur import EnturProvider
78
+ from .bart import BARTProvider
79
+ from .dart import DARTProvider
80
+ from .irishrail import IrishRailProvider
81
+ from .tpg import TPGProvider
68
82
 
69
83
  _PROVIDER_REGISTRY: Dict[str, Type[BaseProvider]] = {}
70
84
 
@@ -140,3 +154,10 @@ register_provider(PROVIDER_OPT, OPTProvider)
140
154
  register_provider(PROVIDER_OTP_CUSTOM, OTPCustomProvider)
141
155
  register_provider(PROVIDER_NATIONAL_RAIL, NationalRailProvider)
142
156
  register_provider(PROVIDER_REJSEPLANEN, RejseplanenProvider)
157
+ register_provider(PROVIDER_NS_NL, NSProvider)
158
+ register_provider(PROVIDER_MOBILITEIT_LU, MobiliteitLuProvider)
159
+ register_provider(PROVIDER_ENTUR_NO, EnturProvider)
160
+ register_provider(PROVIDER_BART_US, BARTProvider)
161
+ register_provider(PROVIDER_DART_US, DARTProvider)
162
+ register_provider(PROVIDER_IRISHRAIL_IE, IrishRailProvider)
163
+ register_provider(PROVIDER_TPG_CH, TPGProvider)
@@ -0,0 +1,26 @@
1
+ """BART — Bay Area Rapid Transit (USA) provider.
2
+
3
+ Modern HAFAS ``mgate.exe`` gateway; unsigned. Config from
4
+ public-transport/transport-apis (``us/bart-hafas-mgate``).
5
+ """
6
+
7
+ from ..const import PROVIDER_BART_US
8
+ from .hafas_mgate_base import HafasMgateBaseProvider
9
+
10
+
11
+ class BARTProvider(HafasMgateBaseProvider):
12
+ """BART (San Francisco Bay Area) via HAFAS mgate."""
13
+
14
+ mgate_endpoint = "https://planner.bart.gov/bin/mgate.exe"
15
+ mgate_auth = {"type": "AID", "aid": "kEwHkFUCIL500dym"}
16
+ mgate_client = {"id": "BART", "type": "WEB", "name": "webapp", "l": "vs_webapp"}
17
+ mgate_ver = "1.40"
18
+ timezone = "America/Los_Angeles"
19
+
20
+ @property
21
+ def provider_id(self) -> str:
22
+ return PROVIDER_BART_US
23
+
24
+ @property
25
+ def provider_name(self) -> str:
26
+ return "BART (San Francisco Bay Area)"
@@ -0,0 +1,29 @@
1
+ """DART — Des Moines Area Rapid Transit (Iowa, USA) provider.
2
+
3
+ Modern HAFAS ``mgate.exe`` gateway; unsigned. Config from
4
+ public-transport/transport-apis (``us/dart-hafas-mgate``). Note: that catalogue
5
+ entry is titled "Dallas Area Rapid Transit", but the endpoint actually serves
6
+ **Des Moines, Iowa** (DART Central Station, DSM/WDM/Ankeny/Johnston stops) —
7
+ verified live. Iowa is Central time, so the timezone is unchanged.
8
+ """
9
+
10
+ from ..const import PROVIDER_DART_US
11
+ from .hafas_mgate_base import HafasMgateBaseProvider
12
+
13
+
14
+ class DARTProvider(HafasMgateBaseProvider):
15
+ """DART (Des Moines, Iowa) via HAFAS mgate."""
16
+
17
+ mgate_endpoint = "https://dart.hafas.de/bin/mgate.exe"
18
+ mgate_auth = {"type": "AID", "aid": "XNFGL2aSkxfDeK8N4waOZnsdJ"}
19
+ mgate_client = {"id": "DART", "type": "WEB", "name": "webapp", "l": "vs_webapp"}
20
+ mgate_ver = "1.35"
21
+ timezone = "America/Chicago"
22
+
23
+ @property
24
+ def provider_id(self) -> str:
25
+ return PROVIDER_DART_US
26
+
27
+ @property
28
+ def provider_name(self) -> str:
29
+ return "DART (Des Moines)"
@@ -0,0 +1,207 @@
1
+ """Entur (Norway) provider implementation.
2
+
3
+ Norway's national journey planner. Departures come from Entur's OTP
4
+ **transmodel** GraphQL API and stop search from Entur's geocoder. Both are
5
+ keyless; Entur only requires an identifying ``ET-Client-Name`` header.
6
+
7
+ * geocoder https://api.entur.io/geocoder/v1/autocomplete
8
+ * journeys https://api.entur.io/journey-planner/v3/graphql
9
+
10
+ Catalogued by public-transport/transport-apis as ``no/entur-otp``.
11
+ """
12
+
13
+ import logging
14
+ from datetime import datetime
15
+ from typing import Any, Dict, List, Optional, Union
16
+ from urllib.parse import quote
17
+ from zoneinfo import ZoneInfo
18
+
19
+ import aiohttp
20
+
21
+ from ..const import PROVIDER_ENTUR_NO
22
+ from ..models import UnifiedDeparture
23
+ from .base import BaseProvider
24
+
25
+ _LOGGER = logging.getLogger(__name__)
26
+
27
+ GEOCODER_URL = "https://api.entur.io/geocoder/v1/autocomplete"
28
+ JOURNEY_PLANNER_URL = "https://api.entur.io/journey-planner/v3/graphql"
29
+ CLIENT_NAME = "nerdysoftpaw-openpublictransport"
30
+
31
+ # Transmodel transportMode (lower-case) -> unified transport type.
32
+ MODE_MAPPING = {
33
+ "rail": "train",
34
+ "coach": "bus",
35
+ "bus": "bus",
36
+ "trolleybus": "bus",
37
+ "tram": "tram",
38
+ "metro": "subway",
39
+ "water": "ferry",
40
+ "ferry": "ferry",
41
+ "funicular": "train",
42
+ "cableway": "tram",
43
+ "monorail": "subway",
44
+ "taxi": "taxi",
45
+ }
46
+
47
+ _DEPARTURES_QUERY = (
48
+ "query($id:String!,$n:Int!){"
49
+ "stopPlace(id:$id){"
50
+ "estimatedCalls(numberOfDepartures:$n,timeRange:7200){"
51
+ "aimedDepartureTime expectedDepartureTime realtime cancellation "
52
+ "destinationDisplay{frontText} quay{publicCode} "
53
+ "serviceJourney{line{publicCode transportMode}}"
54
+ "}}}"
55
+ )
56
+
57
+
58
+ def _parse_dt(s: Optional[str]) -> Optional[datetime]:
59
+ try:
60
+ return datetime.fromisoformat(s) if s else None
61
+ except (ValueError, TypeError):
62
+ return None
63
+
64
+
65
+ class EnturProvider(BaseProvider):
66
+ """Entur (Norway) via transmodel GraphQL + Entur geocoder."""
67
+
68
+ @property
69
+ def provider_id(self) -> str:
70
+ return PROVIDER_ENTUR_NO
71
+
72
+ @property
73
+ def provider_name(self) -> str:
74
+ return "Entur (Norge)"
75
+
76
+ def get_timezone(self) -> str:
77
+ return "Europe/Oslo"
78
+
79
+ def _headers(self) -> Dict[str, str]:
80
+ return {"ET-Client-Name": CLIENT_NAME, "Accept": "application/json"}
81
+
82
+ async def search_stops(self, search_term: str) -> List[Dict[str, Any]]:
83
+ url = (
84
+ f"{GEOCODER_URL}?text={quote(search_term, safe='')}"
85
+ "&size=12&lang=en&layers=venue"
86
+ )
87
+ try:
88
+ async with self.session.get(
89
+ url, headers=self._headers(), timeout=aiohttp.ClientTimeout(total=10)
90
+ ) as response:
91
+ if response.status != 200:
92
+ _LOGGER.error("Entur geocoder returned status %s", response.status)
93
+ return []
94
+ data = await response.json()
95
+ except Exception as e: # noqa: BLE001
96
+ _LOGGER.error("Error searching Entur stops: %s", e)
97
+ return []
98
+
99
+ return self._stops_from_geocoder(data)
100
+
101
+ @staticmethod
102
+ def _stops_from_geocoder(data: Dict[str, Any]) -> List[Dict[str, Any]]:
103
+ results: List[Dict[str, Any]] = []
104
+ for feature in data.get("features", []):
105
+ props = feature.get("properties", {}) if isinstance(feature, dict) else {}
106
+ stop_id = str(props.get("id") or "")
107
+ # Only stop places have a departure board.
108
+ if not stop_id.startswith("NSR:StopPlace:"):
109
+ continue
110
+ name = props.get("label") or props.get("name") or ""
111
+ if not name:
112
+ continue
113
+ results.append(
114
+ {
115
+ "id": stop_id,
116
+ "name": name,
117
+ "place": props.get("locality") or "",
118
+ "area_type": "stop",
119
+ }
120
+ )
121
+ return results
122
+
123
+ async def fetch_departures(
124
+ self,
125
+ station_id: Optional[str],
126
+ place_dm: str,
127
+ name_dm: str,
128
+ departures_limit: int,
129
+ ) -> Optional[Dict[str, Any]]:
130
+ if not station_id:
131
+ _LOGGER.warning("Entur provider requires a station_id")
132
+ return None
133
+
134
+ payload = {
135
+ "query": _DEPARTURES_QUERY,
136
+ "variables": {"id": station_id, "n": departures_limit},
137
+ }
138
+ try:
139
+ async with self.session.post(
140
+ JOURNEY_PLANNER_URL,
141
+ json=payload,
142
+ headers=self._headers(),
143
+ timeout=aiohttp.ClientTimeout(total=15),
144
+ ) as response:
145
+ if response.status != 200:
146
+ _LOGGER.warning("Entur journey planner returned status %s", response.status)
147
+ return None
148
+ data = await response.json()
149
+ except aiohttp.ClientError as e:
150
+ _LOGGER.warning("Entur journey planner request failed: %s", e)
151
+ return None
152
+ except Exception as e: # noqa: BLE001
153
+ _LOGGER.warning("Entur journey planner error: %s", e)
154
+ return None
155
+
156
+ stop_place = (data.get("data") or {}).get("stopPlace")
157
+ if not stop_place:
158
+ return {"stopEvents": []}
159
+ return {"stopEvents": stop_place.get("estimatedCalls") or []}
160
+
161
+ def parse_departure(
162
+ self, stop: Dict[str, Any], tz: Union[ZoneInfo, Any], now: datetime
163
+ ) -> Optional[UnifiedDeparture]:
164
+ try:
165
+ aimed = _parse_dt(stop.get("aimedDepartureTime"))
166
+ if not aimed:
167
+ return None
168
+ expected = _parse_dt(stop.get("expectedDepartureTime")) or aimed
169
+
170
+ planned_local = aimed.astimezone(tz)
171
+ when_local = expected.astimezone(tz)
172
+ delay_minutes = int((when_local - planned_local).total_seconds() / 60)
173
+
174
+ line_info = (stop.get("serviceJourney") or {}).get("line") or {}
175
+ line = line_info.get("publicCode") or ""
176
+ mode = (line_info.get("transportMode") or "").lower()
177
+ transport_type = MODE_MAPPING.get(mode, "unknown")
178
+
179
+ destination = (stop.get("destinationDisplay") or {}).get("frontText") or "Unknown"
180
+ platform = (stop.get("quay") or {}).get("publicCode") or ""
181
+
182
+ minutes_until = max(0, int((when_local - now).total_seconds() / 60))
183
+
184
+ notices = None
185
+ if stop.get("cancellation"):
186
+ notices = ["Cancelled"]
187
+
188
+ return UnifiedDeparture(
189
+ line=line,
190
+ destination=destination,
191
+ departure_time=when_local.strftime("%H:%M"),
192
+ planned_time=planned_local.strftime("%H:%M"),
193
+ delay=delay_minutes,
194
+ platform=platform,
195
+ transportation_type=transport_type,
196
+ is_realtime=bool(stop.get("realtime")),
197
+ minutes_until_departure=minutes_until,
198
+ departure_time_obj=when_local,
199
+ description=None,
200
+ agency=None,
201
+ notices=notices,
202
+ planned_platform=None,
203
+ platform_changed=False,
204
+ )
205
+ except Exception as e: # noqa: BLE001
206
+ _LOGGER.debug("Error parsing Entur departure: %s", e)
207
+ return None