python-openpublictransport 0.1.5__tar.gz → 0.1.7__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.
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/PKG-INFO +1 -1
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/pyproject.toml +1 -1
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/const.py +2 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/efa_base.py +27 -1
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/rejseplanen.py +53 -12
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/python_openpublictransport.egg-info/PKG-INFO +1 -1
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/setup.cfg +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/__init__.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/exceptions.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/models.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/parsers.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/__init__.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/avv.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/base.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/beg.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/bsvg.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/bvg.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/db.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/ding.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/fptf_base.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/gtfsde.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/hvv.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/kvv.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/mvv.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/national_rail.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/nta.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/nvbw.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/nwl.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/oebb.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/otp.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/otp_base.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/otp_custom.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/rmv.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/rvv.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/sbb.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/trafiklab.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/transitous.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/trias_base.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/vagfr.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/vbn.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/vgn.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/vrn.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/vrr.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/vvo.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/providers/vvs.py +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/openpublictransport/py.typed +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/python_openpublictransport.egg-info/SOURCES.txt +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/python_openpublictransport.egg-info/dependency_links.txt +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/python_openpublictransport.egg-info/requires.txt +0 -0
- {python_openpublictransport-0.1.5 → python_openpublictransport-0.1.7}/src/python_openpublictransport.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-openpublictransport"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.7"
|
|
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" }
|
|
@@ -17,6 +17,28 @@ from .base import BaseProvider
|
|
|
17
17
|
_LOGGER = logging.getLogger(__name__)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def _transport_type_from_name(name: str) -> str:
|
|
21
|
+
"""Best-effort transport type from an EFA product name.
|
|
22
|
+
|
|
23
|
+
Used as a fallback when the numeric product class is not in a provider's
|
|
24
|
+
mapping (e.g. KVV Regionalbus class 6), so unmapped classes are not dropped
|
|
25
|
+
as "unknown". Checks subway/tram before the generic "bahn" → train.
|
|
26
|
+
"""
|
|
27
|
+
n = (name or "").lower()
|
|
28
|
+
if "u-bahn" in n or "u_bahn" in n or "ubahn" in n or "subway" in n or "metro" in n:
|
|
29
|
+
return "subway"
|
|
30
|
+
if "straßenbahn" in n or "strassenbahn" in n or "stadtbahn" in n or "tram" in n:
|
|
31
|
+
return "tram"
|
|
32
|
+
if "fähre" in n or "faehre" in n or "schiff" in n or "ferry" in n:
|
|
33
|
+
return "ferry"
|
|
34
|
+
if "bus" in n or "ast" in n or "ruf" in n or "ersatz" in n:
|
|
35
|
+
# Stadt-/Regional-/Schnell-/Nachtbus, AST, Rufbus, (Schienen-)Ersatzverkehr
|
|
36
|
+
return "bus"
|
|
37
|
+
if "bahn" in n or "zug" in n or "train" in n: # S-Bahn, Regionalbahn, Zug, …
|
|
38
|
+
return "train"
|
|
39
|
+
return "unknown"
|
|
40
|
+
|
|
41
|
+
|
|
20
42
|
class EFABaseProvider(BaseProvider):
|
|
21
43
|
"""Base class for all EFA-based providers (VRR, KVV, HVV, MVV, etc.)."""
|
|
22
44
|
|
|
@@ -122,10 +144,14 @@ class EFABaseProvider(BaseProvider):
|
|
|
122
144
|
product = transportation.get("product", {})
|
|
123
145
|
product_class = product.get("class", 0)
|
|
124
146
|
transport_type = type_mapping.get(product_class, "unknown")
|
|
147
|
+
if transport_type == "unknown":
|
|
148
|
+
# Fall back to the product name so unmapped classes aren't dropped.
|
|
149
|
+
transport_type = _transport_type_from_name(product.get("name", ""))
|
|
125
150
|
if transport_type == "unknown":
|
|
126
151
|
_LOGGER.debug(
|
|
127
|
-
"Unknown transport class %s for line %s",
|
|
152
|
+
"Unknown transport class %s / name %r for line %s",
|
|
128
153
|
product_class,
|
|
154
|
+
product.get("name"),
|
|
129
155
|
transportation.get("number", "unknown"),
|
|
130
156
|
)
|
|
131
157
|
return transport_type
|
|
@@ -38,17 +38,49 @@ _PRODUCT_MAPPING: Dict[str, str] = {
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def _parse_hafas_time(date_str: str, time_str: str, tz: Any) -> Optional[datetime]:
|
|
41
|
-
"""Parse Rejseplanen date
|
|
41
|
+
"""Parse a Rejseplanen date + time into a timezone-aware datetime.
|
|
42
|
+
|
|
43
|
+
The HAFAS REST API at www.rejseplanen.dk returns ISO dates (YYYY-MM-DD) and
|
|
44
|
+
times as HH:MM:SS; older HAFAS deployments used DD.MM.YY / HH:MM. Handle both.
|
|
45
|
+
"""
|
|
42
46
|
if not date_str or not time_str:
|
|
43
47
|
return None
|
|
48
|
+
time_fmt = "%H:%M:%S" if time_str.count(":") == 2 else "%H:%M"
|
|
49
|
+
date_fmt = "%Y-%m-%d" if "-" in date_str else "%d.%m.%y"
|
|
44
50
|
try:
|
|
45
|
-
|
|
46
|
-
dt = datetime.strptime(f"{date_str} {time_str}", f"%d.%m.%y {time_fmt}")
|
|
51
|
+
dt = datetime.strptime(f"{date_str} {time_str}", f"{date_fmt} {time_fmt}")
|
|
47
52
|
return dt.replace(tzinfo=tz)
|
|
48
53
|
except ValueError:
|
|
49
54
|
return None
|
|
50
55
|
|
|
51
56
|
|
|
57
|
+
def _resolve_transport_type(stop: Dict[str, Any]) -> str:
|
|
58
|
+
"""Derive the transport type from the HAFAS product category.
|
|
59
|
+
|
|
60
|
+
The departure's top-level ``type`` is a stop type (e.g. "ST"), not the
|
|
61
|
+
product, so read the category from ``Product[].catOut`` and fall back to the
|
|
62
|
+
line name (e.g. "Bus 250S", "Metro M4", "Re 1277").
|
|
63
|
+
"""
|
|
64
|
+
products = stop.get("Product", [])
|
|
65
|
+
if isinstance(products, dict):
|
|
66
|
+
products = [products]
|
|
67
|
+
cat = ""
|
|
68
|
+
if products and isinstance(products[0], dict):
|
|
69
|
+
p = products[0]
|
|
70
|
+
cat = (p.get("catOut") or p.get("catOutL") or p.get("catIn") or "").strip()
|
|
71
|
+
text = f"{cat} {stop.get('name', '')}".lower()
|
|
72
|
+
if "bus" in text: # Bus, Togbus (rail-replacement), Natbus, Expresbus
|
|
73
|
+
return "bus"
|
|
74
|
+
if "metro" in text:
|
|
75
|
+
return "subway"
|
|
76
|
+
if "letbane" in text or "tram" in text:
|
|
77
|
+
return "tram"
|
|
78
|
+
if "færge" in text or "ferry" in text or "havnebus" in text:
|
|
79
|
+
return "ferry"
|
|
80
|
+
# Everything else (IC, ICL, Lyn, Re, Reg, Tog, S-tog, RJ, EC/ECE, …) is rail.
|
|
81
|
+
return "train"
|
|
82
|
+
|
|
83
|
+
|
|
52
84
|
class RejseplanenProvider(BaseProvider):
|
|
53
85
|
"""Rejseplanen (Denmark) provider via HAFAS REST API."""
|
|
54
86
|
|
|
@@ -122,8 +154,11 @@ class RejseplanenProvider(BaseProvider):
|
|
|
122
154
|
_LOGGER.warning("%s: API error %s: %s", self.provider_name, data.get("errorCode"), data.get("errorText"))
|
|
123
155
|
return None
|
|
124
156
|
|
|
125
|
-
|
|
126
|
-
|
|
157
|
+
# www.rejseplanen.dk returns "Departure" at the top level; older HAFAS
|
|
158
|
+
# deployments nested it under "DepartureBoard". Support both.
|
|
159
|
+
departures = data.get("Departure")
|
|
160
|
+
if departures is None:
|
|
161
|
+
departures = data.get("DepartureBoard", {}).get("Departure", [])
|
|
127
162
|
if isinstance(departures, dict):
|
|
128
163
|
departures = [departures]
|
|
129
164
|
|
|
@@ -156,8 +191,7 @@ class RejseplanenProvider(BaseProvider):
|
|
|
156
191
|
delay = max(0, int((actual_dt - planned_dt).total_seconds() / 60))
|
|
157
192
|
minutes_until = max(0, int((actual_dt - now).total_seconds() / 60))
|
|
158
193
|
|
|
159
|
-
|
|
160
|
-
transport_type = _PRODUCT_MAPPING.get(type_str, "train")
|
|
194
|
+
transport_type = _resolve_transport_type(stop)
|
|
161
195
|
|
|
162
196
|
line = stop.get("name", "")
|
|
163
197
|
destination = stop.get("direction", stop.get("finalStop", ""))
|
|
@@ -223,13 +257,20 @@ class RejseplanenProvider(BaseProvider):
|
|
|
223
257
|
if not isinstance(data, dict):
|
|
224
258
|
return []
|
|
225
259
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
260
|
+
# www.rejseplanen.dk returns "stopLocationOrCoordLocation" (a list whose
|
|
261
|
+
# entries wrap a StopLocation or CoordLocation); older HAFAS deployments
|
|
262
|
+
# used LocationList.StopLocation. Support both.
|
|
263
|
+
entries = data.get("stopLocationOrCoordLocation")
|
|
264
|
+
if entries is None:
|
|
265
|
+
entries = data.get("LocationList", {}).get("StopLocation", [])
|
|
266
|
+
if isinstance(entries, dict):
|
|
267
|
+
entries = [entries]
|
|
230
268
|
|
|
231
269
|
results = []
|
|
232
|
-
for
|
|
270
|
+
for entry in entries:
|
|
271
|
+
if not isinstance(entry, dict):
|
|
272
|
+
continue
|
|
273
|
+
loc = entry.get("StopLocation", entry)
|
|
233
274
|
if not isinstance(loc, dict):
|
|
234
275
|
continue
|
|
235
276
|
station_id = loc.get("extId") or loc.get("id", "")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|