python-openpublictransport 0.1.7__tar.gz → 0.1.8__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 (51) hide show
  1. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/PKG-INFO +1 -1
  2. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/pyproject.toml +1 -1
  3. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/national_rail.py +16 -4
  4. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/python_openpublictransport.egg-info/PKG-INFO +1 -1
  5. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/python_openpublictransport.egg-info/SOURCES.txt +2 -1
  6. python_openpublictransport-0.1.8/tests/test_national_rail.py +91 -0
  7. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/setup.cfg +0 -0
  8. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/__init__.py +0 -0
  9. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/const.py +0 -0
  10. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/exceptions.py +0 -0
  11. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/models.py +0 -0
  12. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/parsers.py +0 -0
  13. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/__init__.py +0 -0
  14. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/avv.py +0 -0
  15. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/base.py +0 -0
  16. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/beg.py +0 -0
  17. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/bsvg.py +0 -0
  18. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/bvg.py +0 -0
  19. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/db.py +0 -0
  20. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/ding.py +0 -0
  21. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/efa_base.py +0 -0
  22. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/fptf_base.py +0 -0
  23. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/gtfsde.py +0 -0
  24. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/hvv.py +0 -0
  25. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/kvv.py +0 -0
  26. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/mvv.py +0 -0
  27. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/nta.py +0 -0
  28. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/nvbw.py +0 -0
  29. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/nwl.py +0 -0
  30. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/oebb.py +0 -0
  31. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/otp.py +0 -0
  32. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/otp_base.py +0 -0
  33. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/otp_custom.py +0 -0
  34. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/rejseplanen.py +0 -0
  35. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/rmv.py +0 -0
  36. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/rvv.py +0 -0
  37. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/sbb.py +0 -0
  38. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/trafiklab.py +0 -0
  39. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/transitous.py +0 -0
  40. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/trias_base.py +0 -0
  41. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/vagfr.py +0 -0
  42. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/vbn.py +0 -0
  43. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/vgn.py +0 -0
  44. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/vrn.py +0 -0
  45. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/vrr.py +0 -0
  46. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/vvo.py +0 -0
  47. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/providers/vvs.py +0 -0
  48. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/openpublictransport/py.typed +0 -0
  49. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/python_openpublictransport.egg-info/dependency_links.txt +0 -0
  50. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/python_openpublictransport.egg-info/requires.txt +0 -0
  51. {python_openpublictransport-0.1.7 → python_openpublictransport-0.1.8}/src/python_openpublictransport.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-openpublictransport
3
- Version: 0.1.7
3
+ Version: 0.1.8
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.7"
7
+ version = "0.1.8"
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" }
@@ -274,10 +274,22 @@ class NationalRailProvider(BaseProvider):
274
274
  )
275
275
 
276
276
  async def search_stops(self, search_term: str) -> List[Dict[str, Any]]:
277
- """Find UK railway stations with CRS codes via Overpass API (OSM)."""
278
- escaped = re.sub(r"[.*+?^${}()|[\]\\]", r"\\\g<0>", search_term)
279
-
280
- query = f"""[out:json][timeout:15];
277
+ """Find UK railway stations with CRS codes via Overpass API (OSM).
278
+
279
+ A search term that looks like a 3-letter CRS code (e.g. "WIN", "RDG")
280
+ is matched directly against the ``ref:crs`` tag; anything else is
281
+ matched against the station name(s).
282
+ """
283
+ term = search_term.strip()
284
+
285
+ if re.fullmatch(r"[A-Za-z]{3}", term):
286
+ # Direct CRS code lookup (ref:crs is stored uppercase in OSM)
287
+ query = f"""[out:json][timeout:15];
288
+ node["railway"="station"]["ref:crs"="{term.upper()}"]({_UK_BBOX});
289
+ out 10;"""
290
+ else:
291
+ escaped = re.sub(r"[.*+?^${}()|[\]\\]", r"\\\g<0>", term)
292
+ query = f"""[out:json][timeout:15];
281
293
  (
282
294
  node["railway"="station"]["ref:crs"]["name"~"{escaped}",i]({_UK_BBOX});
283
295
  node["railway"="station"]["ref:crs"]["official_name"~"{escaped}",i]({_UK_BBOX});
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-openpublictransport
3
- Version: 0.1.7
3
+ Version: 0.1.8
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
@@ -45,4 +45,5 @@ src/python_openpublictransport.egg-info/PKG-INFO
45
45
  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
- src/python_openpublictransport.egg-info/top_level.txt
48
+ src/python_openpublictransport.egg-info/top_level.txt
49
+ tests/test_national_rail.py
@@ -0,0 +1,91 @@
1
+ """Tests for the National Rail (UK) provider stop search.
2
+
3
+ Focus: a 3-letter CRS code (e.g. "WIN") must be looked up directly against the
4
+ OSM ``ref:crs`` tag, while a longer term is matched against station names.
5
+ Regression test for https://github.com/NerdySoftPaw/openpublictransport/issues/39
6
+ """
7
+
8
+ import pytest
9
+
10
+ from openpublictransport.providers.national_rail import NationalRailProvider
11
+
12
+
13
+ class _FakeResponse:
14
+ def __init__(self, payload):
15
+ self._payload = payload
16
+ self.status = 200
17
+
18
+ async def __aenter__(self):
19
+ return self
20
+
21
+ async def __aexit__(self, *exc):
22
+ return False
23
+
24
+ async def json(self, content_type=None):
25
+ return self._payload
26
+
27
+
28
+ class _FakeSession:
29
+ """Records the Overpass query and returns a canned payload."""
30
+
31
+ def __init__(self, payload):
32
+ self._payload = payload
33
+ self.last_query = None
34
+
35
+ def post(self, url, data=None, headers=None, timeout=None):
36
+ # The Overpass query is sent as form data under the "data" key.
37
+ self.last_query = (data or {}).get("data", "")
38
+ return _FakeResponse(self._payload)
39
+
40
+
41
+ _PAYLOAD = {
42
+ "elements": [
43
+ {
44
+ "type": "node",
45
+ "tags": {
46
+ "railway": "station",
47
+ "ref:crs": "WIN",
48
+ "name": "Winchester",
49
+ "operator": "South Western Railway",
50
+ },
51
+ }
52
+ ]
53
+ }
54
+
55
+
56
+ async def _search(term):
57
+ session = _FakeSession(_PAYLOAD)
58
+ provider = NationalRailProvider(session)
59
+ results = await provider.search_stops(term)
60
+ return results, session.last_query
61
+
62
+
63
+ async def test_crs_code_search_queries_ref_crs():
64
+ """A 3-letter code searches ref:crs directly, not the name."""
65
+ results, query = await _search("WIN")
66
+
67
+ assert '["ref:crs"="WIN"]' in query
68
+ assert '"name"~' not in query
69
+
70
+ assert results == [
71
+ {
72
+ "id": "WIN",
73
+ "name": "Winchester",
74
+ "place": "South Western Railway",
75
+ "area_type": "stop",
76
+ }
77
+ ]
78
+
79
+
80
+ async def test_lowercase_code_is_uppercased():
81
+ """CRS codes are case-insensitive and normalised to uppercase."""
82
+ _, query = await _search("win")
83
+ assert '["ref:crs"="WIN"]' in query
84
+
85
+
86
+ async def test_name_search_queries_name_tags():
87
+ """A longer term matches the name tags, not an exact CRS code."""
88
+ _, query = await _search("Winchester")
89
+
90
+ assert '"name"~"Winchester"' in query
91
+ assert '["ref:crs"="' not in query