koleo-cli 0.2.137__tar.gz → 0.2.137.2__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.
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/PKG-INFO +1 -1
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/koleo/__init__.py +1 -3
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/koleo/api.py +48 -36
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/koleo/cli.py +118 -44
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/koleo/storage.py +9 -9
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/koleo/types.py +7 -0
- koleo_cli-0.2.137.2/koleo/utils.py +65 -0
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/koleo_cli.egg-info/PKG-INFO +1 -1
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/setup.py +1 -1
- koleo_cli-0.2.137/koleo/utils.py +0 -41
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/README.md +0 -0
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/koleo/__main__.py +0 -0
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/koleo_cli.egg-info/SOURCES.txt +0 -0
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/koleo_cli.egg-info/dependency_links.txt +0 -0
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/koleo_cli.egg-info/entry_points.txt +0 -0
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/koleo_cli.egg-info/requires.txt +0 -0
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/koleo_cli.egg-info/top_level.txt +0 -0
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/pyproject.toml +0 -0
- {koleo_cli-0.2.137 → koleo_cli-0.2.137.2}/setup.cfg +0 -0
|
@@ -1,53 +1,59 @@
|
|
|
1
1
|
import typing as t
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
|
|
4
|
-
from requests import
|
|
4
|
+
from requests import PreparedRequest, Response, Session
|
|
5
5
|
|
|
6
6
|
from koleo.types import *
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class KoleoAPIException(Exception):
|
|
10
|
-
status: int
|
|
11
|
-
request: PreparedRequest
|
|
12
|
-
response: Response
|
|
13
9
|
|
|
14
|
-
def __init__(self, response: Response, *args: object) -> None:
|
|
15
|
-
super().__init__(*args)
|
|
16
|
-
self.status = response.status_code
|
|
17
|
-
self.request = response.request
|
|
18
|
-
self.response = response
|
|
19
10
|
|
|
20
|
-
@classmethod
|
|
21
|
-
def from_response(cls, response: Response) -> "KoleoAPIException":
|
|
22
|
-
if response.status_code == 404:
|
|
23
|
-
return KoleoNotFound(response)
|
|
24
|
-
elif response.status_code == 401:
|
|
25
|
-
return KoleoUnauthorized(response)
|
|
26
|
-
elif response.status_code == 403:
|
|
27
|
-
return KoleoForbidden(response)
|
|
28
|
-
elif response.status_code == 429:
|
|
29
|
-
return KoleoRatelimited(response)
|
|
30
|
-
else:
|
|
31
|
-
return KoleoAPIException(response)
|
|
32
11
|
|
|
33
12
|
|
|
34
|
-
class KoleoNotFound(KoleoAPIException):
|
|
35
|
-
pass
|
|
36
13
|
|
|
14
|
+
class errors:
|
|
15
|
+
class KoleoAPIException(Exception):
|
|
16
|
+
status: int
|
|
17
|
+
request: PreparedRequest
|
|
18
|
+
response: Response
|
|
37
19
|
|
|
38
|
-
|
|
39
|
-
|
|
20
|
+
def __init__(self, response: Response, *args: object) -> None:
|
|
21
|
+
super().__init__(*args)
|
|
22
|
+
self.status = response.status_code
|
|
23
|
+
self.request = response.request
|
|
24
|
+
self.response = response
|
|
40
25
|
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_response(cls, response: Response) -> "t.Self":
|
|
28
|
+
if response.status_code == 404:
|
|
29
|
+
return errors.KoleoNotFound(response)
|
|
30
|
+
elif response.status_code == 401:
|
|
31
|
+
return errors.KoleoUnauthorized(response)
|
|
32
|
+
elif response.status_code == 403:
|
|
33
|
+
return errors.KoleoForbidden(response)
|
|
34
|
+
elif response.status_code == 429:
|
|
35
|
+
return errors.KoleoRatelimited(response)
|
|
36
|
+
else:
|
|
37
|
+
return cls(response)
|
|
41
38
|
|
|
42
|
-
class
|
|
43
|
-
|
|
39
|
+
class KoleoNotFound(KoleoAPIException):
|
|
40
|
+
pass
|
|
44
41
|
|
|
42
|
+
class KoleoForbidden(KoleoAPIException):
|
|
43
|
+
pass
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
|
|
46
|
+
class KoleoUnauthorized(KoleoAPIException):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class KoleoRatelimited(KoleoAPIException):
|
|
51
|
+
pass
|
|
48
52
|
|
|
49
53
|
|
|
50
54
|
class KoleoAPI:
|
|
55
|
+
errors = errors
|
|
56
|
+
|
|
51
57
|
def __init__(self) -> None:
|
|
52
58
|
self.session = Session()
|
|
53
59
|
self.base_url = "https://koleo.pl"
|
|
@@ -61,17 +67,23 @@ class KoleoAPI:
|
|
|
61
67
|
headers = {**self.base_headers, **kwargs.get("headers", {})}
|
|
62
68
|
r = self.session.get(self.base_url + path, *args, headers=headers, **kwargs)
|
|
63
69
|
if not r.ok:
|
|
64
|
-
raise KoleoAPIException.from_response(r)
|
|
70
|
+
raise errors.KoleoAPIException.from_response(r)
|
|
65
71
|
return r
|
|
66
72
|
|
|
67
73
|
def _get_json(self, path, *args, **kwargs) -> t.Any:
|
|
68
74
|
r = self._get(path, *args, **kwargs)
|
|
69
|
-
|
|
75
|
+
res = r.json()
|
|
76
|
+
if res is None:
|
|
77
|
+
raise self.errors.KoleoNotFound(r)
|
|
78
|
+
return res
|
|
70
79
|
|
|
71
80
|
def _get_bytes(self, path, *args, **kwargs) -> bytes:
|
|
72
81
|
r = self._get(path, *args, **kwargs)
|
|
73
82
|
return r.content
|
|
74
83
|
|
|
84
|
+
def get_stations(self) -> list[ExtendedStationInfo]:
|
|
85
|
+
return self._get_json("/api/v2/main/stations")
|
|
86
|
+
|
|
75
87
|
def find_station(self, query: str, language: str = "pl") -> list[ExtendedStationInfo]:
|
|
76
88
|
# https://koleo.pl/ls?q=tere&language=pl
|
|
77
89
|
return self._get_json("/ls", query={"q": query, "language": language})
|
|
@@ -106,11 +118,11 @@ class KoleoAPI:
|
|
|
106
118
|
params = {"brand": brand_name, "nr": number}
|
|
107
119
|
if name:
|
|
108
120
|
params["name"] = name.upper() # WHY!!!!!!!!!
|
|
109
|
-
return self._get_json("/
|
|
121
|
+
return self._get_json("/pl/train_calendars", params=params)
|
|
110
122
|
|
|
111
123
|
def get_train(self, id: int) -> TrainDetailResponse:
|
|
112
124
|
# https://koleo.pl/pl/trains/142821312
|
|
113
|
-
return self._get_json(f"/
|
|
125
|
+
return self._get_json(f"/pl/trains/{id}")
|
|
114
126
|
|
|
115
127
|
def get_connections(
|
|
116
128
|
self,
|
|
@@ -119,7 +131,7 @@ class KoleoAPI:
|
|
|
119
131
|
brand_ids: list[int],
|
|
120
132
|
date: datetime,
|
|
121
133
|
direct: bool = False,
|
|
122
|
-
purchasable: bool = False
|
|
134
|
+
purchasable: bool = False,
|
|
123
135
|
) -> ...:
|
|
124
136
|
params = {
|
|
125
137
|
"query[date]": date.strftime("%d-%m-%Y %H:%M:%S"),
|
|
@@ -127,7 +139,7 @@ class KoleoAPI:
|
|
|
127
139
|
"query[end_station]": end,
|
|
128
140
|
"query[only_purchasable]": purchasable,
|
|
129
141
|
"query[direct]": direct,
|
|
130
|
-
"query[brand_ids]": brand_ids
|
|
142
|
+
"query[brand_ids][]": brand_ids,
|
|
131
143
|
}
|
|
132
144
|
return self._get_json("/api/v2/main/connections", params=params)
|
|
133
145
|
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
1
|
from argparse import ArgumentParser
|
|
2
|
+
from datetime import datetime, timedelta
|
|
3
3
|
|
|
4
4
|
from rich.console import Console
|
|
5
5
|
from rich.traceback import install
|
|
6
6
|
|
|
7
7
|
from .api import KoleoAPI
|
|
8
|
-
from .
|
|
9
|
-
from .
|
|
10
|
-
from .
|
|
8
|
+
from .storage import DEFAULT_CONFIG_PATH, Storage
|
|
9
|
+
from .types import TrainDetailResponse, TrainOnStationInfo, ExtendedBaseStationInfo
|
|
10
|
+
from .utils import convert_platform_number, name_to_slug, parse_datetime, arr_dep_to_dt
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
install(show_locals=False, max_frames=2)
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class CLI:
|
|
@@ -43,10 +44,15 @@ class CLI:
|
|
|
43
44
|
def storage(self, storage: Storage):
|
|
44
45
|
self._storage = storage
|
|
45
46
|
|
|
46
|
-
def list_stations(self, name: str):
|
|
47
|
+
def list_stations(self, name: str):
|
|
48
|
+
...
|
|
47
49
|
|
|
48
50
|
def get_departures(self, station_id: int, date: datetime):
|
|
49
|
-
|
|
51
|
+
cache_id = f"dep-{station_id}-{date.strftime("%Y-%m-%d")}"
|
|
52
|
+
trains = (
|
|
53
|
+
self.storage.get_cache(cache_id) or
|
|
54
|
+
self.storage.set_cache(cache_id, self.client.get_departures(station_id, date))
|
|
55
|
+
)
|
|
50
56
|
trains = [
|
|
51
57
|
i
|
|
52
58
|
for i in trains
|
|
@@ -57,7 +63,11 @@ class CLI:
|
|
|
57
63
|
return table
|
|
58
64
|
|
|
59
65
|
def get_arrivals(self, station_id: int, date: datetime):
|
|
60
|
-
|
|
66
|
+
cache_id = f"arr-{station_id}-{date.strftime("%Y-%m-%d")}"
|
|
67
|
+
trains = (
|
|
68
|
+
self.storage.get_cache(cache_id) or
|
|
69
|
+
self.storage.set_cache(cache_id, self.client.get_arrivals(station_id, date))
|
|
70
|
+
)
|
|
61
71
|
trains = [
|
|
62
72
|
i
|
|
63
73
|
for i in trains
|
|
@@ -68,43 +78,49 @@ class CLI:
|
|
|
68
78
|
return table
|
|
69
79
|
|
|
70
80
|
def full_departures(self, station: str, date: datetime):
|
|
71
|
-
|
|
72
|
-
st = self.storage.get_cache(f"st-{slug}") or self.storage.set_cache(
|
|
73
|
-
f"st-{slug}", self.client.get_station_by_slug(slug)
|
|
74
|
-
)
|
|
81
|
+
st = self.get_station(station)
|
|
75
82
|
station_info = f"[bold blue]{st["name"]}[/bold blue] ID: {st["id"]}"
|
|
76
83
|
self.console.print(station_info)
|
|
77
84
|
self.get_departures(st["id"], date)
|
|
78
85
|
|
|
79
86
|
def full_arrivals(self, station: str, date: datetime):
|
|
80
|
-
|
|
81
|
-
st = self.storage.get_cache(f"st-{slug}") or self.storage.set_cache(
|
|
82
|
-
f"st-{slug}", self.client.get_station_by_slug(slug)
|
|
83
|
-
)
|
|
87
|
+
st = self.get_station(station)
|
|
84
88
|
station_info = f"[bold blue]{st["name"]}[/bold blue] ID: {st["id"]}"
|
|
85
89
|
self.console.print(station_info)
|
|
86
90
|
self.get_arrivals(st["id"], date)
|
|
87
91
|
|
|
88
92
|
def train_info(self, brand: str, name: str, date: datetime):
|
|
89
93
|
brand = brand.upper().strip()
|
|
90
|
-
name = name.
|
|
91
|
-
cache_id = f"tc-{brand}-{name}"
|
|
94
|
+
name = name.strip()
|
|
92
95
|
if name.isnumeric():
|
|
93
96
|
number = int(name)
|
|
94
|
-
|
|
95
|
-
elif len(parts := name.split(" ")) == 2 or len(parts := name.split("-")) == 2:
|
|
96
|
-
number,
|
|
97
|
+
train_name = ""
|
|
98
|
+
elif len((parts := name.split(" "))) == 2 or len((parts := name.split("-"))) == 2:
|
|
99
|
+
number, train_name = parts
|
|
97
100
|
number = int(number)
|
|
98
101
|
else:
|
|
99
102
|
raise ValueError("Invalid train name!")
|
|
103
|
+
brands = self.storage.get_cache("brands") or self.storage.set_cache("brands", self.client.get_brands())
|
|
104
|
+
if brand not in [i["name"] for i in brands]:
|
|
105
|
+
res = {i["logo_text"]: i["name"] for i in brands}.get(brand)
|
|
106
|
+
if not res:
|
|
107
|
+
raise ValueError("Invalid brand name!")
|
|
108
|
+
brand = res
|
|
109
|
+
cache_id = f"tc-{brand}-{number}-{name}"
|
|
100
110
|
train_calendars = self.storage.get_cache(cache_id) or self.storage.set_cache(
|
|
101
|
-
cache_id, self.client.get_train_calendars(brand, number,
|
|
111
|
+
cache_id, self.client.get_train_calendars(brand, number, train_name)
|
|
102
112
|
)
|
|
103
|
-
brands = self.storage.get_cache("brands") or self.storage.set_cache("brands", self.client.get_brands())
|
|
104
113
|
train_id = train_calendars["train_calendars"][0]["date_train_map"][date.strftime("%Y-%m-%d")]
|
|
105
114
|
train_details = self.client.get_train(train_id)
|
|
106
|
-
brand = next(iter(i for i in brands if i["id"] == train_details["train"]["brand_id"]), {}).get("
|
|
107
|
-
|
|
115
|
+
brand = next(iter(i for i in brands if i["id"] == train_details["train"]["brand_id"]), {}).get("logo_text", "")
|
|
116
|
+
parts = [f"{brand} {train_details["train"]["train_full_name"]}"]
|
|
117
|
+
route_start = arr_dep_to_dt(train_details["stops"][0]["departure"])
|
|
118
|
+
route_end = arr_dep_to_dt(train_details["stops"][-1]["arrival"])
|
|
119
|
+
if route_end.hour < route_start.hour or (route_end.hour==route_start.hour and route_end.minute < route_end.minute):
|
|
120
|
+
route_end += timedelta(days=1)
|
|
121
|
+
travel_time = route_end - route_start
|
|
122
|
+
speed = train_details["stops"][-1]["distance"] / 1000 / travel_time.seconds * 3600
|
|
123
|
+
parts.append(f"[white] {travel_time.seconds//3600}h{(travel_time.seconds % 3600)/60:.0f}m {speed:^4.1f}km/h [/white]")
|
|
108
124
|
vehicle_types: dict[str, str] = {
|
|
109
125
|
stop["station_display_name"]: stop["vehicle_type"]
|
|
110
126
|
for stop in train_details["stops"]
|
|
@@ -115,20 +131,32 @@ class CLI:
|
|
|
115
131
|
start = keys[0]
|
|
116
132
|
for i in range(1, len(keys)):
|
|
117
133
|
if vehicle_types[keys[i]] != vehicle_types[start]:
|
|
118
|
-
|
|
134
|
+
parts.append(f"[bold green] {start} - {keys[i]}:[/bold green] {vehicle_types[start]}")
|
|
119
135
|
start = keys[i]
|
|
120
|
-
|
|
121
|
-
self.console.print(
|
|
136
|
+
parts.append(f"[bold green] {start} - {keys[-1]}:[/bold green] {vehicle_types[start]}")
|
|
137
|
+
self.console.print("\n".join(parts))
|
|
122
138
|
self.console.print(self.train_route_table(train_details))
|
|
123
139
|
|
|
124
|
-
def
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
def connections(self, start: str, end: str, date: datetime, brands: list[str], direct: bool = False, purchasable: bool = False):
|
|
141
|
+
start_station = self.get_station(start)
|
|
142
|
+
end_station = self.get_station(end)
|
|
143
|
+
brands = [i.lower().strip() for i in brands]
|
|
144
|
+
api_brands = self.storage.get_cache("brands") or self.storage.set_cache("brands", self.client.get_brands())
|
|
145
|
+
if not brands:
|
|
146
|
+
connection_brands = [i["id"] for i in api_brands]
|
|
147
|
+
else:
|
|
148
|
+
connection_brands = [i["id"] for i in api_brands if i["name"].lower().strip() in brands or i["logo_text"].lower().strip() in brands]
|
|
149
|
+
if not connection_brands:
|
|
150
|
+
self.console.print(f'[bold red]No brands match: "{', '.join(brands)}"[/bold red]')
|
|
151
|
+
exit(2)
|
|
152
|
+
connections = self.client.get_connections(
|
|
153
|
+
start_station["name_slug"],
|
|
154
|
+
end_station["name_slug"],
|
|
155
|
+
connection_brands,
|
|
156
|
+
date,
|
|
157
|
+
direct,
|
|
158
|
+
purchasable
|
|
128
159
|
)
|
|
129
|
-
station_info = f"[bold blue]{st["name"]}[/bold blue] ID: {st["id"]}"
|
|
130
|
-
self.console.print(station_info)
|
|
131
|
-
self.get_arrivals(st["id"], date)
|
|
132
160
|
|
|
133
161
|
def trains_on_station_table(self, trains: list[TrainOnStationInfo], type: int = 1):
|
|
134
162
|
parts = []
|
|
@@ -136,22 +164,34 @@ class CLI:
|
|
|
136
164
|
for train in trains:
|
|
137
165
|
time = train["departure"] if type == 1 else train["arrival"]
|
|
138
166
|
assert time
|
|
139
|
-
brand = next(iter(i for i in brands if i["id"] == train["brand_id"]), {}).get("
|
|
167
|
+
brand = next(iter(i for i in brands if i["id"] == train["brand_id"]), {}).get("logo_text")
|
|
168
|
+
platform = convert_platform_number(train["platform"]) if train["platform"] else ""
|
|
169
|
+
position_info = f"{platform}/{train["track"]}" if train["track"] else platform
|
|
140
170
|
parts.append(
|
|
141
|
-
f"[bold green]{time[11:16]}[/bold green] {brand} {train["train_full_name"]}[purple] {train["stations"][0]["name"]} [/purple]"
|
|
171
|
+
f"[bold green]{time[11:16]}[/bold green] {brand} {train["train_full_name"]}[purple] {train["stations"][0]["name"]} {position_info}[/purple]"
|
|
142
172
|
)
|
|
143
173
|
return "\n".join(parts)
|
|
144
174
|
|
|
145
175
|
def train_route_table(self, train: TrainDetailResponse):
|
|
146
176
|
parts = []
|
|
147
177
|
for stop in train["stops"]:
|
|
148
|
-
arr =
|
|
149
|
-
dep =
|
|
178
|
+
arr = arr_dep_to_dt(stop["arrival"])
|
|
179
|
+
dep = arr_dep_to_dt(stop["departure"])
|
|
180
|
+
platform = convert_platform_number(stop["platform"]) or ""
|
|
150
181
|
parts.append(
|
|
151
|
-
f"[white underline]{stop["distance"] / 1000
|
|
182
|
+
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]"
|
|
152
183
|
)
|
|
153
184
|
return "\n".join(parts)
|
|
154
185
|
|
|
186
|
+
def get_station(self, station: str) -> ExtendedBaseStationInfo:
|
|
187
|
+
slug = name_to_slug(station)
|
|
188
|
+
try:
|
|
189
|
+
return self.storage.get_cache(f"st-{slug}") or self.storage.set_cache(
|
|
190
|
+
f"st-{slug}", self.client.get_station_by_slug(slug)
|
|
191
|
+
)
|
|
192
|
+
except self.client.errors.KoleoNotFound:
|
|
193
|
+
self.console.print(f'[bold red]Station not found: "{station}"[/bold red]')
|
|
194
|
+
exit(2)
|
|
155
195
|
|
|
156
196
|
def main():
|
|
157
197
|
cli = CLI()
|
|
@@ -159,9 +199,9 @@ def main():
|
|
|
159
199
|
parser = ArgumentParser("koleo", description="Koleo CLI")
|
|
160
200
|
parser.add_argument("-c", "--config", help="Custom config path.", default=DEFAULT_CONFIG_PATH)
|
|
161
201
|
parser.add_argument("--nocolor", help="Disable color output", action="store_true", default=False)
|
|
162
|
-
subparsers = parser.add_subparsers(title="actions", required=False)
|
|
202
|
+
subparsers = parser.add_subparsers(title="actions", required=False) # type: ignore
|
|
163
203
|
|
|
164
|
-
departures = subparsers.add_parser("departures", aliases=["d", "dep"], help="Allows you to list station departures")
|
|
204
|
+
departures = subparsers.add_parser("departures", aliases=["d", "dep", "odjazdy", "o"], help="Allows you to list station departures")
|
|
165
205
|
departures.add_argument(
|
|
166
206
|
"station",
|
|
167
207
|
help="The station name",
|
|
@@ -178,7 +218,7 @@ def main():
|
|
|
178
218
|
departures.add_argument("-s", "--save", help="save the station as your default one", action="store_true")
|
|
179
219
|
departures.set_defaults(func=cli.full_departures, pass_=["station", "date"])
|
|
180
220
|
|
|
181
|
-
arrivals = subparsers.add_parser("arrivals", aliases=["a", "arr"], help="Allows you to list station departures")
|
|
221
|
+
arrivals = subparsers.add_parser("arrivals", aliases=["a", "arr", "przyjazdy", "p"], help="Allows you to list station departures")
|
|
182
222
|
arrivals.add_argument(
|
|
183
223
|
"station",
|
|
184
224
|
help="The station name",
|
|
@@ -197,7 +237,7 @@ def main():
|
|
|
197
237
|
|
|
198
238
|
train_route = subparsers.add_parser(
|
|
199
239
|
"trainroute",
|
|
200
|
-
aliases=["r", "tr", "t"],
|
|
240
|
+
aliases=["r", "tr", "t", "poc", "pociąg"],
|
|
201
241
|
help="Allows you to show the train's route",
|
|
202
242
|
)
|
|
203
243
|
train_route.add_argument("brand", help="The brand name", type=str)
|
|
@@ -211,6 +251,40 @@ def main():
|
|
|
211
251
|
)
|
|
212
252
|
train_route.set_defaults(func=cli.train_info, pass_=["brand", "name", "date"])
|
|
213
253
|
|
|
254
|
+
connections = subparsers.add_parser(
|
|
255
|
+
"connections",
|
|
256
|
+
aliases=["do", "z", "szukaj", "path", "find"],
|
|
257
|
+
help="Allows you to search for connections from a to b",
|
|
258
|
+
)
|
|
259
|
+
connections.add_argument("start", help="The starting station", type=str)
|
|
260
|
+
connections.add_argument("end", help="The end station", type=str)
|
|
261
|
+
connections.add_argument(
|
|
262
|
+
"-d",
|
|
263
|
+
"--date",
|
|
264
|
+
help="the date",
|
|
265
|
+
type=lambda s: parse_datetime(s),
|
|
266
|
+
default=datetime.now(),
|
|
267
|
+
)
|
|
268
|
+
connections.add_argument(
|
|
269
|
+
"-b",
|
|
270
|
+
"--brands",
|
|
271
|
+
help="Brands to include",
|
|
272
|
+
action="extend", nargs="+", type=str, default=[]
|
|
273
|
+
)
|
|
274
|
+
connections.add_argument(
|
|
275
|
+
"-f",
|
|
276
|
+
"--direct",
|
|
277
|
+
help="whether or not the result should only include direct trains",
|
|
278
|
+
action="store_true", default=False
|
|
279
|
+
)
|
|
280
|
+
connections.add_argument(
|
|
281
|
+
"-p",
|
|
282
|
+
"--purchasable",
|
|
283
|
+
help="whether or not the result should only trains purchasable on koleo",
|
|
284
|
+
action="store_true", default=False
|
|
285
|
+
)
|
|
286
|
+
connections.set_defaults(func=cli.connections, pass_=["start", "end", "brands", "date", "direct" "purchasable"])
|
|
287
|
+
|
|
214
288
|
args = parser.parse_args()
|
|
215
289
|
|
|
216
290
|
storage = Storage.load(path=args.config)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import typing as t
|
|
2
|
-
from
|
|
3
|
-
from dataclasses import dataclass, asdict, field
|
|
4
|
-
from sys import platform
|
|
2
|
+
from dataclasses import asdict, dataclass, field
|
|
5
3
|
from json import dump, load
|
|
6
|
-
from os import makedirs
|
|
4
|
+
from os import makedirs
|
|
5
|
+
from os import path as ospath
|
|
6
|
+
from sys import platform
|
|
7
|
+
from time import time
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
def get_adequate_config_path():
|
|
@@ -42,7 +43,7 @@ class Storage:
|
|
|
42
43
|
else:
|
|
43
44
|
data = {}
|
|
44
45
|
storage = cls(**data)
|
|
45
|
-
storage._path =
|
|
46
|
+
storage._path = expanded
|
|
46
47
|
return storage
|
|
47
48
|
|
|
48
49
|
def get_cache(self, id: str) -> t.Any | None:
|
|
@@ -62,10 +63,9 @@ class Storage:
|
|
|
62
63
|
return item
|
|
63
64
|
|
|
64
65
|
def save(self):
|
|
65
|
-
|
|
66
|
-
dir = ospath.dirname(expanded)
|
|
66
|
+
dir = ospath.dirname(self._path)
|
|
67
67
|
if dir:
|
|
68
68
|
if not ospath.exists(dir):
|
|
69
69
|
makedirs(dir)
|
|
70
|
-
with open(
|
|
71
|
-
dump(asdict(self), f)
|
|
70
|
+
with open(self._path, "w+") as f:
|
|
71
|
+
dump(asdict(self), f, indent=True)
|
|
@@ -12,6 +12,7 @@ class ExtendedBaseStationInfo(BaseStationInfo):
|
|
|
12
12
|
hits: int
|
|
13
13
|
version: str # "A", "B"
|
|
14
14
|
is_group: bool
|
|
15
|
+
city: str | None
|
|
15
16
|
region: str
|
|
16
17
|
country: str
|
|
17
18
|
latitude: float
|
|
@@ -175,3 +176,9 @@ class TrainStop(t.TypedDict):
|
|
|
175
176
|
class TrainDetailResponse(t.TypedDict):
|
|
176
177
|
train: TrainDetail
|
|
177
178
|
stops: list[TrainStop]
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class ConnectiontrainDetail(TrainDetail):
|
|
182
|
+
|
|
183
|
+
arrival: TimeDict | str # WTF KOLEO!!!!
|
|
184
|
+
departure: TimeDict | str # WTF KOLEO!!!!
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from datetime import datetime, time, timedelta
|
|
2
|
+
|
|
3
|
+
from .types import TimeDict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse_datetime(s: str):
|
|
7
|
+
try:
|
|
8
|
+
now = datetime.now()
|
|
9
|
+
dt = datetime.strptime(s, "%d-%m")
|
|
10
|
+
return dt.replace(year=now.year, hour=0, minute=0)
|
|
11
|
+
except ValueError:
|
|
12
|
+
pass
|
|
13
|
+
try:
|
|
14
|
+
return datetime.strptime(s, "%Y-%m-%d").replace(hour=0, minute=0)
|
|
15
|
+
except ValueError:
|
|
16
|
+
pass
|
|
17
|
+
if s[0] == "+":
|
|
18
|
+
return datetime.now().replace(hour=0, minute=0) + timedelta(days=int(s[1:]))
|
|
19
|
+
return datetime.combine(datetime.now(), datetime.strptime(s, "%H:%M").time())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def arr_dep_to_dt(i: TimeDict | str):
|
|
23
|
+
if isinstance(i, str):
|
|
24
|
+
return datetime.fromisoformat(i)
|
|
25
|
+
now = datetime.today()
|
|
26
|
+
return datetime.combine(now, time(i["hour"], i["minute"], i["second"]))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
TRANSLITERATIONS = {
|
|
30
|
+
"ł": "l",
|
|
31
|
+
"ń": "n",
|
|
32
|
+
"ą": "a",
|
|
33
|
+
"ę": "e",
|
|
34
|
+
"ś": "s",
|
|
35
|
+
"ć": "c",
|
|
36
|
+
"ó": "o",
|
|
37
|
+
"ź": "z",
|
|
38
|
+
"ż": "z",
|
|
39
|
+
" ": "-",
|
|
40
|
+
"_": "-",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def name_to_slug(name: str) -> str:
|
|
45
|
+
return "".join([TRANSLITERATIONS.get(char, char) for char in name.lower()])
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
NUMERAL_TO_ARABIC = {
|
|
49
|
+
"I": 1,
|
|
50
|
+
"II": 2,
|
|
51
|
+
"III": 3,
|
|
52
|
+
"IV": 4,
|
|
53
|
+
"V": 5,
|
|
54
|
+
"VI": 6,
|
|
55
|
+
"VII": 7,
|
|
56
|
+
"VIII": 8,
|
|
57
|
+
"IX": 9,
|
|
58
|
+
"X": 10,
|
|
59
|
+
"XI": 11, # wtf poznań???
|
|
60
|
+
"XII": 12, # just to be safe
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def convert_platform_number(number: str) -> int | None:
|
|
65
|
+
return NUMERAL_TO_ARABIC.get(number)
|
koleo_cli-0.2.137/koleo/utils.py
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from datetime import datetime, time
|
|
2
|
-
|
|
3
|
-
from .types import TimeDict
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def parse_datetime(s: str):
|
|
7
|
-
now = datetime.today()
|
|
8
|
-
try:
|
|
9
|
-
dt = datetime.strptime(s, "%d-%m")
|
|
10
|
-
return dt.replace(year=now.year, hour=0, minute=0)
|
|
11
|
-
except ValueError:
|
|
12
|
-
pass
|
|
13
|
-
try:
|
|
14
|
-
return datetime.strptime(s, "%Y-%m-%d").replace(hour=0, minute=0)
|
|
15
|
-
except ValueError:
|
|
16
|
-
pass
|
|
17
|
-
return datetime.combine(now, datetime.strptime(s, "%H:%M").time())
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def time_dict_to_dt(s: TimeDict):
|
|
21
|
-
now = datetime.today()
|
|
22
|
-
return datetime.combine(now, time(s["hour"], s["minute"], s["second"]))
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
TRANSLITERATIONS = {
|
|
26
|
-
"ł": "l",
|
|
27
|
-
"ń": "n",
|
|
28
|
-
"ą": "a",
|
|
29
|
-
"ę": "e",
|
|
30
|
-
"ś": "s",
|
|
31
|
-
"ć": "c",
|
|
32
|
-
"ó": "o",
|
|
33
|
-
"ź": "z",
|
|
34
|
-
"ż": "z",
|
|
35
|
-
" ": "-",
|
|
36
|
-
"_": "-",
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def name_to_slug(name: str) -> str:
|
|
41
|
-
return "".join([TRANSLITERATIONS.get(char, char) for char in name.lower()])
|
|
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
|