sprawdzai-cli 0.2.0__py3-none-any.whl
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.
- sai/__init__.py +5 -0
- sai/__main__.py +4 -0
- sai/commands/__init__.py +0 -0
- sai/commands/auth_token.py +31 -0
- sai/commands/backend.py +22 -0
- sai/commands/checklib.py +146 -0
- sai/commands/ds/__init__.py +65 -0
- sai/commands/ds/create.py +138 -0
- sai/commands/ds/link.py +133 -0
- sai/commands/ds/pull.py +133 -0
- sai/commands/ds/rename.py +54 -0
- sai/commands/ds/send.py +161 -0
- sai/commands/ds/unlink.py +132 -0
- sai/commands/ds/utils.py +137 -0
- sai/commands/login.py +44 -0
- sai/commands/logout.py +37 -0
- sai/commands/me.py +48 -0
- sai/commands/open.py +1 -0
- sai/commands/pull.py +77 -0
- sai/commands/reload.py +51 -0
- sai/commands/sub.py +204 -0
- sai/commands/sync.py +687 -0
- sai/main.py +30 -0
- sai/state.py +92 -0
- sai/utils/client.py +49 -0
- sai/utils/error_handler.py +45 -0
- sprawdzai_cli-0.2.0.dist-info/METADATA +38 -0
- sprawdzai_cli-0.2.0.dist-info/RECORD +31 -0
- sprawdzai_cli-0.2.0.dist-info/WHEEL +5 -0
- sprawdzai_cli-0.2.0.dist-info/entry_points.txt +2 -0
- sprawdzai_cli-0.2.0.dist-info/top_level.txt +1 -0
sai/main.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .commands.backend import backend
|
|
3
|
+
from .commands.login import login
|
|
4
|
+
from .commands.logout import logout
|
|
5
|
+
from .commands.auth_token import auth_token
|
|
6
|
+
from .commands.me import me
|
|
7
|
+
from .commands.pull import pull
|
|
8
|
+
from .commands.sync import sync
|
|
9
|
+
from .commands.sub import sub
|
|
10
|
+
from .commands.ds import ds_app
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(
|
|
13
|
+
no_args_is_help=True,
|
|
14
|
+
help="""
|
|
15
|
+
SprawdzAI CLI tool.
|
|
16
|
+
"""
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
app.command()(backend)
|
|
20
|
+
app.command()(login)
|
|
21
|
+
app.command()(logout)
|
|
22
|
+
app.command()(auth_token)
|
|
23
|
+
app.command()(me)
|
|
24
|
+
app.command()(pull)
|
|
25
|
+
app.command()(sync)
|
|
26
|
+
app.command()(sub)
|
|
27
|
+
app.add_typer(ds_app, name="ds")
|
|
28
|
+
|
|
29
|
+
def main():
|
|
30
|
+
app()
|
sai/state.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import re
|
|
5
|
+
from urllib.parse import urlparse, urlunparse
|
|
6
|
+
|
|
7
|
+
STATE_FILE = Path.home() / ".sprawdzai"
|
|
8
|
+
|
|
9
|
+
DEFAULT_STATE = {
|
|
10
|
+
"current_backend": "remote",
|
|
11
|
+
"tokens": {}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
def load_state():
|
|
15
|
+
if not STATE_FILE.exists():
|
|
16
|
+
return DEFAULT_STATE.copy()
|
|
17
|
+
with open(STATE_FILE, "r") as f:
|
|
18
|
+
return json.load(f)
|
|
19
|
+
|
|
20
|
+
def save_state(state):
|
|
21
|
+
with open(STATE_FILE, "w") as f:
|
|
22
|
+
json.dump(state, f, indent=4)
|
|
23
|
+
|
|
24
|
+
def get_current_backend():
|
|
25
|
+
return load_state()["current_backend"]
|
|
26
|
+
|
|
27
|
+
def set_current_backend(backend: str):
|
|
28
|
+
state = load_state()
|
|
29
|
+
state["current_backend"] = backend
|
|
30
|
+
save_state(state)
|
|
31
|
+
|
|
32
|
+
def get_token(backend):
|
|
33
|
+
state = load_state()
|
|
34
|
+
return state["tokens"].get(backend)
|
|
35
|
+
|
|
36
|
+
def set_token(backend, token, expires_in):
|
|
37
|
+
state = load_state()
|
|
38
|
+
state["tokens"][backend] = {
|
|
39
|
+
"token": token,
|
|
40
|
+
"refresh_at": int(time.time() + expires_in // 2),
|
|
41
|
+
}
|
|
42
|
+
save_state(state)
|
|
43
|
+
|
|
44
|
+
def clear_token(backend):
|
|
45
|
+
state = load_state()
|
|
46
|
+
if backend in state["tokens"]:
|
|
47
|
+
del state["tokens"][backend]
|
|
48
|
+
save_state(state)
|
|
49
|
+
|
|
50
|
+
def clear_all_tokens():
|
|
51
|
+
state = load_state()
|
|
52
|
+
state["tokens"] = {}
|
|
53
|
+
save_state(state)
|
|
54
|
+
|
|
55
|
+
def normalize_url(url: str) -> str:
|
|
56
|
+
parsed = urlparse(url)
|
|
57
|
+
scheme = parsed.scheme.lower()
|
|
58
|
+
netloc = parsed.netloc.lower()
|
|
59
|
+
path = parsed.path.rstrip("/")
|
|
60
|
+
if (scheme == "http" and netloc.endswith(":80")) or (scheme == "https" and netloc.endswith(":443")):
|
|
61
|
+
netloc = netloc.rsplit(":", 1)[0]
|
|
62
|
+
normalized = urlunparse((scheme, netloc, path, "", "", ""))
|
|
63
|
+
return normalized
|
|
64
|
+
|
|
65
|
+
def get_backend_url(name: str) -> str:
|
|
66
|
+
name = name.lower()
|
|
67
|
+
|
|
68
|
+
if name in ["l", "local"] or re.fullmatch(r"l\d+", name):
|
|
69
|
+
return "http://localhost:8080"
|
|
70
|
+
if name in ["r", "remote"] or re.fullmatch(r"r\d+", name):
|
|
71
|
+
return "https://sprawdzai.org"
|
|
72
|
+
|
|
73
|
+
if name.startswith("http://") or name.startswith("https://"):
|
|
74
|
+
return normalize_url(name)
|
|
75
|
+
|
|
76
|
+
raise ValueError(f"Unknown backend type: {name}")
|
|
77
|
+
|
|
78
|
+
def get_frontend_url(name: str) -> str:
|
|
79
|
+
name = name.lower()
|
|
80
|
+
|
|
81
|
+
if name in ["l", "local"] or re.fullmatch(r"l\d+", name):
|
|
82
|
+
return "https://localhost"
|
|
83
|
+
if name in ["r", "remote"] or re.fullmatch(r"r\d+", name):
|
|
84
|
+
return "https://sprawdzai.org"
|
|
85
|
+
|
|
86
|
+
if name.startswith("http://") or name.startswith("https://"):
|
|
87
|
+
return normalize_url(name)
|
|
88
|
+
|
|
89
|
+
raise ValueError(f"Unknown backend type: {name}")
|
|
90
|
+
|
|
91
|
+
def resolve_backend(name: str | None) -> str:
|
|
92
|
+
return get_current_backend() if name is None else name
|
sai/utils/client.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from ..state import get_backend_url, get_token, set_token
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
class APIClient:
|
|
6
|
+
def __init__(self, backend_name: str):
|
|
7
|
+
self.backend_name = backend_name
|
|
8
|
+
self.base_url = get_backend_url(backend_name) + '/api'
|
|
9
|
+
|
|
10
|
+
def _auth_header(self):
|
|
11
|
+
data = get_token(self.backend_name)
|
|
12
|
+
if not data:
|
|
13
|
+
return {}
|
|
14
|
+
|
|
15
|
+
if data["refresh_at"] > time.time():
|
|
16
|
+
self.refresh(data["token"])
|
|
17
|
+
data = get_token(self.backend_name)
|
|
18
|
+
|
|
19
|
+
return {"Authorization": f"Bearer {data['token']}"}
|
|
20
|
+
|
|
21
|
+
def refresh(self, token: str):
|
|
22
|
+
try:
|
|
23
|
+
url = f"{self.base_url}/auth/refresh"
|
|
24
|
+
hdr = {"Authorization": f"Bearer {token}"}
|
|
25
|
+
resp = requests.post(url, headers=hdr)
|
|
26
|
+
if resp.status_code == 200:
|
|
27
|
+
data = resp.json()
|
|
28
|
+
set_token(
|
|
29
|
+
self.backend_name,
|
|
30
|
+
data["access_token"],
|
|
31
|
+
data["expires_in"]
|
|
32
|
+
)
|
|
33
|
+
except Exception as e:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def get(self, path, **kwargs):
|
|
37
|
+
return requests.get(self.base_url + path, headers=self._auth_header(), **kwargs)
|
|
38
|
+
|
|
39
|
+
def post(self, path, json=None, **kwargs):
|
|
40
|
+
return requests.post(self.base_url + path, json=json, headers=self._auth_header(), **kwargs)
|
|
41
|
+
|
|
42
|
+
def delete(self, path, json=None, **kwargs):
|
|
43
|
+
return requests.delete(self.base_url + path, json=json, headers=self._auth_header(), **kwargs)
|
|
44
|
+
|
|
45
|
+
def put(self, path, json=None, **kwargs):
|
|
46
|
+
return requests.put(self.base_url + path, json=json, headers=self._auth_header(), **kwargs)
|
|
47
|
+
|
|
48
|
+
def patch(self, path, json=None, **kwargs):
|
|
49
|
+
return requests.patch(self.base_url + path, json=json, headers=self._auth_header(), **kwargs)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from requests import Response
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
def get_response_error_text(resp: Response) -> str:
|
|
6
|
+
error_map = {
|
|
7
|
+
404: 'Not Found (404)',
|
|
8
|
+
500: 'Internal Server Error (500)',
|
|
9
|
+
502: 'Bad Gateway (502)'
|
|
10
|
+
}
|
|
11
|
+
detail_map = {
|
|
12
|
+
'user_not_authenticated': 'You are not authenticated',
|
|
13
|
+
'invalid_credentials': 'Invalid login and/or password',
|
|
14
|
+
'task_not_found': 'Task not found - it doesn\'t exist or you don\'t have permissions to see it (are you logged in to correct account?)',
|
|
15
|
+
'slug_not_valid': 'Slug can only contain lowercase Latin latters, digits or "-" character and must be non-empty',
|
|
16
|
+
'slug_not_unique': 'There already exists another task with the same slug'
|
|
17
|
+
}
|
|
18
|
+
err_text_begin = error_map[resp.status_code] if resp.status_code in error_map.keys() else f'Error ({resp.status_code})'
|
|
19
|
+
|
|
20
|
+
if len(resp.text) == 0:
|
|
21
|
+
return f'{err_text_begin}: [Server did not return any response]'
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
res = json.loads(resp.text)
|
|
25
|
+
except:
|
|
26
|
+
return f'{err_text_begin}: {resp.text}'
|
|
27
|
+
|
|
28
|
+
resp_keys = list(res.keys())
|
|
29
|
+
if len(resp_keys) == 1 and resp_keys[0] == "detail":
|
|
30
|
+
detail = res["detail"]
|
|
31
|
+
if type(detail) == str and detail in detail_map.keys():
|
|
32
|
+
return f'{err_text_begin}: {detail_map[detail]}'
|
|
33
|
+
return f'{err_text_begin}: {detail}'
|
|
34
|
+
return f'{err_text_begin}:\n{json.dumps(res, indent=4)}'
|
|
35
|
+
|
|
36
|
+
def write_error(txt: str):
|
|
37
|
+
typer.secho(txt, err=True, fg=typer.colors.RED)
|
|
38
|
+
raise typer.Exit(code=1)
|
|
39
|
+
|
|
40
|
+
def warn(txt: str):
|
|
41
|
+
typer.secho(txt, err=True, fg=typer.colors.YELLOW)
|
|
42
|
+
|
|
43
|
+
def handle_response_error(resp: Response):
|
|
44
|
+
write_error(get_response_error_text(resp))
|
|
45
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sprawdzai-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: SprawdzAI command line tool
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: typer>=0.20.0
|
|
8
|
+
Requires-Dist: requests>=2.32.5
|
|
9
|
+
Requires-Dist: aiofiles>=25.1.0
|
|
10
|
+
Requires-Dist: tqdm>=4.67.1
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
cd src/cli
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install build
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
To build:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bash build.sh
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
To install and run locally:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install -e .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then run:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
sai
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
All additional information and production installation link available on [SprawdzAI](https://sprawdzai.org/tworzenie-zadan)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
sai/__init__.py,sha256=FRdQHcp_Pm9K5EzLsngbrqqbzbPOFotF_uWstGKywkA,278
|
|
2
|
+
sai/__main__.py,sha256=Vdhw8YA1K3wPMlbJQYL5WqvRzAKVeZ16mZQFO9VRmCo,62
|
|
3
|
+
sai/main.py,sha256=2X-4wm3tuWC_DNutZ_idA3W6DOLE5Av2MoaqQR2xWxM,643
|
|
4
|
+
sai/state.py,sha256=Ckn_U-ZpzCacQH9VS25a4LqRSpl057W511tkRvSYsII,2573
|
|
5
|
+
sai/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
sai/commands/auth_token.py,sha256=DbZJuPW9XUWwybHBp0X5hCu-c-O1S5RDkZNG7k1ubbk,834
|
|
7
|
+
sai/commands/backend.py,sha256=zl0ZRfhN2aR79-vizrP0dQVf6pCcBsCbMM06fsYlE8Q,644
|
|
8
|
+
sai/commands/checklib.py,sha256=HpwXWmu7TalsY0FRN9Zu0PLnhi6y_fLSS2MHd_DcF8U,4699
|
|
9
|
+
sai/commands/login.py,sha256=krD6BDZE4xEHaay2O_0UwFJU7vNkI7dfM2mkDmSDEBA,1371
|
|
10
|
+
sai/commands/logout.py,sha256=MyJgbB0CDb_Dm0QaXqRoB9PcoJXHvToE5lcRgJEviPI,906
|
|
11
|
+
sai/commands/me.py,sha256=KW7YVavcIxRKifUDcmACoKnuGB4w4f5j4ladSLoJvEo,1486
|
|
12
|
+
sai/commands/open.py,sha256=1l27DR1jQRyvNFwR9eRSDxxH2yjYXXft1SBSAoFA3F0,47
|
|
13
|
+
sai/commands/pull.py,sha256=dV_F0dS6SxFSuY3aG7SP7wN9rAAk3MRTCKs5vzsBYqE,2003
|
|
14
|
+
sai/commands/reload.py,sha256=V5Iuc0z0PFg2mrycIOIBF-rPf0kYOQiN74lzjOFz9bI,1544
|
|
15
|
+
sai/commands/sub.py,sha256=dqZQ0CAzEmAG3lazyhynk5qU02b7MHG4lg4TJn2ji-A,7808
|
|
16
|
+
sai/commands/sync.py,sha256=NkgegXuAViOorfbMHUtunaI469sRu-T7cfk3Ip8WZYQ,26496
|
|
17
|
+
sai/commands/ds/__init__.py,sha256=tvFgY19iTy4mEj3EXh96B_cPwYREEUDkKuZ3g1nsBiY,2055
|
|
18
|
+
sai/commands/ds/create.py,sha256=0K4H-DtqrxDLL5DhCH3YR7a6yU2ZOYeJsQUGfqkAPs4,3919
|
|
19
|
+
sai/commands/ds/link.py,sha256=3cSiJMj36cl8UVm3EF7e1Q3coQ2eM40O8CKnzNmpxR8,4003
|
|
20
|
+
sai/commands/ds/pull.py,sha256=79UnpUFBfiUPTq-xMbapbkGJYGk30jIJLBBD-GLPhXM,5002
|
|
21
|
+
sai/commands/ds/rename.py,sha256=c1xi7aa9KIPMIqQbmbXsIypZVbW5ebtcyCGKaS81tm0,1319
|
|
22
|
+
sai/commands/ds/send.py,sha256=1Pev-9ESo2SpDAG6eIxIxjcUIW-WTMSbaqW0xgyC3b0,5674
|
|
23
|
+
sai/commands/ds/unlink.py,sha256=nmfQpG8UYxZp_lkpRty5Wvq3uNCZ3ixwj0wH_qBPnQI,4137
|
|
24
|
+
sai/commands/ds/utils.py,sha256=p3iFOgzzU3ounZasR--5-CAPDqrZbvbg_fHUDYv7Ko8,5737
|
|
25
|
+
sai/utils/client.py,sha256=L0jveIUbakUoQgaL29vm9mPaHZrf-qxRMW8iv2w9flI,1780
|
|
26
|
+
sai/utils/error_handler.py,sha256=78-bXqVX8FSazhk6jwhBnFsxB52KZSunFkMr3L0QdSY,1707
|
|
27
|
+
sprawdzai_cli-0.2.0.dist-info/METADATA,sha256=ThwqfGLAdYmDYQ4p2-5MkoNEIYR6ptowQH3f1D_AXNI,584
|
|
28
|
+
sprawdzai_cli-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
29
|
+
sprawdzai_cli-0.2.0.dist-info/entry_points.txt,sha256=l0yg1xfFdhPxHAFRJcQOX0dVhbQ1O-Hr6j4JDvIWohU,38
|
|
30
|
+
sprawdzai_cli-0.2.0.dist-info/top_level.txt,sha256=Kvf-0z0sZYQbLuM6ta183EOzD37k3VZbRH-h7HXntR0,4
|
|
31
|
+
sprawdzai_cli-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sai
|