koleo-cli 0.2.137.12__tar.gz → 0.2.137.25__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.
- koleo_cli-0.2.137.25/MANIFEST.in +1 -0
- koleo_cli-0.2.137.25/PKG-INFO +103 -0
- koleo_cli-0.2.137.25/README.md +70 -0
- {koleo_cli-0.2.137.12 → koleo_cli-0.2.137.25}/koleo/__init__.py +1 -1
- {koleo_cli-0.2.137.12 → koleo_cli-0.2.137.25}/koleo/__main__.py +1 -1
- koleo_cli-0.2.137.25/koleo/api/__init__.py +2 -0
- koleo_cli-0.2.137.25/koleo/api/base.py +70 -0
- koleo_cli-0.2.137.25/koleo/api/client.py +275 -0
- koleo_cli-0.2.137.25/koleo/api/errors.py +46 -0
- koleo_cli-0.2.137.25/koleo/api/logging.py +56 -0
- koleo_cli-0.2.137.25/koleo/api/types.py +592 -0
- koleo_cli-0.2.137.25/koleo/args.py +335 -0
- koleo_cli-0.2.137.25/koleo/cli/__init__.py +8 -0
- koleo_cli-0.2.137.25/koleo/cli/aliases.py +15 -0
- koleo_cli-0.2.137.25/koleo/cli/base.py +146 -0
- koleo_cli-0.2.137.25/koleo/cli/connections.py +412 -0
- koleo_cli-0.2.137.25/koleo/cli/seats.py +195 -0
- koleo_cli-0.2.137.25/koleo/cli/station_board.py +74 -0
- koleo_cli-0.2.137.25/koleo/cli/stations.py +35 -0
- koleo_cli-0.2.137.25/koleo/cli/train_info.py +135 -0
- koleo_cli-0.2.137.25/koleo/cli/utils.py +67 -0
- koleo_cli-0.2.137.25/koleo/storage.py +150 -0
- koleo_cli-0.2.137.25/koleo/utils.py +199 -0
- koleo_cli-0.2.137.25/koleo_cli.egg-info/PKG-INFO +103 -0
- koleo_cli-0.2.137.25/koleo_cli.egg-info/SOURCES.txt +32 -0
- koleo_cli-0.2.137.25/koleo_cli.egg-info/entry_points.txt +2 -0
- koleo_cli-0.2.137.25/koleo_cli.egg-info/requires.txt +3 -0
- {koleo_cli-0.2.137.12 → koleo_cli-0.2.137.25}/pyproject.toml +2 -4
- koleo_cli-0.2.137.25/requirements.txt +3 -0
- {koleo_cli-0.2.137.12 → koleo_cli-0.2.137.25}/setup.py +2 -2
- koleo_cli-0.2.137.12/PKG-INFO +0 -71
- koleo_cli-0.2.137.12/README.md +0 -52
- koleo_cli-0.2.137.12/koleo/api.py +0 -161
- koleo_cli-0.2.137.12/koleo/cli.py +0 -520
- koleo_cli-0.2.137.12/koleo/storage.py +0 -84
- koleo_cli-0.2.137.12/koleo/types.py +0 -237
- koleo_cli-0.2.137.12/koleo/utils.py +0 -78
- koleo_cli-0.2.137.12/koleo_cli.egg-info/PKG-INFO +0 -71
- koleo_cli-0.2.137.12/koleo_cli.egg-info/SOURCES.txt +0 -17
- koleo_cli-0.2.137.12/koleo_cli.egg-info/entry_points.txt +0 -2
- koleo_cli-0.2.137.12/koleo_cli.egg-info/requires.txt +0 -2
- {koleo_cli-0.2.137.12 → koleo_cli-0.2.137.25}/LICENSE +0 -0
- {koleo_cli-0.2.137.12 → koleo_cli-0.2.137.25}/koleo_cli.egg-info/dependency_links.txt +0 -0
- {koleo_cli-0.2.137.12 → koleo_cli-0.2.137.25}/koleo_cli.egg-info/top_level.txt +0 -0
- {koleo_cli-0.2.137.12 → koleo_cli-0.2.137.25}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include requirements.txt
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: koleo-cli
|
|
3
|
+
Version: 0.2.137.25
|
|
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
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: rich~=14.0.0
|
|
18
|
+
Requires-Dist: aiohttp~=3.12.13
|
|
19
|
+
Requires-Dist: orjson~=3.10.18
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: keywords
|
|
26
|
+
Dynamic: license
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: maintainer-email
|
|
29
|
+
Dynamic: project-url
|
|
30
|
+
Dynamic: requires-dist
|
|
31
|
+
Dynamic: requires-python
|
|
32
|
+
Dynamic: summary
|
|
33
|
+
|
|
34
|
+
# Koleo CLI
|
|
35
|
+
[](https://pypi.org/project/koleo-cli)
|
|
36
|
+
[](https://pypi.org/project/koleo-cli)
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
**install via pip by running** `pip install koleo-cli`
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
## it currently allows you to:
|
|
45
|
+
- get departures/arrival list for a station
|
|
46
|
+
- 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)
|
|
47
|
+
- find a station or list all known stations
|
|
48
|
+
- find a connection from station a to b, with filtering by operators
|
|
49
|
+
- save a station as your favourite to quickly check it's departures
|
|
50
|
+
- add station aliases to query them more easily
|
|
51
|
+
- check seat allocation statistics
|
|
52
|
+
|
|
53
|
+
### coming soon™️:
|
|
54
|
+
- TUI ticket purchase interface
|
|
55
|
+
- ticket display
|
|
56
|
+
- your previous tickets + stats
|
|
57
|
+
- find empty compartments
|
|
58
|
+
additionally you can also use the KoleoAPI wrapper directly in your own projects, all returns are fully typed using `typing.TypedDict`
|
|
59
|
+
|
|
60
|
+
## MY(possibly controversial) design choices:
|
|
61
|
+
- platforms and track numbers are shown using arabic numerals instead of roman
|
|
62
|
+
- you can change it by adding `use_roman_numerals: true` to your `koleo-cli.json` config file
|
|
63
|
+
- most api queries are cached for 24h
|
|
64
|
+
- you can change it by adding `disable_cache: true` to your `koleo-cli.json` config file
|
|
65
|
+
- stations/ls uses emojis by default
|
|
66
|
+
- you can disable them by adding `use_country_flags_emoji: false` and `use_country_flags_emoji: false` to your `koleo-cli.json` config file
|
|
67
|
+
pull requests are welcome!!
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
usage: koleo [-h] [-c CONFIG] [--nocolor]
|
|
71
|
+
{departures,d,dep,odjazdy,o,arrivals,a,arr,przyjazdy,p,all,w,wszystkie,all_trains,pociagi,trainroute,r,tr,t,poc,pociąg,traincalendar,kursowanie,tc,k,traindetail,td,tid,id,idpoc,stations,s,find,f,stacje,ls,q,connections,do,z,szukaj,path,trainstats,ts,tp,miejsca,frekwencja,trainconnectionstats,tcs,aliases} ...
|
|
72
|
+
|
|
73
|
+
Koleo CLI
|
|
74
|
+
|
|
75
|
+
options:
|
|
76
|
+
-h, --help show this help message and exit
|
|
77
|
+
-c, --config CONFIG Custom config path.
|
|
78
|
+
--nocolor Disable color output and formatting
|
|
79
|
+
|
|
80
|
+
actions:
|
|
81
|
+
{departures,d,dep,odjazdy,o,arrivals,a,arr,przyjazdy,p,all,w,wszystkie,all_trains,pociagi,trainroute,r,tr,t,poc,pociąg,traincalendar,kursowanie,tc,k,traindetail,td,tid,id,idpoc,stations,s,find,f,stacje,ls,q,connections,do,z,szukaj,path,trainstats,ts,tp,miejsca,frekwencja,trainconnectionstats,tcs,aliases}
|
|
82
|
+
departures (d, dep, odjazdy, o)
|
|
83
|
+
Allows you to list station departures
|
|
84
|
+
arrivals (a, arr, przyjazdy, p)
|
|
85
|
+
Allows you to list station departures
|
|
86
|
+
all (w, wszystkie, all_trains, pociagi)
|
|
87
|
+
Allows you to list all station trains
|
|
88
|
+
trainroute (r, tr, t, poc, pociąg)
|
|
89
|
+
Allows you to check the train's route
|
|
90
|
+
traincalendar (kursowanie, tc, k)
|
|
91
|
+
Allows you to check what days the train runs on
|
|
92
|
+
traindetail (td, tid, id, idpoc)
|
|
93
|
+
Allows you to show the train's route given it's koleo ID
|
|
94
|
+
stations (s, find, f, stacje, ls, q)
|
|
95
|
+
Allows you to find stations by their name
|
|
96
|
+
connections (do, z, szukaj, path)
|
|
97
|
+
Allows you to search for connections from a to b
|
|
98
|
+
trainstats (ts, tp, miejsca, frekwencja)
|
|
99
|
+
Allows you to check seat allocation info for a train.
|
|
100
|
+
trainconnectionstats (tcs)
|
|
101
|
+
Allows you to check the seat allocations on the train connection given it's koleo ID
|
|
102
|
+
aliases Save quick aliases for station names!
|
|
103
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Koleo CLI
|
|
2
|
+
[](https://pypi.org/project/koleo-cli)
|
|
3
|
+
[](https://pypi.org/project/koleo-cli)
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
**install via pip by running** `pip install koleo-cli`
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## it currently allows you to:
|
|
12
|
+
- get departures/arrival list for a station
|
|
13
|
+
- 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)
|
|
14
|
+
- find a station or list all known stations
|
|
15
|
+
- find a connection from station a to b, with filtering by operators
|
|
16
|
+
- save a station as your favourite to quickly check it's departures
|
|
17
|
+
- add station aliases to query them more easily
|
|
18
|
+
- check seat allocation statistics
|
|
19
|
+
|
|
20
|
+
### coming soon™️:
|
|
21
|
+
- TUI ticket purchase interface
|
|
22
|
+
- ticket display
|
|
23
|
+
- your previous tickets + stats
|
|
24
|
+
- find empty compartments
|
|
25
|
+
additionally you can also use the KoleoAPI wrapper directly in your own projects, all returns are fully typed using `typing.TypedDict`
|
|
26
|
+
|
|
27
|
+
## MY(possibly controversial) design choices:
|
|
28
|
+
- platforms and track numbers are shown using arabic numerals instead of roman
|
|
29
|
+
- you can change it by adding `use_roman_numerals: true` to your `koleo-cli.json` config file
|
|
30
|
+
- most api queries are cached for 24h
|
|
31
|
+
- you can change it by adding `disable_cache: true` to your `koleo-cli.json` config file
|
|
32
|
+
- stations/ls uses emojis by default
|
|
33
|
+
- you can disable them by adding `use_country_flags_emoji: false` and `use_country_flags_emoji: false` to your `koleo-cli.json` config file
|
|
34
|
+
pull requests are welcome!!
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
usage: koleo [-h] [-c CONFIG] [--nocolor]
|
|
38
|
+
{departures,d,dep,odjazdy,o,arrivals,a,arr,przyjazdy,p,all,w,wszystkie,all_trains,pociagi,trainroute,r,tr,t,poc,pociąg,traincalendar,kursowanie,tc,k,traindetail,td,tid,id,idpoc,stations,s,find,f,stacje,ls,q,connections,do,z,szukaj,path,trainstats,ts,tp,miejsca,frekwencja,trainconnectionstats,tcs,aliases} ...
|
|
39
|
+
|
|
40
|
+
Koleo CLI
|
|
41
|
+
|
|
42
|
+
options:
|
|
43
|
+
-h, --help show this help message and exit
|
|
44
|
+
-c, --config CONFIG Custom config path.
|
|
45
|
+
--nocolor Disable color output and formatting
|
|
46
|
+
|
|
47
|
+
actions:
|
|
48
|
+
{departures,d,dep,odjazdy,o,arrivals,a,arr,przyjazdy,p,all,w,wszystkie,all_trains,pociagi,trainroute,r,tr,t,poc,pociąg,traincalendar,kursowanie,tc,k,traindetail,td,tid,id,idpoc,stations,s,find,f,stacje,ls,q,connections,do,z,szukaj,path,trainstats,ts,tp,miejsca,frekwencja,trainconnectionstats,tcs,aliases}
|
|
49
|
+
departures (d, dep, odjazdy, o)
|
|
50
|
+
Allows you to list station departures
|
|
51
|
+
arrivals (a, arr, przyjazdy, p)
|
|
52
|
+
Allows you to list station departures
|
|
53
|
+
all (w, wszystkie, all_trains, pociagi)
|
|
54
|
+
Allows you to list all station trains
|
|
55
|
+
trainroute (r, tr, t, poc, pociąg)
|
|
56
|
+
Allows you to check the train's route
|
|
57
|
+
traincalendar (kursowanie, tc, k)
|
|
58
|
+
Allows you to check what days the train runs on
|
|
59
|
+
traindetail (td, tid, id, idpoc)
|
|
60
|
+
Allows you to show the train's route given it's koleo ID
|
|
61
|
+
stations (s, find, f, stacje, ls, q)
|
|
62
|
+
Allows you to find stations by their name
|
|
63
|
+
connections (do, z, szukaj, path)
|
|
64
|
+
Allows you to search for connections from a to b
|
|
65
|
+
trainstats (ts, tp, miejsca, frekwencja)
|
|
66
|
+
Allows you to check seat allocation info for a train.
|
|
67
|
+
trainconnectionstats (tcs)
|
|
68
|
+
Allows you to check the seat allocations on the train connection given it's koleo ID
|
|
69
|
+
aliases Save quick aliases for station names!
|
|
70
|
+
```
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
from .api import KoleoAPI
|
|
2
|
-
from .types import *
|
|
2
|
+
from .api.types import *
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from asyncio import sleep as asleep
|
|
2
|
+
|
|
3
|
+
from aiohttp import (
|
|
4
|
+
ClientConnectorError,
|
|
5
|
+
ClientOSError,
|
|
6
|
+
ClientResponse,
|
|
7
|
+
ClientResponseError,
|
|
8
|
+
ClientSession,
|
|
9
|
+
)
|
|
10
|
+
from orjson import loads
|
|
11
|
+
|
|
12
|
+
from .logging import LoggingMixin
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class JsonableData(bytes):
|
|
16
|
+
response: ClientResponse
|
|
17
|
+
|
|
18
|
+
def __new__(cls, *args, response: ClientResponse, **kwargs):
|
|
19
|
+
obj = super().__new__(cls, *args, **kwargs)
|
|
20
|
+
obj.response = response
|
|
21
|
+
return obj
|
|
22
|
+
|
|
23
|
+
def json(self):
|
|
24
|
+
if "_json" not in self.__dict__:
|
|
25
|
+
self._json = loads(bytes(self))
|
|
26
|
+
return self._json
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BaseAPIClient(LoggingMixin):
|
|
30
|
+
_session: ClientSession
|
|
31
|
+
|
|
32
|
+
exc = ClientResponseError
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def session(self) -> "ClientSession":
|
|
36
|
+
if not hasattr(self, "_session"):
|
|
37
|
+
self._session = ClientSession()
|
|
38
|
+
return self._session
|
|
39
|
+
|
|
40
|
+
async def close(self):
|
|
41
|
+
return await self.session.close()
|
|
42
|
+
|
|
43
|
+
async def exc_getter(self, r: ClientResponse) -> Exception | None:
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
async def request(self, method, url: str, *args, retries: int = 4, fail_wait: float = 8, **kwargs) -> JsonableData:
|
|
47
|
+
try:
|
|
48
|
+
async with self.session.request(method, url, *args, **kwargs) as r:
|
|
49
|
+
if not r.ok:
|
|
50
|
+
self.dl(r.headers)
|
|
51
|
+
try:
|
|
52
|
+
self.dl(await r.text())
|
|
53
|
+
except UnicodeDecodeError:
|
|
54
|
+
self.dl("Response is not text!")
|
|
55
|
+
if exc := (await self.exc_getter(r)):
|
|
56
|
+
raise exc
|
|
57
|
+
r.raise_for_status()
|
|
58
|
+
return JsonableData(await r.read(), response=r)
|
|
59
|
+
except (ClientConnectorError, ClientOSError) as e:
|
|
60
|
+
if retries > 0:
|
|
61
|
+
await asleep(fail_wait)
|
|
62
|
+
return await self.request(
|
|
63
|
+
method,
|
|
64
|
+
url,
|
|
65
|
+
*args,
|
|
66
|
+
retries=retries - 1,
|
|
67
|
+
fail_wait=fail_wait,
|
|
68
|
+
**kwargs,
|
|
69
|
+
)
|
|
70
|
+
raise e
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from aiohttp import ClientResponse
|
|
5
|
+
|
|
6
|
+
from koleo.api.types import *
|
|
7
|
+
|
|
8
|
+
from .base import BaseAPIClient
|
|
9
|
+
from .errors import errors
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class KoleoAPI(BaseAPIClient):
|
|
13
|
+
errors = errors
|
|
14
|
+
|
|
15
|
+
def __init__(self, auth: dict[str, str] | None = None) -> None:
|
|
16
|
+
self.base_url = "https://api.koleo.pl"
|
|
17
|
+
self.version = 2
|
|
18
|
+
self.base_headers = {
|
|
19
|
+
"x-koleo-version": str(self.version),
|
|
20
|
+
"x-koleo-client": "Nuxt-1",
|
|
21
|
+
"User-Agent": "Koleo-CLI(https://pypi.org/project/koleo-cli)",
|
|
22
|
+
}
|
|
23
|
+
self._auth: dict[str, str] | None = auth
|
|
24
|
+
self._auth_valid: bool | None = None
|
|
25
|
+
|
|
26
|
+
async def get(self, path: str, use_auth: bool = False, *args, **kwargs):
|
|
27
|
+
headers = {**self.base_headers, **kwargs.pop("headers", {})}
|
|
28
|
+
if self._auth and use_auth:
|
|
29
|
+
headers["cookie"] = "; ".join([f"{k}={v}" for k, v in self._auth.items()])
|
|
30
|
+
r = await self.request(
|
|
31
|
+
"GET", self.base_url + path if not path.startswith("http") else path, headers=headers, *args, **kwargs
|
|
32
|
+
)
|
|
33
|
+
if len(r) == 0:
|
|
34
|
+
raise self.errors.KoleoNotFound(r.response)
|
|
35
|
+
return r
|
|
36
|
+
|
|
37
|
+
async def post(self, path, use_auth: bool = False, *args, **kwargs):
|
|
38
|
+
headers = {**self.base_headers, **kwargs.pop("headers", {})}
|
|
39
|
+
if self._auth and use_auth:
|
|
40
|
+
headers["cookie"] = ("; ".join([f"{k}={v}" for k, v in self._auth.items()]),)
|
|
41
|
+
r = await self.request(
|
|
42
|
+
"POST", self.base_url + path if not path.startswith("http") else path, headers=headers, *args, **kwargs
|
|
43
|
+
)
|
|
44
|
+
if len(r) == 0:
|
|
45
|
+
raise self.errors.KoleoNotFound(r.response)
|
|
46
|
+
return r
|
|
47
|
+
|
|
48
|
+
async def put(self, path, use_auth: bool = False, *args, **kwargs):
|
|
49
|
+
headers = {**self.base_headers, **kwargs.pop("headers", {})}
|
|
50
|
+
if self._auth and use_auth:
|
|
51
|
+
headers["cookie"] = ("; ".join([f"{k}={v}" for k, v in self._auth.items()]),)
|
|
52
|
+
r = await self.request("PUT", self.base_url + path, headers=headers, *args, **kwargs)
|
|
53
|
+
if len(r) == 0:
|
|
54
|
+
raise self.errors.KoleoNotFound(r.response)
|
|
55
|
+
return r
|
|
56
|
+
|
|
57
|
+
async def exc_getter(self, r: ClientResponse) -> Exception | None:
|
|
58
|
+
return await self.errors.from_response(r)
|
|
59
|
+
|
|
60
|
+
async def _require_auth(self) -> t.Literal[True]:
|
|
61
|
+
if self._auth is None:
|
|
62
|
+
raise errors.AuthRequired()
|
|
63
|
+
if self._auth_valid is None:
|
|
64
|
+
await self.get_current_session()
|
|
65
|
+
self._auth_valid = True
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
async def get_stations(self) -> list[ExtendedStationInfo]:
|
|
69
|
+
return (await self.get("/v2/main/stations")).json()
|
|
70
|
+
|
|
71
|
+
async def find_station(self, query: str, language: str = "pl") -> list[SearchStationInfo]:
|
|
72
|
+
# https://koleo.pl/ls?q=tere&language=pl
|
|
73
|
+
return (await self.get("/ls", params={"q": query, "language": language})).json()["stations"]
|
|
74
|
+
|
|
75
|
+
async def get_station_by_id(self, id: int) -> ExtendedStationInfo:
|
|
76
|
+
# https://koleo.pl/api/v2/main/stations/by_id/24000
|
|
77
|
+
return (
|
|
78
|
+
await self.get(
|
|
79
|
+
f"/v2/main/stations/by_id/{id}",
|
|
80
|
+
)
|
|
81
|
+
).json()
|
|
82
|
+
|
|
83
|
+
async def get_station_by_slug(self, slug: str) -> ExtendedStationInfo:
|
|
84
|
+
# https://koleo.pl/api/v2/main/stations/by_slug/inowroclaw
|
|
85
|
+
return (
|
|
86
|
+
await self.get(
|
|
87
|
+
f"/v2/main/stations/by_slug/{slug}",
|
|
88
|
+
)
|
|
89
|
+
).json()
|
|
90
|
+
|
|
91
|
+
async def get_station_info_by_slug(self, slug: str) -> StationDetails:
|
|
92
|
+
# https://koleo.pl/api/v2/main/station_info/inowroclaw
|
|
93
|
+
return (
|
|
94
|
+
await self.get(
|
|
95
|
+
f"/v2/main/station_info/{slug}",
|
|
96
|
+
)
|
|
97
|
+
).json()
|
|
98
|
+
|
|
99
|
+
async def get_departures(self, station_id: int, date: datetime) -> list[TrainOnStationInfo]:
|
|
100
|
+
# https://koleo.pl/api/v2/main/timetables/18705/2024-03-25/departures
|
|
101
|
+
return (
|
|
102
|
+
await self.get(
|
|
103
|
+
f"/v2/main/timetables/{station_id}/{date.strftime("%Y-%m-%d")}/departures",
|
|
104
|
+
)
|
|
105
|
+
).json()
|
|
106
|
+
|
|
107
|
+
async def get_arrivals(self, station_id: int, date: datetime) -> list[TrainOnStationInfo]:
|
|
108
|
+
# https://koleo.pl/api/v2/main/timetables/18705/2024-03-25/arrivals
|
|
109
|
+
return (
|
|
110
|
+
await self.get(
|
|
111
|
+
f"/v2/main/timetables/{station_id}/{date.strftime("%Y-%m-%d")}/arrivals",
|
|
112
|
+
)
|
|
113
|
+
).json()
|
|
114
|
+
|
|
115
|
+
async def get_train_calendars(self, brand_name: str, number: int, name: str | None = None) -> TrainCalendarResponse:
|
|
116
|
+
# https://koleo.pl/pl/train_calendars?brand=REG&nr=10417
|
|
117
|
+
# https://koleo.pl/pl/train_calendars?brand=IC&nr=1106&name=ESPERANTO ; WHY!!!! WHY!!!!!!1
|
|
118
|
+
params = {"brand": brand_name, "nr": number}
|
|
119
|
+
if name:
|
|
120
|
+
params["name"] = name.upper() # WHY!!!!!!!!!
|
|
121
|
+
return (await self.get("https://koleo.pl/pl/train_calendars", params=params)).json()
|
|
122
|
+
|
|
123
|
+
async def get_train(self, id: int) -> TrainDetailResponse:
|
|
124
|
+
# https://koleo.pl/pl/trains/142821312
|
|
125
|
+
return (await self.get(f"https://koleo.pl/pl/trains/{id}")).json()
|
|
126
|
+
|
|
127
|
+
async def get_connections(
|
|
128
|
+
self,
|
|
129
|
+
start: str,
|
|
130
|
+
end: str,
|
|
131
|
+
brand_ids: list[int],
|
|
132
|
+
date: datetime,
|
|
133
|
+
direct: bool = False,
|
|
134
|
+
purchasable: bool = False,
|
|
135
|
+
) -> list[ConnectionDetail]:
|
|
136
|
+
params = {
|
|
137
|
+
"query[date]": date.strftime("%d-%m-%Y %H:%M:%S"),
|
|
138
|
+
"query[start_station]": start,
|
|
139
|
+
"query[end_station]": end,
|
|
140
|
+
"query[only_purchasable]": str(purchasable).lower(),
|
|
141
|
+
"query[only_direct]": str(direct).lower(),
|
|
142
|
+
"query[brand_ids][]": brand_ids,
|
|
143
|
+
}
|
|
144
|
+
return (await self.get("/v2/main/connections", params=params)).json()["connections"]
|
|
145
|
+
|
|
146
|
+
async def get_connection(self, id: int) -> ConnectionDetail:
|
|
147
|
+
return (
|
|
148
|
+
await self.get(
|
|
149
|
+
f"/v2/main/connections/{id}",
|
|
150
|
+
)
|
|
151
|
+
).json()
|
|
152
|
+
|
|
153
|
+
async def get_brands(self) -> list[ApiBrand]:
|
|
154
|
+
# https://koleo.pl/api/v2/main/brands
|
|
155
|
+
return (
|
|
156
|
+
await self.get(
|
|
157
|
+
"/v2/main/brands",
|
|
158
|
+
)
|
|
159
|
+
).json()
|
|
160
|
+
|
|
161
|
+
async def get_carriers(self) -> list[Carrier]:
|
|
162
|
+
# https://koleo.pl/api/v2/main/carriers
|
|
163
|
+
return (
|
|
164
|
+
await self.get(
|
|
165
|
+
"/v2/main/carriers",
|
|
166
|
+
)
|
|
167
|
+
).json()
|
|
168
|
+
|
|
169
|
+
async def get_discounts(self) -> list[DiscountInfo]:
|
|
170
|
+
# https://koleo.pl/api/v2/main/discounts
|
|
171
|
+
return (
|
|
172
|
+
await self.get(
|
|
173
|
+
"/v2/main/discounts",
|
|
174
|
+
)
|
|
175
|
+
).json()
|
|
176
|
+
|
|
177
|
+
async def get_nested_train_place_types(self, connection_id: int) -> SeatsAvailabilityResponse:
|
|
178
|
+
# https://koleo.pl/api/v2/main/seats_availability/connection_id/train_nr/place_type
|
|
179
|
+
await self._require_auth()
|
|
180
|
+
if self._auth and "_koleo_token" not in self._auth:
|
|
181
|
+
res = await self.post(f"/prices/{connection_id}/passengers")
|
|
182
|
+
self._auth["_koleo_token"] = koleo_token = res.response.cookies["_koleo_token"].value
|
|
183
|
+
return (
|
|
184
|
+
await self.get(
|
|
185
|
+
f"/v2/main/nested_train_place_types/{connection_id}",
|
|
186
|
+
headers={"Authorization": f"Bearer {koleo_token}"},
|
|
187
|
+
use_auth=True,
|
|
188
|
+
)
|
|
189
|
+
).json()
|
|
190
|
+
|
|
191
|
+
async def get_seats_availability(
|
|
192
|
+
self, connection_id: int, train_nr: int, place_type: int
|
|
193
|
+
) -> SeatsAvailabilityResponse:
|
|
194
|
+
# https://koleo.pl/api/v2/main/seats_availability/connection_id/train_nr/place_type
|
|
195
|
+
return (
|
|
196
|
+
await self.get(
|
|
197
|
+
f"/v2/main/seats_availability/{connection_id}/{train_nr}/{place_type}",
|
|
198
|
+
)
|
|
199
|
+
).json()
|
|
200
|
+
|
|
201
|
+
async def get_train_composition(
|
|
202
|
+
self, connection_id: int, train_nr: int, place_type: int
|
|
203
|
+
) -> SeatsAvailabilityResponse:
|
|
204
|
+
# https://koleo.pl/api/v2/main/train_composition/connection_id/train_nr/place_type
|
|
205
|
+
return (
|
|
206
|
+
await self.get(
|
|
207
|
+
f"/v2/main/train_composition/{connection_id}/{train_nr}/{place_type}",
|
|
208
|
+
)
|
|
209
|
+
).json()
|
|
210
|
+
|
|
211
|
+
async def get_carriage_type(self, id: int) -> CarriageType:
|
|
212
|
+
# https://koleo.pl/api/v2/main/carriage_types/id
|
|
213
|
+
return (
|
|
214
|
+
await self.get(
|
|
215
|
+
f"/v2/main/carriage_types/{id}",
|
|
216
|
+
)
|
|
217
|
+
).json()
|
|
218
|
+
|
|
219
|
+
async def get_carriage_types(self) -> list[CarriageType]:
|
|
220
|
+
return (await self.get("/v2/main/carriage_types")).json()
|
|
221
|
+
|
|
222
|
+
async def get_station_keywoards(self) -> list[StationKeyword]:
|
|
223
|
+
return (await self.get("/v2/main/station_keywords")).json()
|
|
224
|
+
|
|
225
|
+
async def get_price(self, connection_id: int) -> Price | None:
|
|
226
|
+
res = await self.get(
|
|
227
|
+
f"https://koleo.pl/pl/prices/{connection_id}",
|
|
228
|
+
)
|
|
229
|
+
return res.json().get("price")
|
|
230
|
+
|
|
231
|
+
async def get_current_session(self) -> CurrentSession:
|
|
232
|
+
return (await self.get("/sessions/current", use_auth=True)).json()
|
|
233
|
+
|
|
234
|
+
async def get_current_user(self) -> CurrentUser:
|
|
235
|
+
return (await self.get("/users/current", use_auth=True)).json()
|
|
236
|
+
|
|
237
|
+
async def v3_connection_search(
|
|
238
|
+
self,
|
|
239
|
+
start_station_id: int,
|
|
240
|
+
end_station_id: int,
|
|
241
|
+
brand_ids: list[int],
|
|
242
|
+
date: datetime,
|
|
243
|
+
direct: bool = False,
|
|
244
|
+
) -> list[V3ConnectionResult]:
|
|
245
|
+
data = {
|
|
246
|
+
"start_id": start_station_id,
|
|
247
|
+
"end_id": end_station_id,
|
|
248
|
+
"departure_after": date.isoformat(),
|
|
249
|
+
"only_direct": direct,
|
|
250
|
+
}
|
|
251
|
+
if brand_ids:
|
|
252
|
+
data["allowed_brands"] = brand_ids
|
|
253
|
+
return (
|
|
254
|
+
await self.post("/v2/main/eol_connections/search", json=data, headers={"accept-eol-response-version": "1"})
|
|
255
|
+
).json()
|
|
256
|
+
|
|
257
|
+
async def v3_get_price(self, id: str) -> V3Price | None:
|
|
258
|
+
try:
|
|
259
|
+
return (await self.get(f"/v2/main/eol_connections/{id}/price")).json()
|
|
260
|
+
except self.errors.KoleoNotFound:
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
async def get_carrier_lines(self, carrier_slug: str) -> list[CarrierLine]:
|
|
264
|
+
return (await self.get(f"/v2/main/carrier_lines/{carrier_slug}")).json()["list"]
|
|
265
|
+
|
|
266
|
+
async def v3_get_connection_id(self, id: str) -> int:
|
|
267
|
+
return (await self.put(f"/v2/main/eol_connections/{id}/connection_id")).json()["connection_id"]
|
|
268
|
+
|
|
269
|
+
async def get_train_attributes(self) -> list[TrainAttribute]:
|
|
270
|
+
# https://koleo.pl/api/v2/main/train_atributes
|
|
271
|
+
return (
|
|
272
|
+
await self.get(
|
|
273
|
+
"/v2/main/train_attributes",
|
|
274
|
+
)
|
|
275
|
+
).json()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from aiohttp import ClientResponse, RequestInfo
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class errors:
|
|
9
|
+
class KoleoAPIException(Exception):
|
|
10
|
+
status: int
|
|
11
|
+
request: "RequestInfo"
|
|
12
|
+
response: "ClientResponse"
|
|
13
|
+
|
|
14
|
+
def __init__(self, response: "ClientResponse", *args: object) -> None:
|
|
15
|
+
super().__init__(*args)
|
|
16
|
+
self.status = response.status
|
|
17
|
+
self.request = response.request_info
|
|
18
|
+
self.response = response
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
async def from_response(response: "ClientResponse") -> "KoleoAPIException":
|
|
22
|
+
if response.status == 404:
|
|
23
|
+
return errors.KoleoNotFound(response)
|
|
24
|
+
elif response.status == 401:
|
|
25
|
+
return errors.KoleoUnauthorized(response)
|
|
26
|
+
elif response.status == 403:
|
|
27
|
+
return errors.KoleoForbidden(response)
|
|
28
|
+
elif response.status == 429:
|
|
29
|
+
return errors.KoleoRatelimited(response)
|
|
30
|
+
else:
|
|
31
|
+
return errors.KoleoAPIException(response, await response.text())
|
|
32
|
+
|
|
33
|
+
class KoleoNotFound(KoleoAPIException):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
class KoleoForbidden(KoleoAPIException):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
class KoleoUnauthorized(KoleoAPIException):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
class KoleoRatelimited(KoleoAPIException):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
class AuthRequired(Exception):
|
|
46
|
+
pass
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class LoggingMixin:
|
|
5
|
+
_l: logging.Logger
|
|
6
|
+
_l_name: str | None = None
|
|
7
|
+
|
|
8
|
+
def __init__(self, name: str | None = None) -> None:
|
|
9
|
+
if name:
|
|
10
|
+
self._l_name = name
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def logger(self) -> logging.Logger:
|
|
14
|
+
if not getattr(self, "_l", None):
|
|
15
|
+
self._l = logging.getLogger(self.logger_name)
|
|
16
|
+
return self._l
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def logger_name(self) -> str:
|
|
20
|
+
return getattr(self, "_l_name", None) or self.__class__.__name__.lower()
|
|
21
|
+
|
|
22
|
+
def dl(self, msg, *args, **kwargs):
|
|
23
|
+
self.logger.debug(msg, *args, **kwargs)
|
|
24
|
+
|
|
25
|
+
def error(self, msg, *args, **kwargs):
|
|
26
|
+
self.logger.error(msg, *args, **kwargs)
|
|
27
|
+
|
|
28
|
+
def warn(self, msg, *args, **kwargs):
|
|
29
|
+
self.logger.warning(msg, *args, **kwargs)
|
|
30
|
+
|
|
31
|
+
def info(self, msg, *args, **kwargs):
|
|
32
|
+
self.logger.info(msg, *args, **kwargs)
|
|
33
|
+
|
|
34
|
+
def create_logging_context(self, prefix: str) -> "ContextLogger":
|
|
35
|
+
return ContextLogger(self.logger, prefix)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ContextLogger:
|
|
39
|
+
def __init__(self, logger: logging.Logger, prefix: str) -> None:
|
|
40
|
+
self.logger = logger
|
|
41
|
+
self.prefix = prefix
|
|
42
|
+
|
|
43
|
+
def _make_msg(self, msg: str):
|
|
44
|
+
return f"{self.prefix}: {msg}"
|
|
45
|
+
|
|
46
|
+
def dl(self, msg, *args, **kwargs):
|
|
47
|
+
self.logger.debug(self._make_msg(msg), *args, **kwargs)
|
|
48
|
+
|
|
49
|
+
def info(self, msg, *args, **kwargs):
|
|
50
|
+
self.logger.info(self._make_msg(msg), *args, **kwargs)
|
|
51
|
+
|
|
52
|
+
def error(self, msg, *args, **kwargs):
|
|
53
|
+
self.logger.error(self._make_msg(msg), *args, **kwargs)
|
|
54
|
+
|
|
55
|
+
def warn(self, msg, *args, **kwargs):
|
|
56
|
+
self.logger.warning(self._make_msg(msg), *args, **kwargs)
|