python-openpublictransport 0.1.11__tar.gz → 0.1.12__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.11 → python_openpublictransport-0.1.12}/PKG-INFO +1 -1
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/pyproject.toml +1 -1
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/national_rail.py +46 -8
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/python_openpublictransport.egg-info/PKG-INFO +1 -1
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/tests/test_national_rail_parse.py +7 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/tests/test_national_rail_static.py +4 -4
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/setup.cfg +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/__init__.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/const.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/data/uk_stations.json +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/exceptions.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/models.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/parsers.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/__init__.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/avv.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/base.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/beg.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/bsvg.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/bvg.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/db.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/ding.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/efa_base.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/fptf_base.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/gtfsde.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/hvv.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/kvv.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/mvv.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/nta.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/nvbw.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/nwl.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/oebb.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/otp.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/otp_base.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/otp_custom.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/rejseplanen.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/rmv.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/rvv.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/sbb.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/trafiklab.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/transitous.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/trias_base.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/vagfr.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/vbn.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/vgn.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/vrn.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/vrr.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/vvo.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/providers/vvs.py +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/openpublictransport/py.typed +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/python_openpublictransport.egg-info/SOURCES.txt +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/python_openpublictransport.egg-info/dependency_links.txt +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/python_openpublictransport.egg-info/requires.txt +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/src/python_openpublictransport.egg-info/top_level.txt +0 -0
- {python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/tests/test_national_rail.py +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.12"
|
|
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" }
|
|
@@ -4,6 +4,7 @@ Stop search uses the Overpass API (OSM) to find UK railway stations with
|
|
|
4
4
|
ref:crs tags, returning the 3-letter CRS code used by OpenLDBWS.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import asyncio
|
|
7
8
|
import json
|
|
8
9
|
import logging
|
|
9
10
|
import re
|
|
@@ -22,8 +23,16 @@ from .base import BaseProvider
|
|
|
22
23
|
|
|
23
24
|
_LOGGER = logging.getLogger(__name__)
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
# Use the same LDBWS version triple as the reference OpenLDBWS clients:
|
|
27
|
+
# endpoint = ldb12.asmx (WSDL ver=2021-11-01)
|
|
28
|
+
# body ns = 2021-11-01/ldb/
|
|
29
|
+
# SOAPAction= 2012-01-13/ldb/GetDepartureBoard
|
|
30
|
+
# The SOAPAction is version-pinned to 2012-01-13 (it does NOT track the body's
|
|
31
|
+
# ldb version); sending a mismatched SOAPAction makes the ASMX service reject the
|
|
32
|
+
# request with an HTTP 500 SOAP fault ("did not recognize the value of the
|
|
33
|
+
# SOAPAction header").
|
|
34
|
+
_ENDPOINT = "https://lite.realtime.nationalrail.co.uk/OpenLDBWS/ldb12.asmx"
|
|
35
|
+
_SOAP_ACTION = "http://thalesgroup.com/RTTI/2012-01-13/ldb/GetDepartureBoard"
|
|
27
36
|
_OVERPASS_URL = "https://overpass-api.de/api/interpreter"
|
|
28
37
|
|
|
29
38
|
# UK bounding box (lat_min, lon_min, lat_max, lon_max)
|
|
@@ -31,7 +40,7 @@ _UK_BBOX = "49,-11,62,2"
|
|
|
31
40
|
|
|
32
41
|
_SOAP_TEMPLATE = """<?xml version="1.0" encoding="UTF-8"?>
|
|
33
42
|
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
|
|
34
|
-
xmlns:ldb="http://thalesgroup.com/RTTI/
|
|
43
|
+
xmlns:ldb="http://thalesgroup.com/RTTI/2021-11-01/ldb/"
|
|
35
44
|
xmlns:tok="http://thalesgroup.com/RTTI/2013-11-28/Token/types">
|
|
36
45
|
<soap:Header>
|
|
37
46
|
<tok:AccessToken>
|
|
@@ -81,6 +90,16 @@ def _static_result(station: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
|
|
93
|
+
def _soap_fault_string(body: str) -> str:
|
|
94
|
+
"""Best-effort extraction of a SOAP fault reason from an error response."""
|
|
95
|
+
if not body:
|
|
96
|
+
return ""
|
|
97
|
+
m = re.search(r"<(?:\w+:)?(?:faultstring|Text|Reason)[^>]*>(.*?)</", body, re.DOTALL | re.IGNORECASE)
|
|
98
|
+
if m:
|
|
99
|
+
return m.group(1).strip()[:200]
|
|
100
|
+
return body.strip()[:200]
|
|
101
|
+
|
|
102
|
+
|
|
84
103
|
def _strip_namespaces(xml_string: str) -> str:
|
|
85
104
|
"""Remove XML namespace prefixes and declarations for simpler parsing.
|
|
86
105
|
|
|
@@ -167,7 +186,21 @@ class NationalRailProvider(BaseProvider):
|
|
|
167
186
|
f"{self.provider_name}: authentication failed (HTTP {resp.status}) — check API key"
|
|
168
187
|
)
|
|
169
188
|
if resp.status != 200:
|
|
170
|
-
|
|
189
|
+
# Surface the SOAP fault reason (HTTP 500) instead of hiding it,
|
|
190
|
+
# so genuine request/token faults are diagnosable.
|
|
191
|
+
body = ""
|
|
192
|
+
try:
|
|
193
|
+
body = await resp.text()
|
|
194
|
+
except Exception: # pragma: no cover - defensive
|
|
195
|
+
pass
|
|
196
|
+
fault = _soap_fault_string(body)
|
|
197
|
+
_LOGGER.warning(
|
|
198
|
+
"%s: HTTP %s for CRS %s%s",
|
|
199
|
+
self.provider_name,
|
|
200
|
+
resp.status,
|
|
201
|
+
crs,
|
|
202
|
+
f" — {fault}" if fault else "",
|
|
203
|
+
)
|
|
171
204
|
return None
|
|
172
205
|
text = await resp.text()
|
|
173
206
|
except Exception as exc:
|
|
@@ -340,7 +373,7 @@ class NationalRailProvider(BaseProvider):
|
|
|
340
373
|
return results
|
|
341
374
|
|
|
342
375
|
# Overpass failed (unreachable / throttled) → use the offline snapshot.
|
|
343
|
-
fallback = self._search_static(term)
|
|
376
|
+
fallback = await self._search_static(term)
|
|
344
377
|
if fallback:
|
|
345
378
|
_LOGGER.info(
|
|
346
379
|
"%s: Overpass unavailable for %r — served %d result(s) from the "
|
|
@@ -351,9 +384,14 @@ class NationalRailProvider(BaseProvider):
|
|
|
351
384
|
)
|
|
352
385
|
return fallback
|
|
353
386
|
|
|
354
|
-
def _search_static(self, term: str) -> List[Dict[str, Any]]:
|
|
355
|
-
"""Match ``term`` against the bundled offline station snapshot.
|
|
356
|
-
|
|
387
|
+
async def _search_static(self, term: str) -> List[Dict[str, Any]]:
|
|
388
|
+
"""Match ``term`` against the bundled offline station snapshot.
|
|
389
|
+
|
|
390
|
+
The snapshot is read from disk on first use; that read is dispatched to
|
|
391
|
+
an executor so it never blocks the event loop (results are cached, so
|
|
392
|
+
subsequent calls do no I/O).
|
|
393
|
+
"""
|
|
394
|
+
stations = await asyncio.get_running_loop().run_in_executor(None, _load_static_stations)
|
|
357
395
|
if not stations:
|
|
358
396
|
return []
|
|
359
397
|
|
|
@@ -55,6 +55,13 @@ SAMPLE = """<?xml version="1.0" encoding="utf-8"?>
|
|
|
55
55
|
</soap:Envelope>"""
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
def test_ldbws_request_uses_consistent_version_triple():
|
|
59
|
+
"""SOAPAction must be the 2012-01-13 value; a mismatch yields HTTP 500."""
|
|
60
|
+
assert nr._ENDPOINT.endswith("ldb12.asmx")
|
|
61
|
+
assert nr._SOAP_ACTION == "http://thalesgroup.com/RTTI/2012-01-13/ldb/GetDepartureBoard"
|
|
62
|
+
assert 'xmlns:ldb="http://thalesgroup.com/RTTI/2021-11-01/ldb/"' in nr._SOAP_TEMPLATE
|
|
63
|
+
|
|
64
|
+
|
|
58
65
|
def test_strip_namespaces_handles_digit_prefixes():
|
|
59
66
|
"""Digit-bearing prefixes must be stripped so the XML parses."""
|
|
60
67
|
stripped = nr._strip_namespaces(SAMPLE)
|
|
@@ -53,17 +53,17 @@ def test_snapshot_loads_and_has_known_stations():
|
|
|
53
53
|
assert by_crs["RDG"]["name"] == "Reading"
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
def test_static_search_by_crs_code():
|
|
56
|
+
async def test_static_search_by_crs_code():
|
|
57
57
|
provider = NationalRailProvider(_Session("down"))
|
|
58
|
-
results = provider._search_static("rdg")
|
|
58
|
+
results = await provider._search_static("rdg")
|
|
59
59
|
assert results == [
|
|
60
60
|
{"id": "RDG", "name": "Reading", "place": "Network Rail", "area_type": "stop"}
|
|
61
61
|
]
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
def test_static_search_by_name():
|
|
64
|
+
async def test_static_search_by_name():
|
|
65
65
|
provider = NationalRailProvider(_Session("down"))
|
|
66
|
-
results = provider._search_static("winchester")
|
|
66
|
+
results = await provider._search_static("winchester")
|
|
67
67
|
assert any(r["id"] == "WIN" and r["name"] == "Winchester" for r in results)
|
|
68
68
|
|
|
69
69
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_openpublictransport-0.1.11 → python_openpublictransport-0.1.12}/tests/test_national_rail.py
RENAMED
|
File without changes
|