python-openpublictransport 0.1.8__tar.gz → 0.1.9__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 (52) hide show
  1. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/PKG-INFO +1 -1
  2. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/pyproject.toml +1 -1
  3. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/national_rail.py +10 -3
  4. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/python_openpublictransport.egg-info/PKG-INFO +1 -1
  5. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/python_openpublictransport.egg-info/SOURCES.txt +2 -1
  6. python_openpublictransport-0.1.9/tests/test_national_rail_parse.py +92 -0
  7. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/setup.cfg +0 -0
  8. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/__init__.py +0 -0
  9. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/const.py +0 -0
  10. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/exceptions.py +0 -0
  11. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/models.py +0 -0
  12. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/parsers.py +0 -0
  13. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/__init__.py +0 -0
  14. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/avv.py +0 -0
  15. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/base.py +0 -0
  16. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/beg.py +0 -0
  17. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/bsvg.py +0 -0
  18. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/bvg.py +0 -0
  19. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/db.py +0 -0
  20. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/ding.py +0 -0
  21. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/efa_base.py +0 -0
  22. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/fptf_base.py +0 -0
  23. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/gtfsde.py +0 -0
  24. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/hvv.py +0 -0
  25. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/kvv.py +0 -0
  26. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/mvv.py +0 -0
  27. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/nta.py +0 -0
  28. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/nvbw.py +0 -0
  29. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/nwl.py +0 -0
  30. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/oebb.py +0 -0
  31. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/otp.py +0 -0
  32. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/otp_base.py +0 -0
  33. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/otp_custom.py +0 -0
  34. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/rejseplanen.py +0 -0
  35. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/rmv.py +0 -0
  36. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/rvv.py +0 -0
  37. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/sbb.py +0 -0
  38. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/trafiklab.py +0 -0
  39. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/transitous.py +0 -0
  40. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/trias_base.py +0 -0
  41. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/vagfr.py +0 -0
  42. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/vbn.py +0 -0
  43. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/vgn.py +0 -0
  44. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/vrn.py +0 -0
  45. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/vrr.py +0 -0
  46. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/vvo.py +0 -0
  47. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/providers/vvs.py +0 -0
  48. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/openpublictransport/py.typed +0 -0
  49. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/python_openpublictransport.egg-info/dependency_links.txt +0 -0
  50. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/python_openpublictransport.egg-info/requires.txt +0 -0
  51. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/src/python_openpublictransport.egg-info/top_level.txt +0 -0
  52. {python_openpublictransport-0.1.8 → python_openpublictransport-0.1.9}/tests/test_national_rail.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-openpublictransport
3
- Version: 0.1.8
3
+ Version: 0.1.9
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.8"
7
+ version = "0.1.9"
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" }
@@ -46,10 +46,17 @@ _SOAP_TEMPLATE = """<?xml version="1.0" encoding="UTF-8"?>
46
46
 
47
47
 
48
48
  def _strip_namespaces(xml_string: str) -> str:
49
- """Remove XML namespace prefixes and declarations for simpler parsing."""
49
+ """Remove XML namespace prefixes and declarations for simpler parsing.
50
+
51
+ Real OpenLDBWS responses use namespace prefixes that contain digits
52
+ (``lt4:``, ``lt5:``, ``lt7:``, ``lt8:`` …). The prefix pattern must
53
+ therefore allow digits — matching only ``[a-zA-Z]+`` leaves those tags
54
+ prefixed after their xmlns declarations have been stripped, producing an
55
+ "unbound prefix" XML parse error and an empty departure board.
56
+ """
50
57
  xml_string = re.sub(r' xmlns[^=]*="[^"]*"', "", xml_string)
51
- xml_string = re.sub(r"<([a-zA-Z]+):", "<", xml_string)
52
- xml_string = re.sub(r"</([a-zA-Z]+):", "</", xml_string)
58
+ xml_string = re.sub(r"<([A-Za-z][\w.\-]*):", "<", xml_string)
59
+ xml_string = re.sub(r"</([A-Za-z][\w.\-]*):", "</", xml_string)
53
60
  return xml_string
54
61
 
55
62
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-openpublictransport
3
- Version: 0.1.8
3
+ Version: 0.1.9
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
@@ -46,4 +46,5 @@ src/python_openpublictransport.egg-info/SOURCES.txt
46
46
  src/python_openpublictransport.egg-info/dependency_links.txt
47
47
  src/python_openpublictransport.egg-info/requires.txt
48
48
  src/python_openpublictransport.egg-info/top_level.txt
49
- tests/test_national_rail.py
49
+ tests/test_national_rail.py
50
+ tests/test_national_rail_parse.py
@@ -0,0 +1,92 @@
1
+ """Tests for National Rail (UK) OpenLDBWS response parsing.
2
+
3
+ Regression test for the departure board coming back empty ("Invalid or empty
4
+ API response") for every user: real OpenLDBWS responses use namespace prefixes
5
+ containing digits (`lt4:`, `lt5:`, `lt7:` …), which the namespace-stripping
6
+ regex did not handle, causing an "unbound prefix" XML parse error.
7
+ See https://github.com/NerdySoftPaw/openpublictransport/issues/39
8
+ """
9
+
10
+ from datetime import datetime
11
+ from xml.etree import ElementTree as ET
12
+ from zoneinfo import ZoneInfo
13
+
14
+ import openpublictransport.providers.national_rail as nr
15
+
16
+ # A realistic OpenLDBWS GetDepartureBoardResponse: digit-bearing prefixes
17
+ # (lt4/lt5/lt7) and their xmlns declarations, exactly as the live service emits.
18
+ SAMPLE = """<?xml version="1.0" encoding="utf-8"?>
19
+ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
20
+ <soap:Body>
21
+ <GetDepartureBoardResponse xmlns="http://thalesgroup.com/RTTI/2017-10-01/ldb/">
22
+ <GetStationBoardResult xmlns:lt="http://thalesgroup.com/RTTI/2012-01-13/ldb/types" xmlns:lt4="http://thalesgroup.com/RTTI/2015-11-27/ldb/types" xmlns:lt5="http://thalesgroup.com/RTTI/2016-02-16/ldb/types" xmlns:lt7="http://thalesgroup.com/RTTI/2017-02-02/ldb/types">
23
+ <lt4:locationName>Winchester</lt4:locationName>
24
+ <lt4:crs>WIN</lt4:crs>
25
+ <lt7:trainServices>
26
+ <lt7:service>
27
+ <lt4:std>18:05</lt4:std>
28
+ <lt4:etd>On time</lt4:etd>
29
+ <lt4:platform>2</lt4:platform>
30
+ <lt4:operator>South Western Railway</lt4:operator>
31
+ <lt4:operatorCode>SW</lt4:operatorCode>
32
+ <lt5:destination>
33
+ <lt4:location>
34
+ <lt4:locationName>London Waterloo</lt4:locationName>
35
+ <lt4:crs>WAT</lt4:crs>
36
+ </lt4:location>
37
+ </lt5:destination>
38
+ </lt7:service>
39
+ <lt7:service>
40
+ <lt4:std>18:20</lt4:std>
41
+ <lt4:etd>18:27</lt4:etd>
42
+ <lt4:operator>Great Western Railway</lt4:operator>
43
+ <lt4:operatorCode>GW</lt4:operatorCode>
44
+ <lt5:destination>
45
+ <lt4:location>
46
+ <lt4:locationName>Southampton Central</lt4:locationName>
47
+ <lt4:crs>SOU</lt4:crs>
48
+ </lt4:location>
49
+ </lt5:destination>
50
+ </lt7:service>
51
+ </lt7:trainServices>
52
+ </GetStationBoardResult>
53
+ </GetDepartureBoardResponse>
54
+ </soap:Body>
55
+ </soap:Envelope>"""
56
+
57
+
58
+ def test_strip_namespaces_handles_digit_prefixes():
59
+ """Digit-bearing prefixes must be stripped so the XML parses."""
60
+ stripped = nr._strip_namespaces(SAMPLE)
61
+ # No prefixed tags and no dangling xmlns declarations should remain.
62
+ assert "lt4:" not in stripped
63
+ assert "lt7:" not in stripped
64
+ assert "xmlns" not in stripped
65
+ # Must now be parseable (previously raised "unbound prefix").
66
+ root = ET.fromstring(stripped)
67
+ assert len(root.findall(".//service")) == 2
68
+
69
+
70
+ def test_parses_services_into_departures():
71
+ """The full parse path yields the expected departures."""
72
+ root = ET.fromstring(nr._strip_namespaces(SAMPLE))
73
+ provider = nr.NationalRailProvider(session=None)
74
+ tz = ZoneInfo("Europe/London")
75
+ now = datetime(2026, 7, 2, 18, 0, tzinfo=tz)
76
+
77
+ deps = []
78
+ for svc in root.findall(".//service"):
79
+ d = provider._service_to_dict(svc, now.date(), tz, now)
80
+ assert d is not None
81
+ deps.append(provider.parse_departure(d, tz, now))
82
+
83
+ assert deps[0].destination == "London Waterloo"
84
+ assert deps[0].planned_time == "18:05"
85
+ assert deps[0].platform == "2"
86
+ assert deps[0].line == "SW"
87
+ assert deps[0].delay == 0
88
+
89
+ # Second service is estimated 7 minutes late (18:05 std -> 18:27 etd on 18:20).
90
+ assert deps[1].destination == "Southampton Central"
91
+ assert deps[1].delay == 7
92
+ assert deps[1].is_realtime is True