softmax-cli 0.26.10__tar.gz → 0.26.11__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.
- {softmax_cli-0.26.10/src/softmax_cli.egg-info → softmax_cli-0.26.11}/PKG-INFO +1 -1
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/src/softmax/__init__.py +8 -9
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/src/softmax/auth.py +22 -25
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/src/softmax/cli.py +24 -27
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/src/softmax/cogames.py +4 -4
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/src/softmax/perform_login.py +14 -13
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/src/softmax/token_storage.py +3 -0
- {softmax_cli-0.26.10 → softmax_cli-0.26.11/src/softmax_cli.egg-info}/PKG-INFO +1 -1
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/tests/test_auth_login.py +15 -20
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/tests/test_python_api.py +2 -2
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/BUILD.bazel +0 -0
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/pyproject.toml +0 -0
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/setup.cfg +0 -0
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/src/softmax/_console.py +0 -0
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/src/softmax_cli.egg-info/SOURCES.txt +0 -0
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/src/softmax_cli.egg-info/dependency_links.txt +0 -0
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/src/softmax_cli.egg-info/entry_points.txt +0 -0
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/src/softmax_cli.egg-info/requires.txt +0 -0
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/src/softmax_cli.egg-info/top_level.txt +0 -0
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/tests/BUILD.bazel +0 -0
- {softmax_cli-0.26.10 → softmax_cli-0.26.11}/tests/test_cli_plugins.py +0 -0
|
@@ -5,7 +5,7 @@ import sys
|
|
|
5
5
|
from pkgutil import extend_path
|
|
6
6
|
|
|
7
7
|
from softmax.auth import (
|
|
8
|
-
|
|
8
|
+
DEFAULT_API_SERVER,
|
|
9
9
|
load_cogames_user_token,
|
|
10
10
|
save_cogames_active_token,
|
|
11
11
|
)
|
|
@@ -17,13 +17,13 @@ __path__ = extend_path(__path__, __name__)
|
|
|
17
17
|
|
|
18
18
|
def login(
|
|
19
19
|
*,
|
|
20
|
-
|
|
20
|
+
api_server: str = DEFAULT_API_SERVER,
|
|
21
21
|
force: bool = False,
|
|
22
22
|
open_browser: bool = True,
|
|
23
23
|
) -> str:
|
|
24
|
-
token = None if force else load_cogames_user_token(
|
|
24
|
+
token = None if force else load_cogames_user_token(api_server=api_server)
|
|
25
25
|
if token is not None:
|
|
26
|
-
save_cogames_active_token(
|
|
26
|
+
save_cogames_active_token(api_server=api_server, token=token)
|
|
27
27
|
return token
|
|
28
28
|
|
|
29
29
|
if not sys.stdin.isatty():
|
|
@@ -33,8 +33,7 @@ def login(
|
|
|
33
33
|
)
|
|
34
34
|
|
|
35
35
|
do_interactive_login_for_token(
|
|
36
|
-
|
|
37
|
-
server_to_save_token_under=login_server,
|
|
36
|
+
api_server=api_server,
|
|
38
37
|
token_kind=TokenKind.COGAMES_USER,
|
|
39
38
|
agent_hint=(
|
|
40
39
|
"If you are a coding agent, ask your human to open the URL below and give you "
|
|
@@ -45,10 +44,10 @@ def login(
|
|
|
45
44
|
open_browser=open_browser,
|
|
46
45
|
)
|
|
47
46
|
|
|
48
|
-
token = load_cogames_user_token(
|
|
47
|
+
token = load_cogames_user_token(api_server=api_server)
|
|
49
48
|
if token is None:
|
|
50
|
-
raise RuntimeError(f"Interactive login did not save a token for {
|
|
51
|
-
save_cogames_active_token(
|
|
49
|
+
raise RuntimeError(f"Interactive login did not save a token for {api_server}")
|
|
50
|
+
save_cogames_active_token(api_server=api_server, token=token)
|
|
52
51
|
return token
|
|
53
52
|
|
|
54
53
|
|
|
@@ -13,12 +13,11 @@ from softmax.token_storage import delete_token as delete_stored_token
|
|
|
13
13
|
from softmax.token_storage import load_token as load_saved_token
|
|
14
14
|
from softmax.token_storage import save_token as save_stored_token
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
DEFAULT_COGAMES_API_SERVER = "https://api.observatory.softmax-research.net"
|
|
16
|
+
DEFAULT_API_SERVER = "https://softmax.com/api"
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
def
|
|
21
|
-
return os.environ.get("
|
|
19
|
+
def get_api_server() -> str:
|
|
20
|
+
return os.environ.get("COGAMES_API_URL", DEFAULT_API_SERVER)
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
class WhoAmIResponse(BaseModel):
|
|
@@ -31,14 +30,14 @@ class WhoAmIResponse(BaseModel):
|
|
|
31
30
|
scopes: list[str] = Field(default_factory=list)
|
|
32
31
|
|
|
33
32
|
|
|
34
|
-
def build_browser_login_url(
|
|
33
|
+
def build_browser_login_url(api_server: str, *, callback_url: str | None = None) -> str:
|
|
35
34
|
"""Build the hosted browser sign-in URL for CLI login."""
|
|
36
35
|
params: dict[str, str] = {}
|
|
37
36
|
if callback_url:
|
|
38
37
|
params["callback"] = callback_url
|
|
39
38
|
|
|
40
39
|
query = urlencode(params)
|
|
41
|
-
parsed = urlsplit(
|
|
40
|
+
parsed = urlsplit(api_server)
|
|
42
41
|
browser_path = parsed.path.rstrip("/").removesuffix("/api") + "/cli-login"
|
|
43
42
|
return urlunsplit((parsed.scheme, parsed.netloc, browser_path, query, ""))
|
|
44
43
|
|
|
@@ -59,35 +58,33 @@ def delete_token(*, token_kind: TokenKind, server: str) -> bool:
|
|
|
59
58
|
return delete_stored_token(token_kind=token_kind, server=server)
|
|
60
59
|
|
|
61
60
|
|
|
62
|
-
def load_cogames_user_token(*,
|
|
63
|
-
return load_token(token_kind=TokenKind.COGAMES_USER, server=
|
|
61
|
+
def load_cogames_user_token(*, api_server: str) -> str | None:
|
|
62
|
+
return load_token(token_kind=TokenKind.COGAMES_USER, server=api_server)
|
|
64
63
|
|
|
65
64
|
|
|
66
|
-
def load_current_cogames_token(*,
|
|
67
|
-
return load_token(token_kind=TokenKind.COGAMES, server=
|
|
68
|
-
login_server=login_server
|
|
69
|
-
)
|
|
65
|
+
def load_current_cogames_token(*, api_server: str) -> str | None:
|
|
66
|
+
return load_token(token_kind=TokenKind.COGAMES, server=api_server) or load_cogames_user_token(api_server=api_server)
|
|
70
67
|
|
|
71
68
|
|
|
72
|
-
def save_cogames_active_token(*,
|
|
73
|
-
save_token(token_kind=TokenKind.COGAMES, server=
|
|
69
|
+
def save_cogames_active_token(*, api_server: str, token: str) -> None:
|
|
70
|
+
save_token(token_kind=TokenKind.COGAMES, server=api_server, token=token)
|
|
74
71
|
|
|
75
72
|
|
|
76
|
-
def save_cogames_user_token(*,
|
|
77
|
-
save_token(token_kind=TokenKind.COGAMES_USER, server=
|
|
78
|
-
save_cogames_active_token(
|
|
73
|
+
def save_cogames_user_token(*, api_server: str, token: str) -> None:
|
|
74
|
+
save_token(token_kind=TokenKind.COGAMES_USER, server=api_server, token=token)
|
|
75
|
+
save_cogames_active_token(api_server=api_server, token=token)
|
|
79
76
|
|
|
80
77
|
|
|
81
|
-
def delete_cogames_tokens(*,
|
|
82
|
-
deleted_active = delete_token(token_kind=TokenKind.COGAMES, server=
|
|
83
|
-
deleted_user = delete_token(token_kind=TokenKind.COGAMES_USER, server=
|
|
78
|
+
def delete_cogames_tokens(*, api_server: str) -> bool:
|
|
79
|
+
deleted_active = delete_token(token_kind=TokenKind.COGAMES, server=api_server)
|
|
80
|
+
deleted_user = delete_token(token_kind=TokenKind.COGAMES_USER, server=api_server)
|
|
84
81
|
return deleted_active or deleted_user
|
|
85
82
|
|
|
86
83
|
|
|
87
84
|
def fetch_cogames_whoami(*, api_server: str | None = None, token: str) -> WhoAmIResponse:
|
|
88
|
-
server = api_server or
|
|
85
|
+
server = api_server or DEFAULT_API_SERVER
|
|
89
86
|
response = httpx.get(
|
|
90
|
-
f"{server.rstrip('/')}/whoami",
|
|
87
|
+
f"{server.rstrip('/')}/observatory/whoami",
|
|
91
88
|
headers={"Authorization": f"Bearer {token}"},
|
|
92
89
|
timeout=10.0,
|
|
93
90
|
)
|
|
@@ -95,9 +92,9 @@ def fetch_cogames_whoami(*, api_server: str | None = None, token: str) -> WhoAmI
|
|
|
95
92
|
return WhoAmIResponse.model_validate(response.json())
|
|
96
93
|
|
|
97
94
|
|
|
98
|
-
def restore_cogames_user_session(*,
|
|
99
|
-
user_token = load_cogames_user_token(
|
|
95
|
+
def restore_cogames_user_session(*, api_server: str) -> str | None:
|
|
96
|
+
user_token = load_cogames_user_token(api_server=api_server)
|
|
100
97
|
if user_token is None:
|
|
101
98
|
return None
|
|
102
|
-
save_cogames_active_token(
|
|
99
|
+
save_cogames_active_token(api_server=api_server, token=user_token)
|
|
103
100
|
return user_token
|
|
@@ -10,11 +10,11 @@ from rich.panel import Panel
|
|
|
10
10
|
|
|
11
11
|
from softmax._console import console
|
|
12
12
|
from softmax.auth import (
|
|
13
|
-
|
|
13
|
+
DEFAULT_API_SERVER,
|
|
14
14
|
build_browser_login_url,
|
|
15
15
|
delete_cogames_tokens,
|
|
16
16
|
fetch_cogames_whoami,
|
|
17
|
-
|
|
17
|
+
get_api_server,
|
|
18
18
|
load_cogames_user_token,
|
|
19
19
|
load_current_cogames_token,
|
|
20
20
|
save_cogames_active_token,
|
|
@@ -47,9 +47,9 @@ def _build_manual_set_token_command(server: str | None = None) -> str:
|
|
|
47
47
|
return "softmax set-token '<TOKEN>'"
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def _print_non_tty_login_instructions(
|
|
51
|
-
resolved =
|
|
52
|
-
is_custom = resolved !=
|
|
50
|
+
def _print_non_tty_login_instructions(api_server: str | None = None) -> None:
|
|
51
|
+
resolved = api_server or get_api_server()
|
|
52
|
+
is_custom = resolved != DEFAULT_API_SERVER
|
|
53
53
|
auth_url = build_browser_login_url(resolved)
|
|
54
54
|
console.print("Interactive login requires a TTY.", style="red")
|
|
55
55
|
console.print()
|
|
@@ -95,11 +95,9 @@ def login_cmd(
|
|
|
95
95
|
"""Sign in to Softmax."""
|
|
96
96
|
from urllib.parse import urlparse # noqa: PLC0415
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
user_token = None if force else load_cogames_user_token(login_server=login_server)
|
|
98
|
+
api_server = server or get_api_server()
|
|
99
|
+
user_token = None if force else load_cogames_user_token(api_server=api_server)
|
|
101
100
|
if user_token is not None:
|
|
102
|
-
api_server = None if is_default_server else login_server
|
|
103
101
|
try:
|
|
104
102
|
whoami = fetch_cogames_whoami(api_server=api_server, token=user_token)
|
|
105
103
|
if whoami.subject_type == "anonymous":
|
|
@@ -115,18 +113,17 @@ def login_cmd(
|
|
|
115
113
|
console.print("Could not reach server to verify token. Proceeding with saved token.", style="yellow")
|
|
116
114
|
|
|
117
115
|
if user_token is not None:
|
|
118
|
-
save_cogames_active_token(
|
|
119
|
-
console.print(f"Already authenticated with {urlparse(
|
|
116
|
+
save_cogames_active_token(api_server=api_server, token=user_token)
|
|
117
|
+
console.print(f"Already authenticated with {urlparse(api_server).hostname}", style="green")
|
|
120
118
|
return
|
|
121
119
|
|
|
122
120
|
if not sys.stdin.isatty():
|
|
123
|
-
_print_non_tty_login_instructions(
|
|
121
|
+
_print_non_tty_login_instructions(api_server)
|
|
124
122
|
raise typer.Exit(1)
|
|
125
123
|
|
|
126
124
|
try:
|
|
127
125
|
do_interactive_login_for_token(
|
|
128
|
-
|
|
129
|
-
server_to_save_token_under=login_server,
|
|
126
|
+
api_server=api_server,
|
|
130
127
|
token_kind=TokenKind.COGAMES_USER,
|
|
131
128
|
agent_hint=(
|
|
132
129
|
"If you are a coding agent, ask your human to open the URL below and give you "
|
|
@@ -142,9 +139,9 @@ def login_cmd(
|
|
|
142
139
|
console.print("Authentication failed.", style="red")
|
|
143
140
|
raise typer.Exit(1) from e
|
|
144
141
|
|
|
145
|
-
user_token = load_cogames_user_token(
|
|
142
|
+
user_token = load_cogames_user_token(api_server=api_server)
|
|
146
143
|
assert user_token is not None
|
|
147
|
-
save_cogames_active_token(
|
|
144
|
+
save_cogames_active_token(api_server=api_server, token=user_token)
|
|
148
145
|
console.print("Authentication successful.", style="green")
|
|
149
146
|
|
|
150
147
|
|
|
@@ -159,8 +156,8 @@ def logout_cmd(
|
|
|
159
156
|
),
|
|
160
157
|
) -> None:
|
|
161
158
|
"""Remove saved authentication token."""
|
|
162
|
-
|
|
163
|
-
if delete_cogames_tokens(
|
|
159
|
+
api_server = server or get_api_server()
|
|
160
|
+
if delete_cogames_tokens(api_server=api_server):
|
|
164
161
|
console.print("Logged out.", style="green")
|
|
165
162
|
else:
|
|
166
163
|
console.print("No token found — already logged out.", style="yellow")
|
|
@@ -177,7 +174,7 @@ def get_login_url_cmd(
|
|
|
177
174
|
),
|
|
178
175
|
) -> None:
|
|
179
176
|
"""Print a browser sign-in URL for manual login."""
|
|
180
|
-
print(build_browser_login_url(server or
|
|
177
|
+
print(build_browser_login_url(server or get_api_server()))
|
|
181
178
|
|
|
182
179
|
|
|
183
180
|
@app.command(name="status")
|
|
@@ -191,13 +188,13 @@ def status_cmd(
|
|
|
191
188
|
),
|
|
192
189
|
) -> None:
|
|
193
190
|
"""Check authentication status via /whoami."""
|
|
194
|
-
|
|
195
|
-
token = load_current_cogames_token(
|
|
191
|
+
api_server = server or get_api_server()
|
|
192
|
+
token = load_current_cogames_token(api_server=api_server)
|
|
196
193
|
if not token:
|
|
197
194
|
console.print("[red]Not authenticated.[/red] Run [cyan]softmax login[/cyan] first.")
|
|
198
195
|
raise typer.Exit(1)
|
|
199
196
|
|
|
200
|
-
session = fetch_cogames_whoami(api_server=
|
|
197
|
+
session = fetch_cogames_whoami(api_server=api_server, token=token)
|
|
201
198
|
if session.subject_type == "anonymous":
|
|
202
199
|
console.print("[red]Token is invalid or expired.[/red] Run [cyan]softmax login[/cyan] to re-authenticate.")
|
|
203
200
|
raise typer.Exit(1)
|
|
@@ -219,8 +216,8 @@ def get_token_cmd(
|
|
|
219
216
|
),
|
|
220
217
|
) -> None:
|
|
221
218
|
"""Print the saved token to stdout (for scripting)."""
|
|
222
|
-
|
|
223
|
-
token = load_current_cogames_token(
|
|
219
|
+
api_server = server or get_api_server()
|
|
220
|
+
token = load_current_cogames_token(api_server=api_server)
|
|
224
221
|
if not token:
|
|
225
222
|
console.print("[red]No token found.[/red] Run [cyan]softmax login[/cyan] first.", style="bold")
|
|
226
223
|
raise typer.Exit(1)
|
|
@@ -239,9 +236,9 @@ def set_token_cmd(
|
|
|
239
236
|
),
|
|
240
237
|
) -> None:
|
|
241
238
|
"""Manually set a token (for CI or headless environments)."""
|
|
242
|
-
|
|
243
|
-
save_cogames_user_token(
|
|
244
|
-
print(f"\nToken saved for {
|
|
239
|
+
api_server = server or get_api_server()
|
|
240
|
+
save_cogames_user_token(api_server=api_server, token=token)
|
|
241
|
+
print(f"\nToken saved for {api_server}")
|
|
245
242
|
|
|
246
243
|
|
|
247
244
|
_register_optional_apps()
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import importlib
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
-
from softmax.auth import
|
|
6
|
+
from softmax.auth import DEFAULT_API_SERVER
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def _get_tournament_client_class() -> type[Any]:
|
|
@@ -26,7 +26,7 @@ class _PlayerAPI:
|
|
|
26
26
|
self,
|
|
27
27
|
token: str,
|
|
28
28
|
*,
|
|
29
|
-
server: str =
|
|
29
|
+
server: str = DEFAULT_API_SERVER,
|
|
30
30
|
) -> list[Any]:
|
|
31
31
|
with _create_client(token=token, server=server) as client:
|
|
32
32
|
return client.list_players()
|
|
@@ -39,7 +39,7 @@ def login(
|
|
|
39
39
|
token: str,
|
|
40
40
|
player_id: str,
|
|
41
41
|
*,
|
|
42
|
-
server: str =
|
|
42
|
+
server: str = DEFAULT_API_SERVER,
|
|
43
43
|
) -> str:
|
|
44
44
|
with _create_client(token=token, server=server) as client:
|
|
45
45
|
return client.login_player(player_id).token
|
|
@@ -49,7 +49,7 @@ def login_response(
|
|
|
49
49
|
token: str,
|
|
50
50
|
player_id: str,
|
|
51
51
|
*,
|
|
52
|
-
server: str =
|
|
52
|
+
server: str = DEFAULT_API_SERVER,
|
|
53
53
|
) -> Any:
|
|
54
54
|
with _create_client(token=token, server=server) as client:
|
|
55
55
|
return client.login_player(player_id)
|
|
@@ -269,11 +269,11 @@ def _create_app(session: _CLIAuthSession) -> FastAPI:
|
|
|
269
269
|
return app
|
|
270
270
|
|
|
271
271
|
|
|
272
|
-
def _validate_token(*,
|
|
273
|
-
|
|
272
|
+
def _validate_token(*, api_server: str, token: str) -> bool | None:
|
|
273
|
+
whoami_url = f"{api_server.rstrip('/')}/observatory/whoami"
|
|
274
274
|
try:
|
|
275
275
|
response = httpx.get(
|
|
276
|
-
|
|
276
|
+
whoami_url,
|
|
277
277
|
headers={"Authorization": f"Bearer {token}"},
|
|
278
278
|
timeout=5.0,
|
|
279
279
|
)
|
|
@@ -282,8 +282,8 @@ def _validate_token(*, login_server: str, token: str) -> bool | None:
|
|
|
282
282
|
|
|
283
283
|
if response.status_code == 200:
|
|
284
284
|
data = response.json()
|
|
285
|
-
return
|
|
286
|
-
if response.status_code in {400, 401, 403
|
|
285
|
+
return data.get("subject_type") != "anonymous"
|
|
286
|
+
if response.status_code in {400, 401, 403}:
|
|
287
287
|
return False
|
|
288
288
|
return None
|
|
289
289
|
|
|
@@ -304,7 +304,7 @@ def _print_login_instructions(*, auth_url: str, agent_hint: str | None) -> None:
|
|
|
304
304
|
console.print()
|
|
305
305
|
|
|
306
306
|
|
|
307
|
-
def _start_manual_token_prompt(*, session: _CLIAuthSession,
|
|
307
|
+
def _start_manual_token_prompt(*, session: _CLIAuthSession, api_server: str) -> None:
|
|
308
308
|
def prompt_loop() -> None:
|
|
309
309
|
while not session.auth_completed.is_set():
|
|
310
310
|
try:
|
|
@@ -317,7 +317,7 @@ def _start_manual_token_prompt(*, session: _CLIAuthSession, login_server: str) -
|
|
|
317
317
|
if not token:
|
|
318
318
|
continue
|
|
319
319
|
|
|
320
|
-
validation_result = _validate_token(
|
|
320
|
+
validation_result = _validate_token(api_server=api_server, token=token)
|
|
321
321
|
if validation_result is False:
|
|
322
322
|
console.print("Invalid token. Please try again.", style="red")
|
|
323
323
|
continue
|
|
@@ -360,11 +360,11 @@ def _wait_for_callback_server_to_start(*, session: _CLIAuthSession, port: int, t
|
|
|
360
360
|
|
|
361
361
|
def do_interactive_login_for_token(
|
|
362
362
|
*,
|
|
363
|
-
|
|
364
|
-
server_to_save_token_under: str,
|
|
363
|
+
api_server: str,
|
|
365
364
|
token_kind: TokenKind,
|
|
366
365
|
agent_hint: str | None,
|
|
367
366
|
open_browser: bool,
|
|
367
|
+
save_token_under: str | None = None,
|
|
368
368
|
) -> None:
|
|
369
369
|
"""Run the CLI browser login flow and save the resulting token."""
|
|
370
370
|
assert sys.stdin.isatty(), "This function should only be called when stdin is a TTY"
|
|
@@ -378,18 +378,19 @@ def do_interactive_login_for_token(
|
|
|
378
378
|
callback_url = f"http://127.0.0.1:{port}/callback"
|
|
379
379
|
session.error = None
|
|
380
380
|
|
|
381
|
-
auth_url = build_browser_login_url(
|
|
381
|
+
auth_url = build_browser_login_url(api_server, callback_url=callback_url)
|
|
382
382
|
_print_login_instructions(auth_url=auth_url, agent_hint=agent_hint)
|
|
383
383
|
if open_browser:
|
|
384
384
|
_open_browser(url=auth_url)
|
|
385
385
|
|
|
386
|
-
_start_manual_token_prompt(session=session,
|
|
386
|
+
_start_manual_token_prompt(session=session, api_server=api_server)
|
|
387
387
|
session.auth_completed.wait()
|
|
388
388
|
if session.error:
|
|
389
389
|
raise RuntimeError(session.error)
|
|
390
390
|
if not session.token:
|
|
391
391
|
raise RuntimeError("No token received")
|
|
392
392
|
|
|
393
|
-
|
|
394
|
-
|
|
393
|
+
token_server = save_token_under or api_server
|
|
394
|
+
save_token(token_kind=token_kind, server=token_server, token=session.token)
|
|
395
|
+
print(f"\nToken saved for {token_server}")
|
|
395
396
|
print()
|
|
@@ -45,6 +45,7 @@ def _load_token_data(*, token_file_name: str) -> dict:
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def load_token(*, token_kind: TokenKind, server: str) -> str | None:
|
|
48
|
+
server = server.rstrip("/")
|
|
48
49
|
data = _load_token_data(token_file_name=_token_file_name(token_kind=token_kind))
|
|
49
50
|
assert isinstance(data, dict), "Token storage file must contain a mapping at the top level"
|
|
50
51
|
|
|
@@ -57,6 +58,7 @@ def load_token(*, token_kind: TokenKind, server: str) -> str | None:
|
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
def save_token(*, token_kind: TokenKind, server: str, token: str) -> None:
|
|
61
|
+
server = server.rstrip("/")
|
|
60
62
|
token_file_name = _token_file_name(token_kind=token_kind)
|
|
61
63
|
token_storage_key = _token_storage_key(token_kind=token_kind)
|
|
62
64
|
data = _load_token_data(token_file_name=token_file_name)
|
|
@@ -76,6 +78,7 @@ def save_token(*, token_kind: TokenKind, server: str, token: str) -> None:
|
|
|
76
78
|
|
|
77
79
|
|
|
78
80
|
def delete_token(*, token_kind: TokenKind, server: str) -> bool:
|
|
81
|
+
server = server.rstrip("/")
|
|
79
82
|
token_file_name = _token_file_name(token_kind=token_kind)
|
|
80
83
|
token_storage_key = _token_storage_key(token_kind=token_kind)
|
|
81
84
|
data = _load_token_data(token_file_name=token_file_name)
|
|
@@ -28,13 +28,12 @@ def test_authenticate_accepts_pasted_token(
|
|
|
28
28
|
monkeypatch.setattr("softmax.perform_login._find_free_port", lambda: 43123)
|
|
29
29
|
monkeypatch.setattr("softmax.perform_login._run_server", lambda *, session, port: None)
|
|
30
30
|
monkeypatch.setattr("softmax.perform_login._wait_for_callback_server_to_start", lambda *, session, port: False)
|
|
31
|
-
monkeypatch.setattr("softmax.perform_login._validate_token", lambda *,
|
|
31
|
+
monkeypatch.setattr("softmax.perform_login._validate_token", lambda *, api_server, token: True)
|
|
32
32
|
monkeypatch.setattr(sys.stdin, "isatty", lambda: True)
|
|
33
33
|
monkeypatch.setattr(builtins, "input", lambda _prompt="": "manual-token-123")
|
|
34
34
|
|
|
35
35
|
do_interactive_login_for_token(
|
|
36
|
-
|
|
37
|
-
server_to_save_token_under="https://softmax.com/api",
|
|
36
|
+
api_server="https://softmax.com/api",
|
|
38
37
|
token_kind=TokenKind.COGAMES,
|
|
39
38
|
agent_hint=COGAMES_AGENT_HINT,
|
|
40
39
|
open_browser=False,
|
|
@@ -63,12 +62,11 @@ def test_authenticate_skips_browser_when_requested(
|
|
|
63
62
|
)
|
|
64
63
|
monkeypatch.setattr(
|
|
65
64
|
"softmax.perform_login._start_manual_token_prompt",
|
|
66
|
-
lambda *, session,
|
|
65
|
+
lambda *, session, api_server: auth_module._finish_authentication(session, token="manual-token-456"),
|
|
67
66
|
)
|
|
68
67
|
|
|
69
68
|
do_interactive_login_for_token(
|
|
70
|
-
|
|
71
|
-
server_to_save_token_under="https://softmax.com/api",
|
|
69
|
+
api_server="https://softmax.com/api",
|
|
72
70
|
token_kind=TokenKind.COGAMES,
|
|
73
71
|
agent_hint=COGAMES_AGENT_HINT,
|
|
74
72
|
open_browser=False,
|
|
@@ -100,12 +98,11 @@ def test_authenticate_falls_back_to_manual_when_callback_server_fails(
|
|
|
100
98
|
)
|
|
101
99
|
monkeypatch.setattr(
|
|
102
100
|
"softmax.perform_login._start_manual_token_prompt",
|
|
103
|
-
lambda *, session,
|
|
101
|
+
lambda *, session, api_server: auth_module._finish_authentication(session, token="manual-token-789"),
|
|
104
102
|
)
|
|
105
103
|
|
|
106
104
|
do_interactive_login_for_token(
|
|
107
|
-
|
|
108
|
-
server_to_save_token_under="https://softmax.com/api",
|
|
105
|
+
api_server="https://softmax.com/api",
|
|
109
106
|
token_kind=TokenKind.COGAMES,
|
|
110
107
|
agent_hint=COGAMES_AGENT_HINT,
|
|
111
108
|
open_browser=True,
|
|
@@ -132,12 +129,11 @@ def test_authenticate_reprompts_after_invalid_token(
|
|
|
132
129
|
validation_results = iter([False, True])
|
|
133
130
|
monkeypatch.setattr(
|
|
134
131
|
"softmax.perform_login._validate_token",
|
|
135
|
-
lambda *,
|
|
132
|
+
lambda *, api_server, token: next(validation_results),
|
|
136
133
|
)
|
|
137
134
|
|
|
138
135
|
do_interactive_login_for_token(
|
|
139
|
-
|
|
140
|
-
server_to_save_token_under="https://softmax.com/api",
|
|
136
|
+
api_server="https://softmax.com/api",
|
|
141
137
|
token_kind=TokenKind.COGAMES,
|
|
142
138
|
agent_hint=COGAMES_AGENT_HINT,
|
|
143
139
|
open_browser=False,
|
|
@@ -166,15 +162,15 @@ def test_generic_authenticator_does_not_print_cogames_agent_hint(
|
|
|
166
162
|
monkeypatch.setattr("softmax.perform_login._run_server", lambda *, session, port: None)
|
|
167
163
|
monkeypatch.setattr(
|
|
168
164
|
"softmax.perform_login._start_manual_token_prompt",
|
|
169
|
-
lambda *, session,
|
|
165
|
+
lambda *, session, api_server: auth_module._finish_authentication(session, token="manual-token-000"),
|
|
170
166
|
)
|
|
171
167
|
|
|
172
168
|
do_interactive_login_for_token(
|
|
173
|
-
|
|
174
|
-
server_to_save_token_under="token-key",
|
|
169
|
+
api_server="https://softmax.com/api",
|
|
175
170
|
token_kind=TokenKind.OBSERVATORY,
|
|
176
171
|
agent_hint=None,
|
|
177
172
|
open_browser=False,
|
|
173
|
+
save_token_under="token-key",
|
|
178
174
|
)
|
|
179
175
|
output = capsys.readouterr().out
|
|
180
176
|
assert "Open this URL in any browser to sign in:" in output
|
|
@@ -193,15 +189,15 @@ def test_generic_authenticator_works_without_agent_hint(
|
|
|
193
189
|
monkeypatch.setattr("softmax.perform_login._run_server", lambda *, session, port: None)
|
|
194
190
|
monkeypatch.setattr(
|
|
195
191
|
"softmax.perform_login._start_manual_token_prompt",
|
|
196
|
-
lambda *, session,
|
|
192
|
+
lambda *, session, api_server: auth_module._finish_authentication(session, token="manual-token-001"),
|
|
197
193
|
)
|
|
198
194
|
|
|
199
195
|
do_interactive_login_for_token(
|
|
200
|
-
|
|
201
|
-
server_to_save_token_under="token-key",
|
|
196
|
+
api_server="https://softmax.com/api",
|
|
202
197
|
token_kind=TokenKind.OBSERVATORY,
|
|
203
198
|
agent_hint=None,
|
|
204
199
|
open_browser=False,
|
|
200
|
+
save_token_under="token-key",
|
|
205
201
|
)
|
|
206
202
|
|
|
207
203
|
|
|
@@ -254,8 +250,7 @@ def test_interactive_login_requires_tty(
|
|
|
254
250
|
|
|
255
251
|
with pytest.raises(AssertionError, match="only be called when stdin is a TTY"):
|
|
256
252
|
do_interactive_login_for_token(
|
|
257
|
-
|
|
258
|
-
server_to_save_token_under="https://softmax.com/api",
|
|
253
|
+
api_server="https://softmax.com/api",
|
|
259
254
|
token_kind=TokenKind.COGAMES,
|
|
260
255
|
agent_hint=COGAMES_AGENT_HINT,
|
|
261
256
|
open_browser=False,
|
|
@@ -91,7 +91,7 @@ def test_softmax_cogames_player_list_uses_expected_defaults(monkeypatch: pytest.
|
|
|
91
91
|
|
|
92
92
|
assert cast(Any, softmax).cogames.player.list("softmax-token") == ["alpha", "beta"]
|
|
93
93
|
assert captured == {
|
|
94
|
-
"server_url": "https://
|
|
94
|
+
"server_url": "https://softmax.com/api",
|
|
95
95
|
"token": "softmax-token",
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -112,7 +112,7 @@ def test_softmax_cogames_login_returns_player_token(monkeypatch: pytest.MonkeyPa
|
|
|
112
112
|
return None
|
|
113
113
|
|
|
114
114
|
def login_player(self, player_id: str) -> FakeLoginResponse:
|
|
115
|
-
assert self.server_url == "https://
|
|
115
|
+
assert self.server_url == "https://softmax.com/api"
|
|
116
116
|
assert self.token == "softmax-token"
|
|
117
117
|
assert player_id == "ply_alpha"
|
|
118
118
|
return FakeLoginResponse()
|
|
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
|
|
File without changes
|
|
File without changes
|