code-battles-cli 1.2.2__tar.gz → 1.2.5__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.
- {code_battles_cli-1.2.2 → code_battles_cli-1.2.5}/PKG-INFO +6 -11
- {code_battles_cli-1.2.2 → code_battles_cli-1.2.5}/README.md +2 -7
- code_battles_cli-1.2.5/pyproject.toml +33 -0
- code_battles_cli-1.2.2/pyproject.toml → code_battles_cli-1.2.5/pyproject.toml.orig +9 -10
- code_battles_cli-1.2.5/src/code_battles_cli/__init__.py +1 -0
- {code_battles_cli-1.2.2 → code_battles_cli-1.2.5}/src/code_battles_cli/__main__.py +2 -1
- {code_battles_cli-1.2.2 → code_battles_cli-1.2.5}/src/code_battles_cli/api.py +92 -58
- {code_battles_cli-1.2.2 → code_battles_cli-1.2.5}/src/code_battles_cli/cli.py +3 -5
- {code_battles_cli-1.2.2 → code_battles_cli-1.2.5}/src/code_battles_cli/commands/download.py +8 -7
- {code_battles_cli-1.2.2 → code_battles_cli-1.2.5}/src/code_battles_cli/commands/run.py +13 -12
- {code_battles_cli-1.2.2 → code_battles_cli-1.2.5}/src/code_battles_cli/commands/upload.py +7 -6
- {code_battles_cli-1.2.2 → code_battles_cli-1.2.5}/src/code_battles_cli/log.py +3 -3
- code_battles_cli-1.2.2/.github/workflows/test.yml +0 -21
- code_battles_cli-1.2.2/.gitignore +0 -13
- code_battles_cli-1.2.2/.python-version +0 -1
- code_battles_cli-1.2.2/LICENSE.md +0 -7
- code_battles_cli-1.2.2/requirements-dev.lock +0 -84
- code_battles_cli-1.2.2/requirements.lock +0 -72
- code_battles_cli-1.2.2/src/code_battles_cli/__init__.py +0 -1
- code_battles_cli-1.2.2/tests/test_cli.py +0 -2
- {code_battles_cli-1.2.2 → code_battles_cli-1.2.5}/src/code_battles_cli/commands/__init__.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: code-battles-cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.5
|
|
4
4
|
Summary: A CLI for competitors in any Code Battles event!
|
|
5
|
-
Requires-Python: >=3.8
|
|
6
|
-
Requires-Dist: click>=8.1.7
|
|
7
|
-
Requires-Dist: google-cloud-firestore>=2.19.0
|
|
8
5
|
Requires-Dist: requests>=2.32.3
|
|
6
|
+
Requires-Dist: google-cloud-firestore>=2.19.0
|
|
9
7
|
Requires-Dist: rich>=13.9.3
|
|
8
|
+
Requires-Dist: click>=8.1.7
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -27,8 +27,6 @@ Description-Content-Type: text/markdown
|
|
|
27
27
|
🏃 <a href="#getting-started">Getting Started</a>
|
|
28
28
|
·
|
|
29
29
|
💡 <a href="#features">Features</a>
|
|
30
|
-
·
|
|
31
|
-
🚗 <a href="#roadmap">Roadmap</a>
|
|
32
30
|
</p>
|
|
33
31
|
|
|
34
32
|
# Getting Started
|
|
@@ -72,9 +70,6 @@ print(client.get_bots())
|
|
|
72
70
|
print(client.run_simulation("NYC", ["bots/example.py", "bots/another_example.py"]))
|
|
73
71
|
```
|
|
74
72
|
|
|
75
|
-
#
|
|
73
|
+
# Development
|
|
76
74
|
|
|
77
|
-
-
|
|
78
|
-
- [x] Upload bots.
|
|
79
|
-
- [x] Run no-UI simulations locally.
|
|
80
|
-
- [x] Be usable in scripts.
|
|
75
|
+
For development, you should set up the pre-commit hooks with `uv run pre-commit install`.
|
|
@@ -16,8 +16,6 @@
|
|
|
16
16
|
🏃 <a href="#getting-started">Getting Started</a>
|
|
17
17
|
·
|
|
18
18
|
💡 <a href="#features">Features</a>
|
|
19
|
-
·
|
|
20
|
-
🚗 <a href="#roadmap">Roadmap</a>
|
|
21
19
|
</p>
|
|
22
20
|
|
|
23
21
|
# Getting Started
|
|
@@ -61,9 +59,6 @@ print(client.get_bots())
|
|
|
61
59
|
print(client.run_simulation("NYC", ["bots/example.py", "bots/another_example.py"]))
|
|
62
60
|
```
|
|
63
61
|
|
|
64
|
-
#
|
|
62
|
+
# Development
|
|
65
63
|
|
|
66
|
-
-
|
|
67
|
-
- [x] Upload bots.
|
|
68
|
-
- [x] Run no-UI simulations locally.
|
|
69
|
-
- [x] Be usable in scripts.
|
|
64
|
+
For development, you should set up the pre-commit hooks with `uv run pre-commit install`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "code-battles-cli"
|
|
3
|
+
version = "1.2.5"
|
|
4
|
+
description = "A CLI for competitors in any Code Battles event!"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"requests>=2.32.3",
|
|
7
|
+
"google-cloud-firestore>=2.19.0",
|
|
8
|
+
"rich>=13.9.3",
|
|
9
|
+
"click>=8.1.7",
|
|
10
|
+
]
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">= 3.8"
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
code-battles = "code_battles_cli:main"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["uv_build>=0.10,<0.13"]
|
|
19
|
+
build-backend = "uv_build"
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = [
|
|
23
|
+
"mypy>=1.14.1",
|
|
24
|
+
"pre-commit>=3.5.0",
|
|
25
|
+
"pytest>=8.3.5",
|
|
26
|
+
"ruff>=0.16.10",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[tool.mypy]
|
|
30
|
+
untyped_calls_exclude = "google"
|
|
31
|
+
|
|
32
|
+
[tool.ruff.lint]
|
|
33
|
+
ignore = ["LOG015"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "code-battles-cli"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.5"
|
|
4
4
|
description = "A CLI for competitors in any Code Battles event!"
|
|
5
5
|
dependencies = [
|
|
6
6
|
"requests>=2.32.3",
|
|
@@ -15,15 +15,14 @@ requires-python = ">= 3.8"
|
|
|
15
15
|
"code-battles" = "code_battles_cli:main"
|
|
16
16
|
|
|
17
17
|
[build-system]
|
|
18
|
-
requires = ["
|
|
19
|
-
build-backend = "
|
|
18
|
+
requires = ["uv_build>=0.10,<0.13"]
|
|
19
|
+
build-backend = "uv_build"
|
|
20
20
|
|
|
21
|
-
[
|
|
22
|
-
|
|
23
|
-
dev-dependencies = ["pytest>=8.3.5"]
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = ["mypy>=1.14.1", "pre-commit>=3.5.0", "pytest>=8.3.5", "ruff>=0.16.10"]
|
|
24
23
|
|
|
25
|
-
[tool.
|
|
26
|
-
|
|
24
|
+
[tool.mypy]
|
|
25
|
+
untyped_calls_exclude = "google"
|
|
27
26
|
|
|
28
|
-
[tool.
|
|
29
|
-
|
|
27
|
+
[tool.ruff]
|
|
28
|
+
lint.ignore = ["LOG015"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from code_battles_cli.cli import main as main
|
|
@@ -4,6 +4,8 @@ Code Battles Python Client API
|
|
|
4
4
|
Firestore client implementation inspired by https://medium.com/@bobthomas295/client-side-authentication-with-python-firestore-and-firebase-352e484a2634
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
7
9
|
import base64
|
|
8
10
|
import datetime
|
|
9
11
|
import gzip
|
|
@@ -15,12 +17,13 @@ import subprocess
|
|
|
15
17
|
import sys
|
|
16
18
|
from dataclasses import dataclass
|
|
17
19
|
from pathlib import Path
|
|
18
|
-
from typing import Any, Callable,
|
|
20
|
+
from typing import Any, Callable, Literal
|
|
19
21
|
|
|
20
22
|
import requests
|
|
21
23
|
from google.cloud.firestore import Client as FirestoreClient
|
|
22
24
|
from google.oauth2.credentials import Credentials
|
|
23
25
|
from rich.prompt import Prompt
|
|
26
|
+
from typing_extensions import overload
|
|
24
27
|
|
|
25
28
|
from code_battles_cli.log import console, log
|
|
26
29
|
|
|
@@ -28,7 +31,7 @@ SIMULATION_FINISHED_MARK = b"--- SIMULATION FINISHED ---"
|
|
|
28
31
|
SIMULATION_STEP_MARK = b"__CODE_BATTLES_ADVANCE_STEP"
|
|
29
32
|
|
|
30
33
|
|
|
31
|
-
def normalize(url: str):
|
|
34
|
+
def normalize(url: str) -> str:
|
|
32
35
|
if url.startswith("https"):
|
|
33
36
|
url = url[5:]
|
|
34
37
|
|
|
@@ -45,7 +48,7 @@ class SimulationException(Exception):
|
|
|
45
48
|
self.stderr = stderr
|
|
46
49
|
self.exit_code = exit_code
|
|
47
50
|
|
|
48
|
-
def __str__(self):
|
|
51
|
+
def __str__(self) -> str:
|
|
49
52
|
return (
|
|
50
53
|
f"Simulation failed with exit code {self.exit_code}. Output:\n"
|
|
51
54
|
+ self.stderr.decode()
|
|
@@ -57,7 +60,7 @@ class LogEntry:
|
|
|
57
60
|
step: int
|
|
58
61
|
text: str
|
|
59
62
|
color: str
|
|
60
|
-
player_index:
|
|
63
|
+
player_index: int | None
|
|
61
64
|
|
|
62
65
|
|
|
63
66
|
@dataclass
|
|
@@ -65,22 +68,23 @@ class SimulationResults:
|
|
|
65
68
|
winner_index: int
|
|
66
69
|
winner: str
|
|
67
70
|
steps: int
|
|
68
|
-
logs:
|
|
71
|
+
logs: list[LogEntry]
|
|
69
72
|
|
|
70
73
|
|
|
71
74
|
@dataclass
|
|
72
75
|
class Simulation:
|
|
73
|
-
parameters:
|
|
76
|
+
parameters: dict[str, str]
|
|
77
|
+
statistics: dict[str, float]
|
|
74
78
|
player_names: str
|
|
75
79
|
game: str
|
|
76
80
|
version: str
|
|
77
81
|
timestamp: datetime.datetime
|
|
78
|
-
logs: list
|
|
79
|
-
alerts: list
|
|
80
|
-
decisions:
|
|
82
|
+
logs: list[Any]
|
|
83
|
+
alerts: list[Any]
|
|
84
|
+
decisions: list[bytes]
|
|
81
85
|
seed: int
|
|
82
86
|
|
|
83
|
-
def dump(self):
|
|
87
|
+
def dump(self) -> str:
|
|
84
88
|
return base64.b64encode(
|
|
85
89
|
gzip.compress(
|
|
86
90
|
json.dumps(
|
|
@@ -97,18 +101,20 @@ class Simulation:
|
|
|
97
101
|
for decision in self.decisions
|
|
98
102
|
],
|
|
99
103
|
"seed": self.seed,
|
|
104
|
+
"statistics": self.statistics,
|
|
100
105
|
}
|
|
101
106
|
).encode()
|
|
102
107
|
)
|
|
103
108
|
).decode()
|
|
104
109
|
|
|
105
110
|
@staticmethod
|
|
106
|
-
def load(file: str):
|
|
107
|
-
contents:
|
|
111
|
+
def load(file: str) -> Simulation:
|
|
112
|
+
contents: dict[str, Any] = json.loads(gzip.decompress(base64.b64decode(file)))
|
|
108
113
|
return Simulation(
|
|
109
114
|
contents["parameters"]
|
|
110
115
|
if "parameters" in contents
|
|
111
116
|
else {"map": contents["map"]},
|
|
117
|
+
contents.get("statistics", {}),
|
|
112
118
|
contents["playerNames"],
|
|
113
119
|
contents["game"],
|
|
114
120
|
contents["version"],
|
|
@@ -123,10 +129,10 @@ class Simulation:
|
|
|
123
129
|
class Client:
|
|
124
130
|
def __init__(
|
|
125
131
|
self,
|
|
126
|
-
url:
|
|
127
|
-
username:
|
|
128
|
-
password:
|
|
129
|
-
dump_credentials=True,
|
|
132
|
+
url: str | None = None,
|
|
133
|
+
username: str | None = None,
|
|
134
|
+
password: str | None = None,
|
|
135
|
+
dump_credentials: bool = True,
|
|
130
136
|
):
|
|
131
137
|
"""
|
|
132
138
|
Creates a client for getting and setting the bots for a Code Battles hosted at `url`
|
|
@@ -146,16 +152,13 @@ class Client:
|
|
|
146
152
|
if dump_credentials:
|
|
147
153
|
self._dump_credentials()
|
|
148
154
|
|
|
149
|
-
def _get_credentials(self):
|
|
155
|
+
def _get_credentials(self) -> None:
|
|
150
156
|
if os.path.exists("code-battles.json"):
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
self.password: str = configuration["password"]
|
|
157
|
-
except Exception:
|
|
158
|
-
pass
|
|
157
|
+
with open("code-battles.json", "r") as f:
|
|
158
|
+
configuration = json.load(f)
|
|
159
|
+
self.url = configuration["url"]
|
|
160
|
+
self.username = configuration["username"]
|
|
161
|
+
self.password = configuration["password"]
|
|
159
162
|
|
|
160
163
|
if (
|
|
161
164
|
not hasattr(self, "url")
|
|
@@ -171,14 +174,14 @@ class Client:
|
|
|
171
174
|
|
|
172
175
|
self.username = Prompt.ask("Enter your team's username", console=console)
|
|
173
176
|
|
|
174
|
-
if
|
|
177
|
+
if self.username != self.username.lower():
|
|
175
178
|
log.warning("Your username should most likely be lowercased.")
|
|
176
179
|
|
|
177
180
|
self.password = Prompt.ask(
|
|
178
181
|
"Enter your team's password", console=console, password=True
|
|
179
182
|
)
|
|
180
183
|
|
|
181
|
-
def _dump_credentials(self):
|
|
184
|
+
def _dump_credentials(self) -> None:
|
|
182
185
|
with open("code-battles.json", "w") as f:
|
|
183
186
|
json.dump(
|
|
184
187
|
{"url": self.url, "username": self.username, "password": self.password},
|
|
@@ -188,12 +191,12 @@ class Client:
|
|
|
188
191
|
"Credentials were dumped to `code-battles.json`. Make sure other teams don't have access to this file!"
|
|
189
192
|
)
|
|
190
193
|
|
|
191
|
-
def _get_firebase_data(self):
|
|
194
|
+
def _get_firebase_data(self) -> None:
|
|
192
195
|
configuration = requests.get(self.url + "/firebase-configuration.json").json()
|
|
193
196
|
self.firebase_api_key: str = configuration["apiKey"]
|
|
194
197
|
self.firebase_project_id: str = configuration["projectId"]
|
|
195
198
|
|
|
196
|
-
def _sign_in(self, email_domain="gmail.com"):
|
|
199
|
+
def _sign_in(self, email_domain: str = "gmail.com") -> None:
|
|
197
200
|
try:
|
|
198
201
|
response = requests.post(
|
|
199
202
|
f"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={self.firebase_api_key}",
|
|
@@ -203,27 +206,29 @@ class Client:
|
|
|
203
206
|
"returnSecureToken": True,
|
|
204
207
|
},
|
|
205
208
|
).json()
|
|
206
|
-
except Exception:
|
|
207
|
-
raise
|
|
209
|
+
except Exception as e:
|
|
210
|
+
raise RuntimeError(
|
|
208
211
|
"Sign in failed! Make sure the username and password are correct."
|
|
209
|
-
)
|
|
212
|
+
) from e
|
|
210
213
|
|
|
211
214
|
self.credentials = Credentials(response["idToken"], response["refreshToken"])
|
|
212
215
|
self.client = FirestoreClient(self.firebase_project_id, self.credentials)
|
|
213
216
|
self.document = self.client.document(f"bots/{self.username}")
|
|
214
217
|
|
|
215
|
-
def get_bots(self) ->
|
|
218
|
+
def get_bots(self) -> dict[str, str]:
|
|
216
219
|
"""Returns a mapping from a bot's name to their Python code."""
|
|
217
|
-
|
|
220
|
+
result = self.document.get().to_dict()
|
|
221
|
+
assert result is not None
|
|
222
|
+
return result
|
|
218
223
|
|
|
219
|
-
def set_bots(self, bots:
|
|
224
|
+
def set_bots(self, bots: dict[str, str], merge: bool = True) -> None:
|
|
220
225
|
"""
|
|
221
226
|
Sets the bots in the website to the specified bots.
|
|
222
227
|
Doesn't remove any bot unless ``merge`` is ``False``, in which case only bots specified in ``bots`` will remain.
|
|
223
228
|
"""
|
|
224
229
|
self.document.set(bots, merge)
|
|
225
230
|
|
|
226
|
-
def _possibly_download(self, force_download=False):
|
|
231
|
+
def _possibly_download(self, force_download: bool = False) -> str:
|
|
227
232
|
directory_name = "".join(
|
|
228
233
|
[c for c in normalize(self.url) if c.isalnum() or c == "-"]
|
|
229
234
|
)
|
|
@@ -245,14 +250,16 @@ class Client:
|
|
|
245
250
|
|
|
246
251
|
def _get_simulation_output(
|
|
247
252
|
self,
|
|
248
|
-
p: subprocess.Popen,
|
|
249
|
-
json_output=False,
|
|
250
|
-
on_step:
|
|
251
|
-
) ->
|
|
253
|
+
p: subprocess.Popen[bytes],
|
|
254
|
+
json_output: bool = False,
|
|
255
|
+
on_step: Callable[[], None] | None = None,
|
|
256
|
+
) -> SimulationResults | str:
|
|
252
257
|
while True:
|
|
253
258
|
if p.poll() is not None and p.returncode != 0:
|
|
259
|
+
assert p.stderr is not None
|
|
254
260
|
raise SimulationException(p.stderr.read(), p.returncode)
|
|
255
261
|
|
|
262
|
+
assert p.stdout is not None
|
|
256
263
|
line: bytes = p.stdout.readline()
|
|
257
264
|
line = line.strip()
|
|
258
265
|
if line == SIMULATION_FINISHED_MARK:
|
|
@@ -267,19 +274,20 @@ class Client:
|
|
|
267
274
|
if json_output:
|
|
268
275
|
return output.decode()
|
|
269
276
|
|
|
270
|
-
|
|
277
|
+
output_json = json.loads(output)
|
|
271
278
|
result = SimulationResults(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
279
|
+
output_json["winner_index"],
|
|
280
|
+
output_json["winner"],
|
|
281
|
+
output_json["steps"],
|
|
275
282
|
[
|
|
276
283
|
LogEntry(
|
|
277
284
|
entry["step"], entry["text"], entry["color"], entry["player_index"]
|
|
278
285
|
)
|
|
279
|
-
for entry in
|
|
286
|
+
for entry in output_json["logs"]
|
|
280
287
|
],
|
|
281
288
|
)
|
|
282
289
|
|
|
290
|
+
assert p.stderr is not None
|
|
283
291
|
error: bytes = p.stderr.read()
|
|
284
292
|
exit_code = p.wait()
|
|
285
293
|
if exit_code != 0:
|
|
@@ -287,17 +295,43 @@ class Client:
|
|
|
287
295
|
|
|
288
296
|
return result
|
|
289
297
|
|
|
298
|
+
@overload
|
|
299
|
+
def run_simulation(
|
|
300
|
+
self,
|
|
301
|
+
parameters: dict[str, str],
|
|
302
|
+
bot_filenames: list[str],
|
|
303
|
+
bot_names: list[str] | None = None,
|
|
304
|
+
seed: int | None = None,
|
|
305
|
+
force_download: bool = False,
|
|
306
|
+
json_output: Literal[False] = False,
|
|
307
|
+
on_step: Callable[[], None] | None = None,
|
|
308
|
+
output_file: str | None = None,
|
|
309
|
+
) -> SimulationResults: ...
|
|
310
|
+
|
|
311
|
+
@overload
|
|
312
|
+
def run_simulation(
|
|
313
|
+
self,
|
|
314
|
+
parameters: dict[str, str],
|
|
315
|
+
bot_filenames: list[str],
|
|
316
|
+
bot_names: list[str] | None = None,
|
|
317
|
+
seed: int | None = None,
|
|
318
|
+
force_download: bool = False,
|
|
319
|
+
json_output: Literal[True] = True,
|
|
320
|
+
on_step: Callable[[], None] | None = None,
|
|
321
|
+
output_file: str | None = None,
|
|
322
|
+
) -> str: ...
|
|
323
|
+
|
|
290
324
|
def run_simulation(
|
|
291
325
|
self,
|
|
292
|
-
parameters:
|
|
293
|
-
bot_filenames:
|
|
294
|
-
bot_names:
|
|
295
|
-
seed:
|
|
296
|
-
force_download=False,
|
|
297
|
-
json_output=False,
|
|
298
|
-
on_step:
|
|
299
|
-
output_file:
|
|
300
|
-
) ->
|
|
326
|
+
parameters: dict[str, str],
|
|
327
|
+
bot_filenames: list[str],
|
|
328
|
+
bot_names: list[str] | None = None,
|
|
329
|
+
seed: int | None = None,
|
|
330
|
+
force_download: bool = False,
|
|
331
|
+
json_output: bool = False,
|
|
332
|
+
on_step: Callable[[], None] | None = None,
|
|
333
|
+
output_file: str | None = None,
|
|
334
|
+
) -> SimulationResults | str:
|
|
301
335
|
"""
|
|
302
336
|
Runs the given simulation without UI locally.
|
|
303
337
|
If ``bot_names`` is not specified, they will be the filenames without the extension.
|
|
@@ -340,10 +374,10 @@ class Client:
|
|
|
340
374
|
def run_simulation_from_file(
|
|
341
375
|
self,
|
|
342
376
|
simulation_file: str,
|
|
343
|
-
force_download=False,
|
|
344
|
-
json_output=False,
|
|
345
|
-
on_step:
|
|
346
|
-
) ->
|
|
377
|
+
force_download: bool = False,
|
|
378
|
+
json_output: bool = False,
|
|
379
|
+
on_step: Callable[[], None] | None = None,
|
|
380
|
+
) -> SimulationResults | str:
|
|
347
381
|
"""
|
|
348
382
|
Runs the given simulation without UI locally from the given simulation file.
|
|
349
383
|
|
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
|
|
3
1
|
import click
|
|
4
2
|
|
|
5
3
|
|
|
6
4
|
@click.group()
|
|
7
5
|
@click.version_option(message="%(prog)s %(version)s")
|
|
8
|
-
def cli():
|
|
6
|
+
def cli() -> None:
|
|
9
7
|
pass
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
def main() -> int:
|
|
13
|
-
from code_battles_cli.log import setup_logging
|
|
11
|
+
from code_battles_cli.log import setup_logging
|
|
14
12
|
|
|
15
13
|
setup_logging()
|
|
16
14
|
|
|
17
15
|
from code_battles_cli.commands.download import download
|
|
18
|
-
from code_battles_cli.commands.upload import upload
|
|
19
16
|
from code_battles_cli.commands.run import run
|
|
17
|
+
from code_battles_cli.commands.upload import upload
|
|
20
18
|
|
|
21
19
|
cli.add_command(download)
|
|
22
20
|
cli.add_command(upload)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import os
|
|
2
|
-
from typing import Optional
|
|
3
4
|
|
|
4
5
|
import click
|
|
5
6
|
|
|
@@ -35,18 +36,18 @@ from code_battles_cli.log import log
|
|
|
35
36
|
help="The directory to download the bots into",
|
|
36
37
|
)
|
|
37
38
|
def download(
|
|
38
|
-
url:
|
|
39
|
-
username:
|
|
40
|
-
password:
|
|
39
|
+
url: str | None,
|
|
40
|
+
username: str | None,
|
|
41
|
+
password: str | None,
|
|
41
42
|
dump_credentials: bool,
|
|
42
|
-
bot:
|
|
43
|
+
bot: str | None,
|
|
43
44
|
d: str,
|
|
44
|
-
):
|
|
45
|
+
) -> None:
|
|
45
46
|
client = Client(url, username, password, dump_credentials)
|
|
46
47
|
bots = client.get_bots()
|
|
47
48
|
if bot is not None:
|
|
48
49
|
if bot not in bots:
|
|
49
|
-
raise
|
|
50
|
+
raise KeyError(bot)
|
|
50
51
|
bots = {bot: bots[bot]}
|
|
51
52
|
|
|
52
53
|
if not os.path.exists(d):
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
4
|
import logging
|
|
3
|
-
import sys
|
|
4
5
|
import time
|
|
5
|
-
from typing import List, Optional, Tuple
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
8
|
|
|
@@ -50,17 +50,17 @@ from code_battles_cli.log import log, progress
|
|
|
50
50
|
help="Output file to dump the simulation into",
|
|
51
51
|
)
|
|
52
52
|
def run(
|
|
53
|
-
url:
|
|
54
|
-
username:
|
|
55
|
-
password:
|
|
53
|
+
url: str | None,
|
|
54
|
+
username: str | None,
|
|
55
|
+
password: str | None,
|
|
56
56
|
dump_credentials: bool,
|
|
57
|
-
parameters:
|
|
58
|
-
bots:
|
|
57
|
+
parameters: str | None,
|
|
58
|
+
bots: tuple[str],
|
|
59
59
|
force_download: bool,
|
|
60
|
-
seed:
|
|
61
|
-
simulation_file:
|
|
62
|
-
output_file:
|
|
63
|
-
):
|
|
60
|
+
seed: int | None,
|
|
61
|
+
simulation_file: str | None,
|
|
62
|
+
output_file: str | None,
|
|
63
|
+
) -> None:
|
|
64
64
|
logging.getLogger("rich").setLevel(logging.WARNING)
|
|
65
65
|
client = Client(url, username, password, dump_credentials)
|
|
66
66
|
client._possibly_download(force_download)
|
|
@@ -69,7 +69,7 @@ def run(
|
|
|
69
69
|
with progress:
|
|
70
70
|
progress_id = progress.add_task("[green]Simulating...", total=None)
|
|
71
71
|
|
|
72
|
-
def on_step():
|
|
72
|
+
def on_step() -> None:
|
|
73
73
|
progress.update(progress_id, advance=1)
|
|
74
74
|
|
|
75
75
|
if simulation_file is not None:
|
|
@@ -103,6 +103,7 @@ def run(
|
|
|
103
103
|
else:
|
|
104
104
|
assert map is not None, "Map is required for simulation!"
|
|
105
105
|
assert len(bots) > 0, "Bots are required for simulation!"
|
|
106
|
+
assert parameters is not None, "Parameters are required for simulation!"
|
|
106
107
|
|
|
107
108
|
results = client.run_simulation(
|
|
108
109
|
json.loads(parameters),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import os
|
|
2
|
-
from typing import Optional
|
|
3
4
|
|
|
4
5
|
import click
|
|
5
6
|
|
|
@@ -33,12 +34,12 @@ from code_battles_cli.log import log
|
|
|
33
34
|
)
|
|
34
35
|
def upload(
|
|
35
36
|
file: str,
|
|
36
|
-
name:
|
|
37
|
-
url:
|
|
38
|
-
username:
|
|
39
|
-
password:
|
|
37
|
+
name: str | None,
|
|
38
|
+
url: str | None,
|
|
39
|
+
username: str | None,
|
|
40
|
+
password: str | None,
|
|
40
41
|
dump_credentials: bool,
|
|
41
|
-
):
|
|
42
|
+
) -> None:
|
|
42
43
|
bot_name = name if name is not None else os.path.splitext(os.path.basename(file))[0]
|
|
43
44
|
with open(file, "r") as f:
|
|
44
45
|
bot_code = f.read()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import logging
|
|
2
|
+
import os
|
|
3
3
|
|
|
4
4
|
from rich.console import Console
|
|
5
|
+
from rich.logging import RichHandler
|
|
5
6
|
from rich.progress import BarColumn, MofNCompleteColumn, Progress, TextColumn
|
|
6
7
|
from rich.table import Column
|
|
7
8
|
from rich.traceback import install
|
|
8
|
-
from rich.logging import RichHandler
|
|
9
9
|
|
|
10
10
|
log = logging.getLogger("rich")
|
|
11
11
|
console = Console()
|
|
@@ -22,7 +22,7 @@ progress = Progress(
|
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def setup_logging():
|
|
25
|
+
def setup_logging() -> None:
|
|
26
26
|
FORMAT = "%(message)s"
|
|
27
27
|
|
|
28
28
|
logging.basicConfig(
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
name: Test
|
|
2
|
-
on: [push]
|
|
3
|
-
jobs:
|
|
4
|
-
test:
|
|
5
|
-
name: Test
|
|
6
|
-
strategy:
|
|
7
|
-
matrix:
|
|
8
|
-
version: ["3.8", "3.12"]
|
|
9
|
-
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
10
|
-
runs-on: ${{ matrix.os }}
|
|
11
|
-
steps:
|
|
12
|
-
- name: Clone the repository
|
|
13
|
-
uses: actions/checkout@v4
|
|
14
|
-
- name: Install the latest version of rye
|
|
15
|
-
uses: eifinger/setup-rye@v4
|
|
16
|
-
- name: Pin Python version
|
|
17
|
-
run: rye pin ${{ matrix.version }}
|
|
18
|
-
- name: Sync rye
|
|
19
|
-
run: rye sync
|
|
20
|
-
- name: Run tests
|
|
21
|
-
run: rye test
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
3.8
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
Copyright 2024 Noam Zaks and Code Battles Contributors
|
|
2
|
-
|
|
3
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
-
|
|
5
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
-
|
|
7
|
-
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# generated by rye
|
|
2
|
-
# use `rye lock` or `rye sync` to update this lockfile
|
|
3
|
-
#
|
|
4
|
-
# last locked with the following flags:
|
|
5
|
-
# pre: false
|
|
6
|
-
# features: []
|
|
7
|
-
# all-features: false
|
|
8
|
-
# with-sources: false
|
|
9
|
-
# generate-hashes: false
|
|
10
|
-
# universal: false
|
|
11
|
-
|
|
12
|
-
-e file:.
|
|
13
|
-
cachetools==5.5.0
|
|
14
|
-
# via google-auth
|
|
15
|
-
certifi==2024.8.30
|
|
16
|
-
# via requests
|
|
17
|
-
charset-normalizer==3.4.0
|
|
18
|
-
# via requests
|
|
19
|
-
click==8.1.7
|
|
20
|
-
# via code-battles-cli
|
|
21
|
-
exceptiongroup==1.3.0
|
|
22
|
-
# via pytest
|
|
23
|
-
google-api-core==2.21.0
|
|
24
|
-
# via google-cloud-core
|
|
25
|
-
# via google-cloud-firestore
|
|
26
|
-
google-auth==2.35.0
|
|
27
|
-
# via google-api-core
|
|
28
|
-
# via google-cloud-core
|
|
29
|
-
# via google-cloud-firestore
|
|
30
|
-
google-cloud-core==2.4.1
|
|
31
|
-
# via google-cloud-firestore
|
|
32
|
-
google-cloud-firestore==2.19.0
|
|
33
|
-
# via code-battles-cli
|
|
34
|
-
googleapis-common-protos==1.65.0
|
|
35
|
-
# via google-api-core
|
|
36
|
-
# via grpcio-status
|
|
37
|
-
grpcio==1.67.0
|
|
38
|
-
# via google-api-core
|
|
39
|
-
# via grpcio-status
|
|
40
|
-
grpcio-status==1.67.0
|
|
41
|
-
# via google-api-core
|
|
42
|
-
idna==3.10
|
|
43
|
-
# via requests
|
|
44
|
-
iniconfig==2.1.0
|
|
45
|
-
# via pytest
|
|
46
|
-
markdown-it-py==3.0.0
|
|
47
|
-
# via rich
|
|
48
|
-
mdurl==0.1.2
|
|
49
|
-
# via markdown-it-py
|
|
50
|
-
packaging==25.0
|
|
51
|
-
# via pytest
|
|
52
|
-
pluggy==1.5.0
|
|
53
|
-
# via pytest
|
|
54
|
-
proto-plus==1.24.0
|
|
55
|
-
# via google-api-core
|
|
56
|
-
# via google-cloud-firestore
|
|
57
|
-
protobuf==5.28.2
|
|
58
|
-
# via google-api-core
|
|
59
|
-
# via google-cloud-firestore
|
|
60
|
-
# via googleapis-common-protos
|
|
61
|
-
# via grpcio-status
|
|
62
|
-
# via proto-plus
|
|
63
|
-
pyasn1==0.6.1
|
|
64
|
-
# via pyasn1-modules
|
|
65
|
-
# via rsa
|
|
66
|
-
pyasn1-modules==0.4.1
|
|
67
|
-
# via google-auth
|
|
68
|
-
pygments==2.18.0
|
|
69
|
-
# via rich
|
|
70
|
-
pytest==8.3.5
|
|
71
|
-
requests==2.32.3
|
|
72
|
-
# via code-battles-cli
|
|
73
|
-
# via google-api-core
|
|
74
|
-
rich==13.9.3
|
|
75
|
-
# via code-battles-cli
|
|
76
|
-
rsa==4.9
|
|
77
|
-
# via google-auth
|
|
78
|
-
tomli==2.2.1
|
|
79
|
-
# via pytest
|
|
80
|
-
typing-extensions==4.13.2
|
|
81
|
-
# via exceptiongroup
|
|
82
|
-
# via rich
|
|
83
|
-
urllib3==2.2.3
|
|
84
|
-
# via requests
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# generated by rye
|
|
2
|
-
# use `rye lock` or `rye sync` to update this lockfile
|
|
3
|
-
#
|
|
4
|
-
# last locked with the following flags:
|
|
5
|
-
# pre: false
|
|
6
|
-
# features: []
|
|
7
|
-
# all-features: false
|
|
8
|
-
# with-sources: false
|
|
9
|
-
# generate-hashes: false
|
|
10
|
-
# universal: false
|
|
11
|
-
|
|
12
|
-
-e file:.
|
|
13
|
-
cachetools==5.5.0
|
|
14
|
-
# via google-auth
|
|
15
|
-
certifi==2024.8.30
|
|
16
|
-
# via requests
|
|
17
|
-
charset-normalizer==3.4.0
|
|
18
|
-
# via requests
|
|
19
|
-
click==8.1.7
|
|
20
|
-
# via code-battles-cli
|
|
21
|
-
google-api-core==2.21.0
|
|
22
|
-
# via google-cloud-core
|
|
23
|
-
# via google-cloud-firestore
|
|
24
|
-
google-auth==2.35.0
|
|
25
|
-
# via google-api-core
|
|
26
|
-
# via google-cloud-core
|
|
27
|
-
# via google-cloud-firestore
|
|
28
|
-
google-cloud-core==2.4.1
|
|
29
|
-
# via google-cloud-firestore
|
|
30
|
-
google-cloud-firestore==2.19.0
|
|
31
|
-
# via code-battles-cli
|
|
32
|
-
googleapis-common-protos==1.65.0
|
|
33
|
-
# via google-api-core
|
|
34
|
-
# via grpcio-status
|
|
35
|
-
grpcio==1.67.0
|
|
36
|
-
# via google-api-core
|
|
37
|
-
# via grpcio-status
|
|
38
|
-
grpcio-status==1.67.0
|
|
39
|
-
# via google-api-core
|
|
40
|
-
idna==3.10
|
|
41
|
-
# via requests
|
|
42
|
-
markdown-it-py==3.0.0
|
|
43
|
-
# via rich
|
|
44
|
-
mdurl==0.1.2
|
|
45
|
-
# via markdown-it-py
|
|
46
|
-
proto-plus==1.24.0
|
|
47
|
-
# via google-api-core
|
|
48
|
-
# via google-cloud-firestore
|
|
49
|
-
protobuf==5.28.2
|
|
50
|
-
# via google-api-core
|
|
51
|
-
# via google-cloud-firestore
|
|
52
|
-
# via googleapis-common-protos
|
|
53
|
-
# via grpcio-status
|
|
54
|
-
# via proto-plus
|
|
55
|
-
pyasn1==0.6.1
|
|
56
|
-
# via pyasn1-modules
|
|
57
|
-
# via rsa
|
|
58
|
-
pyasn1-modules==0.4.1
|
|
59
|
-
# via google-auth
|
|
60
|
-
pygments==2.18.0
|
|
61
|
-
# via rich
|
|
62
|
-
requests==2.32.3
|
|
63
|
-
# via code-battles-cli
|
|
64
|
-
# via google-api-core
|
|
65
|
-
rich==13.9.3
|
|
66
|
-
# via code-battles-cli
|
|
67
|
-
rsa==4.9
|
|
68
|
-
# via google-auth
|
|
69
|
-
typing-extensions==4.13.2
|
|
70
|
-
# via rich
|
|
71
|
-
urllib3==2.2.3
|
|
72
|
-
# via requests
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from code_battles_cli.cli import main
|
|
File without changes
|