codeapi-client 0.4.1__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.
- codeapi/__init__.py +17 -0
- codeapi/client/__init__.py +37 -0
- codeapi/client/_async/__init__.py +357 -0
- codeapi/client/_async/_app.py +369 -0
- codeapi/client/_async/_cli.py +334 -0
- codeapi/client/_async/_code.py +211 -0
- codeapi/client/_async/_jobs.py +512 -0
- codeapi/client/_async/_mcp.py +445 -0
- codeapi/client/_async/_web.py +343 -0
- codeapi/client/_base.py +118 -0
- codeapi/client/_sync/__init__.py +347 -0
- codeapi/client/_sync/_app.py +367 -0
- codeapi/client/_sync/_cli.py +332 -0
- codeapi/client/_sync/_code.py +203 -0
- codeapi/client/_sync/_jobs.py +497 -0
- codeapi/client/_sync/_mcp.py +442 -0
- codeapi/client/_sync/_web.py +341 -0
- codeapi/client/_utils.py +61 -0
- codeapi/client/py.typed +0 -0
- codeapi/types/__init__.py +77 -0
- codeapi/types/_api.py +30 -0
- codeapi/types/_base.py +21 -0
- codeapi/types/_code.py +31 -0
- codeapi/types/_enums.py +150 -0
- codeapi/types/_env.py +65 -0
- codeapi/types/_exc.py +35 -0
- codeapi/types/_job.py +67 -0
- codeapi/types/_json.py +67 -0
- codeapi/types/_stream.py +36 -0
- codeapi/types/_swarm.py +85 -0
- codeapi/types/_time.py +46 -0
- codeapi/types/_zips.py +466 -0
- codeapi/types/py.typed +0 -0
- codeapi_client-0.4.1.dist-info/METADATA +14 -0
- codeapi_client-0.4.1.dist-info/RECORD +36 -0
- codeapi_client-0.4.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, List
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from . import Client
|
|
9
|
+
from codeapi.types import (
|
|
10
|
+
CodeInfo,
|
|
11
|
+
CodeType,
|
|
12
|
+
CodeZip,
|
|
13
|
+
Job,
|
|
14
|
+
JobStage,
|
|
15
|
+
JobStatus,
|
|
16
|
+
JobType,
|
|
17
|
+
JsonData,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StoredWebClient:
|
|
22
|
+
def __init__(self, client: Client):
|
|
23
|
+
self._client = client
|
|
24
|
+
|
|
25
|
+
def run(
|
|
26
|
+
self,
|
|
27
|
+
code_id: str,
|
|
28
|
+
) -> Job:
|
|
29
|
+
"""Runs a stored Web page.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
code_id (str): The code ID.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Job: The created job.
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
HTTPException: If the request fails.
|
|
39
|
+
"""
|
|
40
|
+
url = f"{self._client.base_url}/jobs/code/{code_id}/run/web"
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
response = httpx.post(
|
|
44
|
+
url,
|
|
45
|
+
headers=self._client.api_key_header,
|
|
46
|
+
)
|
|
47
|
+
response.raise_for_status()
|
|
48
|
+
return Job(**response.json())
|
|
49
|
+
except httpx.HTTPStatusError as e:
|
|
50
|
+
raise self._client._get_http_exception(httpx_error=e)
|
|
51
|
+
|
|
52
|
+
def list_info(self) -> list[CodeInfo]:
|
|
53
|
+
"""List all stored Web code.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
list[CodeInfo]: List of Web code info.
|
|
57
|
+
"""
|
|
58
|
+
return self._client.code.list_info(code_type=CodeType.WEB)
|
|
59
|
+
|
|
60
|
+
def delete(self, code_id: str) -> str:
|
|
61
|
+
"""Delete stored Web code.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
code_id (str): The code ID to delete.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
str: Deletion confirmation message.
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
ValueError: If the code_id is not Web code.
|
|
71
|
+
"""
|
|
72
|
+
# Verify this is actually Web code
|
|
73
|
+
code_info = self._client.code.get_info(code_id)
|
|
74
|
+
if code_info.code_type != CodeType.WEB:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Code '{code_id}' is {code_info.code_type}, not Web code. "
|
|
77
|
+
"Cannot delete non-Web code from Web client."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return self._client.code.delete(code_id)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class WebJobsClient:
|
|
84
|
+
def __init__(self, client):
|
|
85
|
+
self._client = client
|
|
86
|
+
|
|
87
|
+
def list(
|
|
88
|
+
self,
|
|
89
|
+
job_status: JobStatus | None = None,
|
|
90
|
+
job_stage: JobStage | None = None,
|
|
91
|
+
) -> List[Job]:
|
|
92
|
+
"""List Web jobs.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
job_status (JobStatus | None): Filter by job status.
|
|
96
|
+
job_stage (JobStage | None): Filter by job stage.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
list[Job]: List of Web jobs.
|
|
100
|
+
"""
|
|
101
|
+
return self._client.jobs.list(
|
|
102
|
+
job_type=JobType.RUN_WEB,
|
|
103
|
+
job_status=job_status,
|
|
104
|
+
job_stage=job_stage,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def get_latest(self) -> Job | None:
|
|
108
|
+
"""Get the most recent Web job.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Job | None: The most recent Web job, or None if no jobs exist.
|
|
112
|
+
"""
|
|
113
|
+
jobs = self.list()
|
|
114
|
+
return jobs[0] if jobs else None
|
|
115
|
+
|
|
116
|
+
def list_queued(self) -> List[Job]:
|
|
117
|
+
"""Get all queued Web jobs.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
List[Job]: List of queued Web jobs.
|
|
121
|
+
"""
|
|
122
|
+
return self.list(job_status=JobStatus.QUEUED)
|
|
123
|
+
|
|
124
|
+
def list_scheduled(self) -> List[Job]:
|
|
125
|
+
"""Get all scheduled Web jobs.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
List[Job]: List of scheduled Web jobs.
|
|
129
|
+
"""
|
|
130
|
+
return self.list(job_status=JobStatus.SCHEDULED)
|
|
131
|
+
|
|
132
|
+
def list_started(self) -> List[Job]:
|
|
133
|
+
"""Get all started Web jobs.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
list[Job]: List of started Web jobs.
|
|
137
|
+
"""
|
|
138
|
+
return self.list(job_status=JobStatus.STARTED)
|
|
139
|
+
|
|
140
|
+
def list_deferred(self) -> List[Job]:
|
|
141
|
+
"""Get all deferred Web jobs.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
List[Job]: List of deferred Web jobs.
|
|
145
|
+
"""
|
|
146
|
+
return self.list(job_status=JobStatus.DEFERRED)
|
|
147
|
+
|
|
148
|
+
def list_canceled(self) -> List[Job]:
|
|
149
|
+
"""Get all canceled Web jobs.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
List[Job]: List of canceled Web jobs.
|
|
153
|
+
"""
|
|
154
|
+
return self.list(job_status=JobStatus.CANCELED)
|
|
155
|
+
|
|
156
|
+
def list_stopped(self) -> List[Job]:
|
|
157
|
+
"""Get all stopped Web jobs.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List[Job]: List of stopped Web jobs.
|
|
161
|
+
"""
|
|
162
|
+
return self.list(job_status=JobStatus.STOPPED)
|
|
163
|
+
|
|
164
|
+
def list_failed(self) -> List[Job]:
|
|
165
|
+
"""Get all failed Web jobs.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
list[Job]: List of failed Web jobs.
|
|
169
|
+
"""
|
|
170
|
+
return self.list(job_status=JobStatus.FAILED)
|
|
171
|
+
|
|
172
|
+
def list_finished(self) -> List[Job]:
|
|
173
|
+
"""Get all finished Web jobs.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
list[Job]: List of finished Web jobs.
|
|
177
|
+
"""
|
|
178
|
+
return self.list(job_status=JobStatus.FINISHED)
|
|
179
|
+
|
|
180
|
+
def list_timed_out(self) -> List[Job]:
|
|
181
|
+
"""Get all timed out Web jobs.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
List[Job]: List of timed out Web jobs.
|
|
185
|
+
"""
|
|
186
|
+
return self.list(job_status=JobStatus.TIMEOUT)
|
|
187
|
+
|
|
188
|
+
def list_pre_running(self) -> List[Job]:
|
|
189
|
+
"""Get all pre-running Web jobs.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
List[Job]: List of pre-running Web jobs.
|
|
193
|
+
"""
|
|
194
|
+
return self.list(job_stage=JobStage.PRE_RUNNING)
|
|
195
|
+
|
|
196
|
+
def list_running(self) -> List[Job]:
|
|
197
|
+
"""Get all running Web jobs.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
List[Job]: List of running Web jobs.
|
|
201
|
+
"""
|
|
202
|
+
return self.list(job_stage=JobStage.RUNNING)
|
|
203
|
+
|
|
204
|
+
def list_post_running(self) -> List[Job]:
|
|
205
|
+
"""Get all post-running Web jobs.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
List[Job]: List of post-running Web jobs.
|
|
209
|
+
"""
|
|
210
|
+
return self.list(job_stage=JobStage.POST_RUNNING)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class WebClient:
|
|
214
|
+
def __init__(self, client: Client):
|
|
215
|
+
self._client = client
|
|
216
|
+
self.stored = StoredWebClient(client)
|
|
217
|
+
self.jobs = WebJobsClient(client)
|
|
218
|
+
|
|
219
|
+
def run(
|
|
220
|
+
self,
|
|
221
|
+
code_zip: CodeZip,
|
|
222
|
+
) -> Job:
|
|
223
|
+
"""Runs a Web page from code zip.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
code_zip (CodeZip): The code zip.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Job: The created job.
|
|
230
|
+
|
|
231
|
+
Raises:
|
|
232
|
+
HTTPException: If the request fails.
|
|
233
|
+
"""
|
|
234
|
+
url = f"{self._client.base_url}/jobs/code/run/web"
|
|
235
|
+
|
|
236
|
+
files = self._client._prepare_files(code_zip=code_zip)
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
response = httpx.post(
|
|
240
|
+
url,
|
|
241
|
+
headers=self._client.api_key_header,
|
|
242
|
+
files=files,
|
|
243
|
+
)
|
|
244
|
+
response.raise_for_status()
|
|
245
|
+
return Job(**response.json())
|
|
246
|
+
except httpx.HTTPStatusError as e:
|
|
247
|
+
raise self._client._get_http_exception(httpx_error=e)
|
|
248
|
+
|
|
249
|
+
def get_url(self, job_id: str) -> str:
|
|
250
|
+
"""Gets the URL for a Web page.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
job_id (str): The job ID.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
str: The Web page URL.
|
|
257
|
+
"""
|
|
258
|
+
return self._client.get_subdomain_proxy_url(job_id)
|
|
259
|
+
|
|
260
|
+
def upload(
|
|
261
|
+
self,
|
|
262
|
+
code_zip: CodeZip,
|
|
263
|
+
code_name: str,
|
|
264
|
+
metadata: JsonData | dict | None = None,
|
|
265
|
+
) -> str:
|
|
266
|
+
"""Upload Web code.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
code_zip (CodeZip): The code zip.
|
|
270
|
+
code_name (str): The name of the code.
|
|
271
|
+
metadata (JsonData | dict | None): The JSON metadata of the code.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
str: The code ID.
|
|
275
|
+
"""
|
|
276
|
+
return self._client.code.upload(
|
|
277
|
+
code_zip=code_zip,
|
|
278
|
+
code_name=code_name,
|
|
279
|
+
code_type=CodeType.WEB,
|
|
280
|
+
metadata=metadata,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def is_healthy(self, job_id: str) -> bool:
|
|
284
|
+
"""Checks whether launched Web page is healthy.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
job_id (str): The ID of the Web page launch job.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
bool: True if Web page is healthy else False.
|
|
291
|
+
|
|
292
|
+
Raises:
|
|
293
|
+
HTTPException: If the request fails.
|
|
294
|
+
"""
|
|
295
|
+
return self._client.jobs.is_healthy(job_id=job_id)
|
|
296
|
+
|
|
297
|
+
def await_healthy(self, job_id: str, timeout: float | None = None) -> Job:
|
|
298
|
+
"""Waits for a Web page to become healthy.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
job_id (str): The ID of the Web page run job.
|
|
302
|
+
timeout (float | None): Maximum time to wait in seconds. If None, waits indefinitely.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
Job: The job object of the Web page run job.
|
|
306
|
+
|
|
307
|
+
Raises:
|
|
308
|
+
HTTPException: If the request fails.
|
|
309
|
+
APIException: If the job enters stage POST_RUNNING unexpectedly.
|
|
310
|
+
TimeoutError: If the timeout is exceeded.
|
|
311
|
+
"""
|
|
312
|
+
return self._client.jobs.await_healthy(job_id=job_id, timeout=timeout)
|
|
313
|
+
|
|
314
|
+
def ingest(
|
|
315
|
+
self,
|
|
316
|
+
code_zip: CodeZip,
|
|
317
|
+
code_name: str,
|
|
318
|
+
metadata: JsonData | dict | None = None,
|
|
319
|
+
build_pexenv: bool = False,
|
|
320
|
+
pexenv_python: str | None = None,
|
|
321
|
+
) -> Job:
|
|
322
|
+
"""Ingest Web code.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
code_zip (CodeZip): The code zip.
|
|
326
|
+
code_name (str): The name of the code.
|
|
327
|
+
metadata (JsonData | dict | None): The JSON metadata of the code.
|
|
328
|
+
build_pexenv (bool): Whether to build the pex venv.
|
|
329
|
+
pexenv_python: (str | None): Python interpreter for the pex venv.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Job: The code ingestion job.
|
|
333
|
+
"""
|
|
334
|
+
return self._client.code.ingest(
|
|
335
|
+
code_zip=code_zip,
|
|
336
|
+
code_name=code_name,
|
|
337
|
+
code_type=CodeType.WEB,
|
|
338
|
+
metadata=metadata,
|
|
339
|
+
build_pexenv=build_pexenv,
|
|
340
|
+
pexenv_python=pexenv_python,
|
|
341
|
+
)
|
codeapi/client/_utils.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import aiofiles
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_file_dir(filepath: Path | str) -> str:
|
|
8
|
+
return str(Path(filepath).parent)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_filename(filepath: Path | str) -> str:
|
|
12
|
+
return Path(filepath).name
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def file_exists(path: Path | str) -> bool:
|
|
16
|
+
return os.path.isfile(path)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def read_file(path: Path | str) -> str:
|
|
20
|
+
return Path(path).read_text()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def read_file_async(path: Path | str) -> str:
|
|
24
|
+
async with aiofiles.open(path, mode="r") as f:
|
|
25
|
+
return await f.read()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def read_bytes(path: Path | str) -> bytes:
|
|
29
|
+
return Path(path).read_bytes()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def read_bytes_async(path: Path | str) -> bytes:
|
|
33
|
+
async with aiofiles.open(path, mode="rb") as f:
|
|
34
|
+
return await f.read()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def read_lines(path: Path | str) -> list[str]:
|
|
38
|
+
return read_file(path=path).splitlines()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def read_lines_async(path: Path | str) -> list[str]:
|
|
42
|
+
content = await read_file_async(path)
|
|
43
|
+
return content.splitlines()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def write_file(path: Path | str, data: str) -> None:
|
|
47
|
+
Path(path).write_text(data=data)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def write_file_async(path: Path | str, data: str) -> None:
|
|
51
|
+
async with aiofiles.open(path, "w") as f:
|
|
52
|
+
await f.write(data)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def write_bytes(path: Path | str, data: bytes) -> None:
|
|
56
|
+
Path(path).write_bytes(data=data)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def write_bytes_async(path: Path | str, data: bytes) -> None:
|
|
60
|
+
async with aiofiles.open(path, "wb") as f:
|
|
61
|
+
await f.write(data)
|
codeapi/client/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from ._api import (
|
|
2
|
+
API_KEY_HEADER,
|
|
3
|
+
ServerStatus,
|
|
4
|
+
ServicesHealth,
|
|
5
|
+
TokenRequest,
|
|
6
|
+
TokenResponse,
|
|
7
|
+
)
|
|
8
|
+
from ._base import CustomBaseModel
|
|
9
|
+
from ._code import CodeInfo
|
|
10
|
+
from ._enums import (
|
|
11
|
+
CodeAPIError,
|
|
12
|
+
CodeType,
|
|
13
|
+
ExitType,
|
|
14
|
+
JobStage,
|
|
15
|
+
JobStatus,
|
|
16
|
+
JobType,
|
|
17
|
+
WorkerDeployment,
|
|
18
|
+
)
|
|
19
|
+
from ._env import EnvVars
|
|
20
|
+
from ._exc import CodeAPIException
|
|
21
|
+
from ._job import Job
|
|
22
|
+
from ._json import JsonData
|
|
23
|
+
from ._stream import StreamOutput
|
|
24
|
+
from ._swarm import HiveHeartbeat, SwarmStats
|
|
25
|
+
from ._time import T_HOURS, T_MINUTES, T_SECONDS
|
|
26
|
+
from ._zips import (
|
|
27
|
+
DOTENV_FILE,
|
|
28
|
+
EMPTY_ZIP_BYTES,
|
|
29
|
+
PEXENV_CMD,
|
|
30
|
+
PEXENV_FILE,
|
|
31
|
+
REQUIREMENTS_TXT,
|
|
32
|
+
CodeZip,
|
|
33
|
+
DataZip,
|
|
34
|
+
JobResult,
|
|
35
|
+
ZipBytes,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"API_KEY_HEADER",
|
|
40
|
+
"CodeAPIError",
|
|
41
|
+
"CodeAPIException",
|
|
42
|
+
"DOTENV_FILE",
|
|
43
|
+
"EMPTY_ZIP_BYTES",
|
|
44
|
+
"PEXENV_CMD",
|
|
45
|
+
"PEXENV_FILE",
|
|
46
|
+
"REQUIREMENTS_TXT",
|
|
47
|
+
"CodeInfo",
|
|
48
|
+
"CodeType",
|
|
49
|
+
"CodeZip",
|
|
50
|
+
"CustomBaseModel",
|
|
51
|
+
"DataZip",
|
|
52
|
+
"EnvVars",
|
|
53
|
+
"ExitType",
|
|
54
|
+
"HiveHeartbeat",
|
|
55
|
+
"SwarmStats",
|
|
56
|
+
"Job",
|
|
57
|
+
"JobResult",
|
|
58
|
+
"JobStage",
|
|
59
|
+
"JobStatus",
|
|
60
|
+
"JobType",
|
|
61
|
+
"JsonData",
|
|
62
|
+
"ServerStatus",
|
|
63
|
+
"ServicesHealth",
|
|
64
|
+
"StreamOutput",
|
|
65
|
+
"T_HOURS",
|
|
66
|
+
"T_MINUTES",
|
|
67
|
+
"T_SECONDS",
|
|
68
|
+
"TokenRequest",
|
|
69
|
+
"TokenResponse",
|
|
70
|
+
"WorkerDeployment",
|
|
71
|
+
"ZipBytes",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
for __name in __all__:
|
|
76
|
+
if not __name.startswith("__") and hasattr(locals()[__name], "__module__"):
|
|
77
|
+
setattr(locals()[__name], "__module__", __name__)
|
codeapi/types/_api.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
API_KEY_HEADER = "CodeAPI-Key"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TokenRequest(BaseModel):
|
|
7
|
+
user: str
|
|
8
|
+
ulid: str | None = None
|
|
9
|
+
ttl: int = -1 # [s]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TokenResponse(BaseModel):
|
|
13
|
+
token: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ServerStatus(BaseModel):
|
|
17
|
+
is_healthy: bool
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ServicesHealth(BaseModel):
|
|
21
|
+
redis_healthy: bool
|
|
22
|
+
storage_healthy: bool
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def all_healthy(self) -> bool:
|
|
26
|
+
return all(
|
|
27
|
+
getattr(self, attr)
|
|
28
|
+
for attr in self.__dict__
|
|
29
|
+
if isinstance(getattr(self, attr), bool)
|
|
30
|
+
)
|
codeapi/types/_base.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from strenum import StrEnum
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CustomBaseModel(BaseModel):
|
|
6
|
+
def __str__(self):
|
|
7
|
+
fields = self.__class__.__pydantic_fields__
|
|
8
|
+
field_values = ", ".join(
|
|
9
|
+
f"{k}={repr(v)}" for k, v in self.model_dump().items() if fields[k].repr
|
|
10
|
+
)
|
|
11
|
+
return f"{self.__class__.__name__}({field_values})"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CaseInsensitiveStrEnum(StrEnum):
|
|
15
|
+
@classmethod
|
|
16
|
+
def _missing_(cls, value):
|
|
17
|
+
if isinstance(value, str):
|
|
18
|
+
value = value.upper()
|
|
19
|
+
if value in cls._value2member_map_:
|
|
20
|
+
return cls._value2member_map_[value]
|
|
21
|
+
return None
|
codeapi/types/_code.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from pydantic import BaseModel, ConfigDict, field_serializer, field_validator
|
|
2
|
+
|
|
3
|
+
from ._enums import CodeType
|
|
4
|
+
from ._json import JsonData
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CodeInfo(BaseModel):
|
|
8
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
9
|
+
|
|
10
|
+
code_id: str
|
|
11
|
+
code_name: str
|
|
12
|
+
code_type: CodeType
|
|
13
|
+
metadata: JsonData
|
|
14
|
+
|
|
15
|
+
@field_serializer("metadata")
|
|
16
|
+
def serialize_metadata(self, value: JsonData) -> dict:
|
|
17
|
+
return dict(value)
|
|
18
|
+
|
|
19
|
+
@field_validator("metadata", mode="before")
|
|
20
|
+
@classmethod
|
|
21
|
+
def deserialize_metadata(cls, value: JsonData | dict) -> JsonData:
|
|
22
|
+
if isinstance(value, JsonData):
|
|
23
|
+
return value
|
|
24
|
+
return JsonData(value)
|
|
25
|
+
|
|
26
|
+
def __str__(self):
|
|
27
|
+
fields = self.__class__.__pydantic_fields__
|
|
28
|
+
field_values = ", ".join(
|
|
29
|
+
f"{k}={repr(v)}" for k, v in self.model_dump().items() if fields[k].repr
|
|
30
|
+
)
|
|
31
|
+
return f"{self.__class__.__name__}({field_values})"
|
codeapi/types/_enums.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum, auto
|
|
4
|
+
from typing import Type
|
|
5
|
+
|
|
6
|
+
from strenum import StrEnum
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CodeType(StrEnum):
|
|
10
|
+
CLI = auto()
|
|
11
|
+
APP = auto()
|
|
12
|
+
MCP = auto()
|
|
13
|
+
WEB = auto()
|
|
14
|
+
LIB = auto()
|
|
15
|
+
UNKNOWN = auto()
|
|
16
|
+
|
|
17
|
+
def __repr__(self):
|
|
18
|
+
return f"{self.__class__.__name__}.{self.name}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ExitType(StrEnum):
|
|
22
|
+
NORMAL = auto()
|
|
23
|
+
ERROR = auto()
|
|
24
|
+
TIMEOUT = auto()
|
|
25
|
+
TERMINATED = auto()
|
|
26
|
+
|
|
27
|
+
def __repr__(self):
|
|
28
|
+
return f"{self.__class__.__name__}.{self.name}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class JobType(StrEnum):
|
|
32
|
+
RUN_CLI = auto()
|
|
33
|
+
RUN_APP = auto()
|
|
34
|
+
RUN_MCP = auto()
|
|
35
|
+
RUN_WEB = auto()
|
|
36
|
+
INGEST = auto()
|
|
37
|
+
PEXENV = auto()
|
|
38
|
+
REQUIREMENTS = auto()
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_code_type(cls, code_type: CodeType) -> JobType | None:
|
|
42
|
+
"""Maps CodeType to corresponding JobType for run operations.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
code_type (CodeType): The code type to map.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
JobType | None: The corresponding job type, or None if no mapping exists.
|
|
49
|
+
"""
|
|
50
|
+
mapping = {
|
|
51
|
+
CodeType.CLI: cls.RUN_CLI,
|
|
52
|
+
CodeType.APP: cls.RUN_APP,
|
|
53
|
+
CodeType.MCP: cls.RUN_MCP,
|
|
54
|
+
CodeType.WEB: cls.RUN_WEB,
|
|
55
|
+
# CodeType.LIB has no corresponding run job type
|
|
56
|
+
}
|
|
57
|
+
return mapping.get(code_type)
|
|
58
|
+
|
|
59
|
+
def __repr__(self):
|
|
60
|
+
return f"{self.__class__.__name__}.{self.name}"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class JobStage(StrEnum):
|
|
64
|
+
UNKNOWN = auto()
|
|
65
|
+
PRE_RUNNING = auto()
|
|
66
|
+
RUNNING = auto()
|
|
67
|
+
POST_RUNNING = auto()
|
|
68
|
+
|
|
69
|
+
def __repr__(self):
|
|
70
|
+
return f"{self.__class__.__name__}.{self.name}"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class JobStatus(StrEnum):
|
|
74
|
+
QUEUED = auto()
|
|
75
|
+
SCHEDULED = auto()
|
|
76
|
+
STARTED = auto()
|
|
77
|
+
DEFERRED = auto()
|
|
78
|
+
CANCELED = auto()
|
|
79
|
+
STOPPED = auto()
|
|
80
|
+
FAILED = auto()
|
|
81
|
+
FINISHED = auto()
|
|
82
|
+
TIMEOUT = auto()
|
|
83
|
+
UNKNOWN = auto()
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def from_rq(cls: Type[JobStatus], rq_status: Enum | None) -> JobStatus:
|
|
87
|
+
if not rq_status:
|
|
88
|
+
return cls.UNKNOWN
|
|
89
|
+
return cls(str(rq_status.value).upper())
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def stage(self) -> JobStage:
|
|
93
|
+
if self == JobStatus.UNKNOWN:
|
|
94
|
+
return JobStage.UNKNOWN
|
|
95
|
+
if self in [JobStatus.QUEUED, JobStatus.SCHEDULED, JobStatus.DEFERRED]:
|
|
96
|
+
return JobStage.PRE_RUNNING
|
|
97
|
+
if self == JobStatus.STARTED:
|
|
98
|
+
return JobStage.RUNNING
|
|
99
|
+
return JobStage.POST_RUNNING # [CANCELED, STOPPED, FAILED, FINISHED, TIMEOUT]
|
|
100
|
+
|
|
101
|
+
def __repr__(self):
|
|
102
|
+
return f"{self.__class__.__name__}.{self.name}"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class HiveStatus(StrEnum):
|
|
106
|
+
ALIVE = auto()
|
|
107
|
+
IDLE = auto()
|
|
108
|
+
BUSY = auto()
|
|
109
|
+
DEAD = auto()
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def _missing_(cls, value):
|
|
113
|
+
if isinstance(value, str):
|
|
114
|
+
value = value.upper()
|
|
115
|
+
if value in cls._value2member_map_:
|
|
116
|
+
return cls._value2member_map_[value]
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class WorkerDeployment(StrEnum):
|
|
121
|
+
LOCAL = auto()
|
|
122
|
+
HIVES = auto()
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def _missing_(cls, value):
|
|
126
|
+
if isinstance(value, str):
|
|
127
|
+
value = value.upper()
|
|
128
|
+
if value in cls._value2member_map_:
|
|
129
|
+
return cls._value2member_map_[value]
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
def __repr__(self):
|
|
133
|
+
return f"{self.__class__.__name__}.{self.name}"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class CodeAPIError(StrEnum):
|
|
137
|
+
CODE_NOT_FOUND = auto()
|
|
138
|
+
JOB_FAILED = auto()
|
|
139
|
+
NO_JOB_META = auto()
|
|
140
|
+
JOB_NOT_FINISHED = auto()
|
|
141
|
+
JOB_NOT_FOUND = auto()
|
|
142
|
+
JOB_POST_RUNNING = auto()
|
|
143
|
+
JOB_STILL_QUEUED = auto()
|
|
144
|
+
JOB_TIMED_OUT = auto()
|
|
145
|
+
NO_ASSOCIATED_CODE_ID = auto()
|
|
146
|
+
NO_STORAGE_BACKEND = auto()
|
|
147
|
+
UNKNOWN = auto()
|
|
148
|
+
|
|
149
|
+
def __repr__(self):
|
|
150
|
+
return f"{self.__class__.__name__}.{self.name}"
|