koleo-cli 0.2.137.4__tar.gz → 0.2.137.6__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.

Potentially problematic release.


This version of koleo-cli might be problematic. Click here for more details.

@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.1
2
+ Name: koleo-cli
3
+ Version: 0.2.137.6
4
+ Summary: Koleo CLI
5
+ Home-page: https://github.com/lzgirlcat/koleo-cli
6
+ Author: Zoey !
7
+ Maintainer-email: cb98uzhd@duck.com
8
+ License: GNU General Public License v3.0
9
+ Project-URL: Source (GitHub), https://github.com/lzgirlcat/koleo-cli
10
+ Project-URL: Issue Tracker, https://github.com/lzgirlcat/koleo-cli/issues
11
+ Keywords: koleo,timetable,trains,rail,poland
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.12
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: rich==13.7.1
17
+ Requires-Dist: requests==2.31.0
18
+
19
+ [![PyPI - Version](https://img.shields.io/pypi/v/koleo-cli.svg)](https://pypi.org/project/koleo-cli)
20
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/koleo-cli.svg)](https://pypi.org/project/koleo-cli)
21
+
22
+ # Koleo CLI
23
+
24
+ ![gif showcasing the functionality](https://github.com/lzgirlcat/koleo-cli/blob/main/koleo-cli.gif?raw=true)
25
+
26
+
27
+ install via pip by running
28
+
29
+ `pip install koleo-cli`
30
+
31
+ ## it currently allows you to:
32
+ - get departures/arrival list for a station
33
+ - get train info given its number and name(pull requests are welcome if you know how to get a train object by just the number)
34
+ - find a station or list all known stations
35
+ - find a connection from station a to b, with filtering by operators
36
+ - save a station as your favourite to quickly check it's departures
37
+
38
+ additionally you can also use the KoleoAPI wrapper directly in your own projects, all returns are fully typed using `typing.TypedDict`
39
+
40
+ ## MY(possibly controversial) design choices:
41
+ - platforms and track numbers are shown using arabic numerals instead of roman
42
+ - you can change it by adding `use_roman_numerals: true` to your config.json file
43
+ - most api queries are cached for 24h
44
+ - you can change it by adding `disable_cache: true` to your config.json file
45
+ - the cli.py code is really dirty but printing formatted data is hard :<
46
+
47
+ pull requests are welcome!!
48
+
49
+ ```
50
+ usage: koleo [-h] [-c CONFIG] [--nocolor] {departures,d,dep,odjazdy,o,arrivals,a,arr,przyjazdy,p,trainroute,r,tr,t,poc,pociąg,stations,s,find,f,stacje,ls,connections,do,z,szukaj,path} ...
51
+
52
+ Koleo CLI
53
+
54
+ options:
55
+ -h, --help show this help message and exit
56
+ -c CONFIG, --config CONFIG
57
+ Custom config path.
58
+ --nocolor Disable color output and formatting
59
+
60
+ actions:
61
+ {departures,d,dep,odjazdy,o,arrivals,a,arr,przyjazdy,p,trainroute,r,tr,t,poc,pociąg,stations,s,find,f,stacje,ls,connections,do,z,szukaj,path}
62
+ departures (d, dep, odjazdy, o)
63
+ Allows you to list station departures
64
+ arrivals (a, arr, przyjazdy, p)
65
+ Allows you to list station departures
66
+ trainroute (r, tr, t, poc, pociąg)
67
+ Allows you to show the train's route
68
+ stations (s, find, f, stacje, ls)
69
+ Allows you to find stations by their name
70
+ connections (do, z, szukaj, path)
71
+ Allows you to search for connections from a to b
72
+ ```
@@ -0,0 +1,54 @@
1
+ [![PyPI - Version](https://img.shields.io/pypi/v/koleo-cli.svg)](https://pypi.org/project/koleo-cli)
2
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/koleo-cli.svg)](https://pypi.org/project/koleo-cli)
3
+
4
+ # Koleo CLI
5
+
6
+ ![gif showcasing the functionality](https://github.com/lzgirlcat/koleo-cli/blob/main/koleo-cli.gif?raw=true)
7
+
8
+
9
+ install via pip by running
10
+
11
+ `pip install koleo-cli`
12
+
13
+ ## it currently allows you to:
14
+ - get departures/arrival list for a station
15
+ - get train info given its number and name(pull requests are welcome if you know how to get a train object by just the number)
16
+ - find a station or list all known stations
17
+ - find a connection from station a to b, with filtering by operators
18
+ - save a station as your favourite to quickly check it's departures
19
+
20
+ additionally you can also use the KoleoAPI wrapper directly in your own projects, all returns are fully typed using `typing.TypedDict`
21
+
22
+ ## MY(possibly controversial) design choices:
23
+ - platforms and track numbers are shown using arabic numerals instead of roman
24
+ - you can change it by adding `use_roman_numerals: true` to your config.json file
25
+ - most api queries are cached for 24h
26
+ - you can change it by adding `disable_cache: true` to your config.json file
27
+ - the cli.py code is really dirty but printing formatted data is hard :<
28
+
29
+ pull requests are welcome!!
30
+
31
+ ```
32
+ usage: koleo [-h] [-c CONFIG] [--nocolor] {departures,d,dep,odjazdy,o,arrivals,a,arr,przyjazdy,p,trainroute,r,tr,t,poc,pociąg,stations,s,find,f,stacje,ls,connections,do,z,szukaj,path} ...
33
+
34
+ Koleo CLI
35
+
36
+ options:
37
+ -h, --help show this help message and exit
38
+ -c CONFIG, --config CONFIG
39
+ Custom config path.
40
+ --nocolor Disable color output and formatting
41
+
42
+ actions:
43
+ {departures,d,dep,odjazdy,o,arrivals,a,arr,przyjazdy,p,trainroute,r,tr,t,poc,pociąg,stations,s,find,f,stacje,ls,connections,do,z,szukaj,path}
44
+ departures (d, dep, odjazdy, o)
45
+ Allows you to list station departures
46
+ arrivals (a, arr, przyjazdy, p)
47
+ Allows you to list station departures
48
+ trainroute (r, tr, t, poc, pociąg)
49
+ Allows you to show the train's route
50
+ stations (s, find, f, stacje, ls)
51
+ Allows you to find stations by their name
52
+ connections (do, z, szukaj, path)
53
+ Allows you to search for connections from a to b
54
+ ```
@@ -37,11 +37,9 @@ class errors:
37
37
  class KoleoForbidden(KoleoAPIException):
38
38
  pass
39
39
 
40
-
41
40
  class KoleoUnauthorized(KoleoAPIException):
42
41
  pass
43
42
 
44
-
45
43
  class KoleoRatelimited(KoleoAPIException):
46
44
  pass
47
45
 
@@ -7,11 +7,11 @@ from rich.traceback import install
7
7
 
8
8
  from .api import KoleoAPI
9
9
  from .storage import DEFAULT_CONFIG_PATH, Storage
10
- from .types import TrainDetailResponse, TrainOnStationInfo, ExtendedBaseStationInfo
11
- from .utils import convert_platform_number, name_to_slug, parse_datetime, arr_dep_to_dt
10
+ from .types import ExtendedBaseStationInfo, TrainDetailResponse, TrainOnStationInfo
11
+ from .utils import RemainderString, arr_dep_to_dt, convert_platform_number, name_to_slug, parse_datetime
12
12
 
13
13
 
14
- install(show_locals=False, max_frames=2)
14
+ install(show_locals=True, max_frames=2)
15
15
 
16
16
 
17
17
  class CLI:
@@ -28,7 +28,7 @@ class CLI:
28
28
 
29
29
  def print(self, text, *args, **kwargs):
30
30
  if self.no_color:
31
- result = re.sub(r'\[[^\]]*\]', '', text)
31
+ result = re.sub(r"\[[^\]]*\]", "", text)
32
32
  print(result)
33
33
  else:
34
34
  self.console.print(text, *args, **kwargs)
@@ -55,9 +55,8 @@ class CLI:
55
55
 
56
56
  def get_departures(self, station_id: int, date: datetime):
57
57
  cache_id = f"dep-{station_id}-{date.strftime("%Y-%m-%d")}"
58
- trains = (
59
- self.storage.get_cache(cache_id) or
60
- self.storage.set_cache(cache_id, self.client.get_departures(station_id, date))
58
+ trains = self.storage.get_cache(cache_id) or self.storage.set_cache(
59
+ cache_id, self.client.get_departures(station_id, date)
61
60
  )
62
61
  trains = [
63
62
  i
@@ -70,9 +69,8 @@ class CLI:
70
69
 
71
70
  def get_arrivals(self, station_id: int, date: datetime):
72
71
  cache_id = f"arr-{station_id}-{date.strftime("%Y-%m-%d")}"
73
- trains = (
74
- self.storage.get_cache(cache_id) or
75
- self.storage.set_cache(cache_id, self.client.get_arrivals(station_id, date))
72
+ trains = self.storage.get_cache(cache_id) or self.storage.set_cache(
73
+ cache_id, self.client.get_arrivals(station_id, date)
76
74
  )
77
75
  trains = [
78
76
  i
@@ -99,22 +97,23 @@ class CLI:
99
97
  if query:
100
98
  stations = self.client.find_station(query)
101
99
  else:
102
- stations = (
103
- self.storage.get_cache("stations") or
104
- self.storage.set_cache("stations", self.client.get_stations())
100
+ stations = self.storage.get_cache("stations") or self.storage.set_cache(
101
+ "stations", self.client.get_stations()
105
102
  )
106
103
  for st in stations:
107
- self.print(f"[bold blue][link=https://koleo.pl/dworzec-pkp/{st["name_slug"]}]{st["name"]}[/bold blue] ID: {st["id"]}[/link]")
104
+ self.print(
105
+ f"[bold blue][link=https://koleo.pl/dworzec-pkp/{st["name_slug"]}]{st["name"]}[/bold blue] ID: {st["id"]}[/link]"
106
+ )
108
107
 
109
108
  def train_info(self, brand: str, name: str, date: datetime):
110
109
  brand = brand.upper().strip()
111
- name = name.strip()
112
- if name.isnumeric():
113
- number = int(name)
110
+ name_parts = name.split(" ")
111
+ if len(name_parts) == 1 and name_parts[0].isnumeric():
112
+ number = int(name_parts[0])
114
113
  train_name = ""
115
- elif len((parts := name.split(" "))) == 2 or len((parts := name.split("-"))) == 2:
116
- number, train_name = parts
117
- number = int(number)
114
+ elif len(name) > 1:
115
+ number = int(name_parts.pop(0))
116
+ train_name = " ".join(name_parts)
118
117
  else:
119
118
  raise ValueError("Invalid train name!")
120
119
  brands = self.storage.get_cache("brands") or self.storage.set_cache("brands", self.client.get_brands())
@@ -124,20 +123,28 @@ class CLI:
124
123
  raise ValueError("Invalid brand name!")
125
124
  brand = res
126
125
  cache_id = f"tc-{brand}-{number}-{name}"
127
- train_calendars = self.storage.get_cache(cache_id) or self.storage.set_cache(
128
- cache_id, self.client.get_train_calendars(brand, number, train_name)
129
- )
126
+ try:
127
+ train_calendars = self.storage.get_cache(cache_id) or self.storage.set_cache(
128
+ cache_id, self.client.get_train_calendars(brand, number, train_name)
129
+ )
130
+ except self.client.errors.KoleoNotFound:
131
+ self.print(f'[bold red]Train not found: nr={number}, name="{train_name}"[/bold red]')
132
+ exit(2)
130
133
  train_id = train_calendars["train_calendars"][0]["date_train_map"][date.strftime("%Y-%m-%d")]
131
134
  train_details = self.client.get_train(train_id)
132
135
  brand = next(iter(i for i in brands if i["id"] == train_details["train"]["brand_id"]), {}).get("logo_text", "")
133
136
  parts = [f"[red]{brand}[/red] [bold blue]{train_details["train"]["train_full_name"]}[/bold blue]"]
134
137
  route_start = arr_dep_to_dt(train_details["stops"][0]["departure"])
135
138
  route_end = arr_dep_to_dt(train_details["stops"][-1]["arrival"])
136
- if route_end.hour < route_start.hour or (route_end.hour==route_start.hour and route_end.minute < route_end.minute):
139
+ if route_end.hour < route_start.hour or (
140
+ route_end.hour == route_start.hour and route_end.minute < route_end.minute
141
+ ):
137
142
  route_end += timedelta(days=1)
138
143
  travel_time = route_end - route_start
139
144
  speed = train_details["stops"][-1]["distance"] / 1000 / travel_time.seconds * 3600
140
- parts.append(f"[white] {travel_time.seconds//3600}h{(travel_time.seconds % 3600)/60:.0f}m {speed:^4.1f}km/h [/white]")
145
+ parts.append(
146
+ f"[white] {travel_time.seconds//3600}h{(travel_time.seconds % 3600)/60:.0f}m {speed:^4.1f}km/h [/white]"
147
+ )
141
148
  vehicle_types: dict[str, str] = {
142
149
  stop["station_display_name"]: stop["vehicle_type"]
143
150
  for stop in train_details["stops"]
@@ -162,24 +169,27 @@ class CLI:
162
169
  if not brands:
163
170
  connection_brands = [i["id"] for i in api_brands]
164
171
  else:
165
- connection_brands = [i["id"] for i in api_brands if i["name"].lower().strip() in brands or i["logo_text"].lower().strip() in brands]
172
+ connection_brands = [
173
+ i["id"]
174
+ for i in api_brands
175
+ if i["name"].lower().strip() in brands or i["logo_text"].lower().strip() in brands
176
+ ]
166
177
  if not connection_brands:
167
178
  self.print(f'[bold red]No brands match: "{', '.join(brands)}"[/bold red]')
168
179
  exit(2)
169
180
  connections = self.client.get_connections(
170
- start_station["name_slug"],
171
- end_station["name_slug"],
172
- connection_brands,
173
- date,
174
- direct,
175
- purchasable
181
+ start_station["name_slug"], end_station["name_slug"], connection_brands, date, direct, purchasable
176
182
  )
177
- parts = [f"[bold blue]{start_station["name"]} → {end_station["name"]} at {date.strftime("%H:%M %d-%m")}[/bold blue]"]
183
+ parts = [
184
+ f"[bold blue]{start_station["name"]} → {end_station["name"]} at {date.strftime("%H:%M %d-%m")}[/bold blue]"
185
+ ]
178
186
  for i in connections:
179
187
  arr = arr_dep_to_dt(i["arrival"])
180
188
  dep = arr_dep_to_dt(i["departure"])
181
189
  travel_time = (arr - dep).seconds
182
- parts.append(f"[bold green][link=https://koleo.pl/travel-options/{i["id"]}]{dep.strftime("%H:%M")} - {arr.strftime("%H:%M")}[/bold green] {travel_time//3600}h{(travel_time % 3600)/60:.0f}m {i['distance']}km: [/link]")
190
+ parts.append(
191
+ f"[bold green][link=https://koleo.pl/travel-options/{i["id"]}]{dep.strftime("%H:%M")} - {arr.strftime("%H:%M")}[/bold green] {travel_time//3600}h{(travel_time % 3600)/60:.0f}m {i['distance']}km: [/link]"
192
+ )
183
193
  if i["constriction_info"]:
184
194
  for constriction in i["constriction_info"]:
185
195
  parts.append(f" [bold red]- {constriction} [/bold red]")
@@ -187,14 +197,17 @@ class CLI:
187
197
  train = i["trains"][0]
188
198
  stop = next(iter(i for i in train["stops"] if i["station_id"] == train["start_station_id"]), {})
189
199
  stop_station = (
190
- start_station if stop["station_id"] == start_station["id"] else
191
- self.storage.get_cache(f"st-{stop['station_id']}")
192
- or self.storage.set_cache(f"st-{stop['station_id']}", self.client.get_station_by_id(stop['station_id']))
200
+ start_station
201
+ if stop["station_id"] == start_station["id"]
202
+ else self.storage.get_cache(f"st-{stop['station_id']}")
203
+ or self.storage.set_cache(
204
+ f"st-{stop['station_id']}", self.client.get_station_by_id(stop["station_id"])
205
+ )
193
206
  )
194
- platform = convert_platform_number(stop["platform"]) if stop["platform"] else ""
195
- position_info = f"{platform}/{stop["track"]}" if stop["track"] else platform
196
207
  brand = next(iter(i for i in api_brands if i["id"] == train["brand_id"]), {}).get("logo_text")
197
- parts[-1]+=f" [red]{brand}[/red] {train["train_full_name"]}[purple] {stop_station['name']} {position_info}[/purple]"
208
+ parts[-1] += (
209
+ f" [red]{brand}[/red] {train["train_full_name"]}[purple] {stop_station['name']} {self.format_position(stop["platform"], stop["track"])}[/purple]"
210
+ )
198
211
  else:
199
212
  previous_arrival: datetime | None = None
200
213
  for train in i["trains"]:
@@ -202,29 +215,35 @@ class CLI:
202
215
 
203
216
  fs = next(iter(i for i in train["stops"] if i["station_id"] == train["start_station_id"]), {})
204
217
  fs_station = (
205
- start_station if fs["station_id"] == start_station["id"] else
206
- self.storage.get_cache(f"st-{fs['station_id']}")
207
- or self.storage.set_cache(f"st-{fs['station_id']}", self.client.get_station_by_id(fs['station_id']))
218
+ start_station
219
+ if fs["station_id"] == start_station["id"]
220
+ else self.storage.get_cache(f"st-{fs['station_id']}")
221
+ or self.storage.set_cache(
222
+ f"st-{fs['station_id']}", self.client.get_station_by_id(fs["station_id"])
223
+ )
208
224
  )
209
- fs_platform = convert_platform_number(fs["platform"]) if fs["platform"] else ""
210
- fs_arr = arr_dep_to_dt(fs["arrival"])
225
+ # fs_arr = arr_dep_to_dt(fs["arrival"])
211
226
  fs_dep = arr_dep_to_dt(fs["departure"])
212
- fs_info = f"[bold green]{fs_dep.strftime("%H:%M")} [/bold green][purple]{fs_station['name']} {f"{fs_platform}/{fs["track"]}" if fs["track"] else fs_platform}[/purple]"
227
+ fs_info = f"[bold green]{fs_dep.strftime("%H:%M")} [/bold green][purple]{fs_station['name']} {self.format_position(fs["platform"], fs["track"])}[/purple]"
213
228
 
214
229
  ls = next(iter(i for i in train["stops"] if i["station_id"] == train["end_station_id"]), {})
215
230
  ls_station = (
216
- start_station if ls["station_id"] == start_station["id"] else
217
- self.storage.get_cache(f"st-{ls['station_id']}")
218
- or self.storage.set_cache(f"st-{ls['station_id']}", self.client.get_station_by_id(ls['station_id']))
231
+ start_station
232
+ if ls["station_id"] == start_station["id"]
233
+ else self.storage.get_cache(f"st-{ls['station_id']}")
234
+ or self.storage.set_cache(
235
+ f"st-{ls['station_id']}", self.client.get_station_by_id(ls["station_id"])
236
+ )
219
237
  )
220
- ls_platform = convert_platform_number(ls["platform"]) if ls["platform"] else ""
221
238
  ls_arr = arr_dep_to_dt(ls["arrival"])
222
- ls_dep = arr_dep_to_dt(ls["departure"])
223
- ls_info = f"[bold green]{ls_arr.strftime("%H:%M")} [/bold green][purple]{ls_station['name']} {f"{ls_platform}/{ls["track"]}" if ls["track"] else ls_platform}[/purple]"
239
+ # ls_dep = arr_dep_to_dt(ls["departure"])
240
+ ls_info = f"[bold green]{ls_arr.strftime("%H:%M")} [/bold green][purple]{ls_station['name']} {self.format_position(ls["platform"], ls["track"])}[/purple]"
224
241
  connection_time = (fs_dep - previous_arrival).seconds if previous_arrival else ""
225
242
  previous_arrival = ls_arr
226
243
  if connection_time:
227
- parts.append(f" {connection_time//3600}h{(connection_time % 3600)/60:.0f}m at [purple]{fs_station['name']}[/purple]")
244
+ parts.append(
245
+ f" {connection_time//3600}h{(connection_time % 3600)/60:.0f}m at [purple]{fs_station['name']}[/purple]"
246
+ )
228
247
  parts.append(f" [red]{brand}[/red] {train["train_full_name"]} {fs_info} - {ls_info}")
229
248
  self.print("\n".join(parts))
230
249
 
@@ -235,10 +254,8 @@ class CLI:
235
254
  time = train["departure"] if type == 1 else train["arrival"]
236
255
  assert time
237
256
  brand = next(iter(i for i in brands if i["id"] == train["brand_id"]), {}).get("logo_text")
238
- platform = convert_platform_number(train["platform"]) if train["platform"] else ""
239
- position_info = f"{platform}/{train["track"]}" if train["track"] else platform
240
257
  parts.append(
241
- f"[bold green]{time[11:16]}[/bold green] [red]{brand}[/red] {train["train_full_name"]}[purple] {train["stations"][0]["name"]} {position_info}[/purple]"
258
+ f"[bold green]{time[11:16]}[/bold green] [red]{brand}[/red] {train["train_full_name"]}[purple] {train["stations"][0]["name"]} {self.format_position(train["platform"], train["track"])}[/purple]"
242
259
  )
243
260
  return "\n".join(parts)
244
261
 
@@ -247,12 +264,17 @@ class CLI:
247
264
  for stop in train["stops"]:
248
265
  arr = arr_dep_to_dt(stop["arrival"])
249
266
  dep = arr_dep_to_dt(stop["departure"])
250
- platform = convert_platform_number(stop["platform"]) or ""
251
267
  parts.append(
252
- f"[white underline]{stop["distance"] / 1000:^5.1f}km[/white underline] [bold green]{arr.strftime("%H:%M")}[/bold green] - [bold red]{dep.strftime("%H:%M")}[/bold red] [purple]{stop["station_display_name"]} {platform} [/purple]"
268
+ f"[white underline]{stop["distance"] / 1000:^5.1f}km[/white underline] [bold green]{arr.strftime("%H:%M")}[/bold green] - [bold red]{dep.strftime("%H:%M")}[/bold red] [purple]{stop["station_display_name"]} {self.format_position(stop["platform"])} [/purple]"
253
269
  )
254
270
  return "\n".join(parts)
255
271
 
272
+ def format_position(self, platform: str, track: str | None = None):
273
+ res = str(convert_platform_number(platform)) or "" if not self.storage.use_roman_numerals else platform
274
+ if track is not None and track != "":
275
+ res += f"/{track}"
276
+ return res
277
+
256
278
  def get_station(self, station: str) -> ExtendedBaseStationInfo:
257
279
  slug = name_to_slug(station)
258
280
  try:
@@ -263,20 +285,24 @@ class CLI:
263
285
  self.print(f'[bold red]Station not found: "{station}"[/bold red]')
264
286
  exit(2)
265
287
 
288
+
266
289
  def main():
267
290
  cli = CLI()
268
291
 
269
292
  parser = ArgumentParser("koleo", description="Koleo CLI")
270
293
  parser.add_argument("-c", "--config", help="Custom config path.", default=DEFAULT_CONFIG_PATH)
271
- parser.add_argument("--nocolor", help="Disable color output", action="store_true", default=False)
272
- subparsers = parser.add_subparsers(title="actions", required=False) # type: ignore
294
+ parser.add_argument("--nocolor", help="Disable color output and formatting", action="store_true", default=False)
295
+ subparsers = parser.add_subparsers(title="actions", required=False) # type: ignore
273
296
 
274
- departures = subparsers.add_parser("departures", aliases=["d", "dep", "odjazdy", "o"], help="Allows you to list station departures")
297
+ departures = subparsers.add_parser(
298
+ "departures", aliases=["d", "dep", "odjazdy", "o"], help="Allows you to list station departures"
299
+ )
275
300
  departures.add_argument(
276
301
  "station",
277
302
  help="The station name",
278
303
  default=None,
279
- nargs="?",
304
+ nargs="*",
305
+ action=RemainderString,
280
306
  )
281
307
  departures.add_argument(
282
308
  "-d",
@@ -288,12 +314,15 @@ def main():
288
314
  departures.add_argument("-s", "--save", help="save the station as your default one", action="store_true")
289
315
  departures.set_defaults(func=cli.full_departures, pass_=["station", "date"])
290
316
 
291
- arrivals = subparsers.add_parser("arrivals", aliases=["a", "arr", "przyjazdy", "p"], help="Allows you to list station departures")
317
+ arrivals = subparsers.add_parser(
318
+ "arrivals", aliases=["a", "arr", "przyjazdy", "p"], help="Allows you to list station departures"
319
+ )
292
320
  arrivals.add_argument(
293
321
  "station",
294
322
  help="The station name",
295
323
  default=None,
296
- nargs="?",
324
+ nargs="*",
325
+ action=RemainderString,
297
326
  )
298
327
  arrivals.add_argument(
299
328
  "-d",
@@ -311,7 +340,7 @@ def main():
311
340
  help="Allows you to show the train's route",
312
341
  )
313
342
  train_route.add_argument("brand", help="The brand name", type=str)
314
- train_route.add_argument("name", help="The train name", type=str)
343
+ train_route.add_argument("name", help="The train name", nargs="+", action=RemainderString)
315
344
  train_route.add_argument(
316
345
  "-d",
317
346
  "--date",
@@ -321,12 +350,15 @@ def main():
321
350
  )
322
351
  train_route.set_defaults(func=cli.train_info, pass_=["brand", "name", "date"])
323
352
 
324
- stations = subparsers.add_parser("stations", aliases=["s", "find", "f", "stacje", "ls"], help="Allows you to find stations by their name")
353
+ stations = subparsers.add_parser(
354
+ "stations", aliases=["s", "find", "f", "stacje", "ls", "q"], help="Allows you to find stations by their name"
355
+ )
325
356
  stations.add_argument(
326
357
  "query",
327
358
  help="The station name",
328
359
  default=None,
329
- nargs="?",
360
+ nargs="*",
361
+ action=RemainderString,
330
362
  )
331
363
  stations.set_defaults(func=cli.find_station, pass_=["query"])
332
364
 
@@ -345,22 +377,21 @@ def main():
345
377
  default=datetime.now(),
346
378
  )
347
379
  connections.add_argument(
348
- "-b",
349
- "--brands",
350
- help="Brands to include",
351
- action="extend", nargs="+", type=str, default=[]
380
+ "-b", "--brands", help="Brands to include", action="extend", nargs="+", type=str, default=[]
352
381
  )
353
382
  connections.add_argument(
354
383
  "-f",
355
384
  "--direct",
356
385
  help="whether or not the result should only include direct trains",
357
- action="store_true", default=False
386
+ action="store_true",
387
+ default=False,
358
388
  )
359
389
  connections.add_argument(
360
390
  "-p",
361
391
  "--purchasable",
362
392
  help="whether or not the result should only trains purchasable on koleo",
363
- action="store_true", default=False
393
+ action="store_true",
394
+ default=False,
364
395
  )
365
396
  connections.set_defaults(func=cli.connections, pass_=["start", "end", "brands", "date", "direct", "purchasable"])
366
397
 
@@ -27,9 +27,9 @@ T = t.TypeVar("T")
27
27
  @dataclass
28
28
  class Storage:
29
29
  favourite_station: str | None = None
30
- last_searched_connections: list[tuple[str, str]] = field(default_factory=list)
31
- last_searched_stations: list[str] = field(default_factory=list)
32
30
  cache: dict[str, tuple[int, t.Any]] = field(default_factory=dict)
31
+ disable_cache: bool = False
32
+ use_roman_numerals: bool = False
33
33
 
34
34
  def __post_init__(self):
35
35
  self._path: str
@@ -47,6 +47,8 @@ class Storage:
47
47
  return storage
48
48
 
49
49
  def get_cache(self, id: str) -> t.Any | None:
50
+ if self.disable_cache:
51
+ return
50
52
  cache_result = self.cache.get(id)
51
53
  if not cache_result:
52
54
  return
@@ -58,6 +60,8 @@ class Storage:
58
60
  self.save()
59
61
 
60
62
  def set_cache(self, id: str, item: T, ttl: int = 86400) -> T:
63
+ if self.disable_cache:
64
+ return item
61
65
  self.cache[id] = (int(time() + ttl), item)
62
66
  self.save()
63
67
  return item
@@ -179,8 +179,8 @@ class TrainDetailResponse(t.TypedDict):
179
179
 
180
180
 
181
181
  class ConnectionTrainStop(t.TypedDict):
182
- arrival: TimeDict | str # WTF KOLEO!!!!
183
- departure: TimeDict | str # WTF KOLEO!!!!
182
+ arrival: TimeDict | str # WTF KOLEO!!!!
183
+ departure: TimeDict | str # WTF KOLEO!!!!
184
184
  distance: int
185
185
  in_path: bool
186
186
  station_id: int
@@ -196,8 +196,8 @@ class ConnectionTrainStop(t.TypedDict):
196
196
 
197
197
 
198
198
  class ConnectiontrainDetail(TrainDetail):
199
- arrival: TimeDict | str # WTF KOLEO!!!!
200
- departure: TimeDict | str # WTF KOLEO!!!!
199
+ arrival: TimeDict | str # WTF KOLEO!!!!
200
+ departure: TimeDict | str # WTF KOLEO!!!!
201
201
  stops: list[ConnectionTrainStop]
202
202
  bookable: bool
203
203
  train_attribute_ids: list[int]
@@ -225,8 +225,8 @@ class ConnectionDetail(t.TypedDict):
225
225
  brand_ids: list[int]
226
226
  start_station_id: int
227
227
  end_station_id: int
228
- arrival: TimeDict | str # WTF KOLEO!!!!
229
- departure: TimeDict | str # WTF KOLEO!!!!
228
+ arrival: TimeDict | str # WTF KOLEO!!!!
229
+ departure: TimeDict | str # WTF KOLEO!!!!
230
230
  bookable: bool
231
231
  special_event_slug: str | None
232
232
  is_advanced_travel_options: bool
@@ -234,4 +234,4 @@ class ConnectionDetail(t.TypedDict):
234
234
  max_passengers_count: bool
235
235
  constriction_info: list[str]
236
236
  is_estimated_timetable_available: bool
237
- trains: list[ConnectiontrainDetail]
237
+ trains: list[ConnectiontrainDetail]
@@ -1,3 +1,4 @@
1
+ from argparse import Action
1
2
  from datetime import datetime, time, timedelta
2
3
 
3
4
  from .types import TimeDict
@@ -64,9 +65,14 @@ NUMERAL_TO_ARABIC = {
64
65
  "X": 10,
65
66
  "XI": 11, # wtf poznań???
66
67
  "XII": 12, # just to be safe
67
- "BUS": "BUS"
68
+ "BUS": "BUS",
68
69
  }
69
70
 
70
71
 
71
72
  def convert_platform_number(number: str) -> int | None | str:
72
73
  return NUMERAL_TO_ARABIC.get(number)
74
+
75
+
76
+ class RemainderString(Action):
77
+ def __call__(self, _, namespace, values: list[str], __):
78
+ setattr(namespace, self.dest, " ".join(values) if values else self.default)
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.1
2
+ Name: koleo-cli
3
+ Version: 0.2.137.6
4
+ Summary: Koleo CLI
5
+ Home-page: https://github.com/lzgirlcat/koleo-cli
6
+ Author: Zoey !
7
+ Maintainer-email: cb98uzhd@duck.com
8
+ License: GNU General Public License v3.0
9
+ Project-URL: Source (GitHub), https://github.com/lzgirlcat/koleo-cli
10
+ Project-URL: Issue Tracker, https://github.com/lzgirlcat/koleo-cli/issues
11
+ Keywords: koleo,timetable,trains,rail,poland
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.12
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: rich==13.7.1
17
+ Requires-Dist: requests==2.31.0
18
+
19
+ [![PyPI - Version](https://img.shields.io/pypi/v/koleo-cli.svg)](https://pypi.org/project/koleo-cli)
20
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/koleo-cli.svg)](https://pypi.org/project/koleo-cli)
21
+
22
+ # Koleo CLI
23
+
24
+ ![gif showcasing the functionality](https://github.com/lzgirlcat/koleo-cli/blob/main/koleo-cli.gif?raw=true)
25
+
26
+
27
+ install via pip by running
28
+
29
+ `pip install koleo-cli`
30
+
31
+ ## it currently allows you to:
32
+ - get departures/arrival list for a station
33
+ - get train info given its number and name(pull requests are welcome if you know how to get a train object by just the number)
34
+ - find a station or list all known stations
35
+ - find a connection from station a to b, with filtering by operators
36
+ - save a station as your favourite to quickly check it's departures
37
+
38
+ additionally you can also use the KoleoAPI wrapper directly in your own projects, all returns are fully typed using `typing.TypedDict`
39
+
40
+ ## MY(possibly controversial) design choices:
41
+ - platforms and track numbers are shown using arabic numerals instead of roman
42
+ - you can change it by adding `use_roman_numerals: true` to your config.json file
43
+ - most api queries are cached for 24h
44
+ - you can change it by adding `disable_cache: true` to your config.json file
45
+ - the cli.py code is really dirty but printing formatted data is hard :<
46
+
47
+ pull requests are welcome!!
48
+
49
+ ```
50
+ usage: koleo [-h] [-c CONFIG] [--nocolor] {departures,d,dep,odjazdy,o,arrivals,a,arr,przyjazdy,p,trainroute,r,tr,t,poc,pociąg,stations,s,find,f,stacje,ls,connections,do,z,szukaj,path} ...
51
+
52
+ Koleo CLI
53
+
54
+ options:
55
+ -h, --help show this help message and exit
56
+ -c CONFIG, --config CONFIG
57
+ Custom config path.
58
+ --nocolor Disable color output and formatting
59
+
60
+ actions:
61
+ {departures,d,dep,odjazdy,o,arrivals,a,arr,przyjazdy,p,trainroute,r,tr,t,poc,pociąg,stations,s,find,f,stacje,ls,connections,do,z,szukaj,path}
62
+ departures (d, dep, odjazdy, o)
63
+ Allows you to list station departures
64
+ arrivals (a, arr, przyjazdy, p)
65
+ Allows you to list station departures
66
+ trainroute (r, tr, t, poc, pociąg)
67
+ Allows you to show the train's route
68
+ stations (s, find, f, stacje, ls)
69
+ Allows you to find stations by their name
70
+ connections (do, z, szukaj, path)
71
+ Allows you to search for connections from a to b
72
+ ```
@@ -14,7 +14,7 @@ def parse_requirements_file(path):
14
14
 
15
15
  setuptools.setup(
16
16
  name="koleo-cli",
17
- version="0.2.137.4",
17
+ version="0.2.137.6",
18
18
  description="Koleo CLI",
19
19
  long_description=long_description(),
20
20
  long_description_content_type="text/markdown",
@@ -1,17 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: koleo-cli
3
- Version: 0.2.137.4
4
- Summary: Koleo CLI
5
- Home-page: https://github.com/lzgirlcat/koleo-cli
6
- Author: Zoey !
7
- Maintainer-email: cb98uzhd@duck.com
8
- License: GNU General Public License v3.0
9
- Project-URL: Source (GitHub), https://github.com/lzgirlcat/koleo-cli
10
- Project-URL: Issue Tracker, https://github.com/lzgirlcat/koleo-cli/issues
11
- Keywords: koleo,timetable,trains,rail,poland
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Operating System :: OS Independent
14
- Requires-Python: >=3.12
15
- Description-Content-Type: text/markdown
16
- Requires-Dist: rich==13.7.1
17
- Requires-Dist: requests==2.31.0
File without changes
@@ -1,17 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: koleo-cli
3
- Version: 0.2.137.4
4
- Summary: Koleo CLI
5
- Home-page: https://github.com/lzgirlcat/koleo-cli
6
- Author: Zoey !
7
- Maintainer-email: cb98uzhd@duck.com
8
- License: GNU General Public License v3.0
9
- Project-URL: Source (GitHub), https://github.com/lzgirlcat/koleo-cli
10
- Project-URL: Issue Tracker, https://github.com/lzgirlcat/koleo-cli/issues
11
- Keywords: koleo,timetable,trains,rail,poland
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Operating System :: OS Independent
14
- Requires-Python: >=3.12
15
- Description-Content-Type: text/markdown
16
- Requires-Dist: rich==13.7.1
17
- Requires-Dist: requests==2.31.0
File without changes