Skip to content

Commit ab47dd6

Browse files
committed
feat(api): support batch operation for artifacts, attachments and tasks
1 parent 989bbac commit ab47dd6

File tree

2 files changed

+237
-0
lines changed

2 files changed

+237
-0
lines changed

src/pynetmito/client.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@
6262
AttachmentsDownloadByFilterReq,
6363
AttachmentsDownloadByKeysReq,
6464
AttachmentsDownloadListResp,
65+
ArtifactsDeleteByFilterReq,
66+
ArtifactsDeleteByFilterResp,
67+
ArtifactsDeleteByUuidsReq,
68+
ArtifactsDeleteByUuidsResp,
69+
AttachmentsDeleteByFilterReq,
70+
AttachmentsDeleteByFilterResp,
71+
AttachmentsDeleteByKeysReq,
72+
AttachmentsDeleteByKeysResp,
73+
TasksSubmitReq,
74+
TasksSubmitResp,
6575
)
6676

6777

@@ -946,3 +956,81 @@ def admin_shutdown_coordinator(self, req: ShutdownReq):
946956
raise Exception(
947957
f"Failed to shutdown coordinator, status code: {resp.status_code}, error: {resp.text}"
948958
)
959+
960+
def batch_delete_artifacts_by_filter(
961+
self, req: ArtifactsDeleteByFilterReq
962+
) -> ArtifactsDeleteByFilterResp:
963+
"""Batch delete artifacts by filter criteria."""
964+
url = self._get_url("tasks/delete/artifacts")
965+
headers = {"Authorization": f"Bearer {self.credential}"}
966+
resp = self.http_client.post(url, headers=headers, json=req.to_dict())
967+
if resp.status_code == 200:
968+
r = ArtifactsDeleteByFilterResp.model_validate(resp.json())
969+
return r
970+
else:
971+
self.logger.error(resp.text)
972+
raise Exception(
973+
f"Failed to batch delete artifacts by filter, status code: {resp.status_code}, error: {resp.text}"
974+
)
975+
976+
def batch_delete_artifacts_by_list(
977+
self, req: ArtifactsDeleteByUuidsReq
978+
) -> ArtifactsDeleteByUuidsResp:
979+
"""Batch delete artifacts by task UUIDs."""
980+
url = self._get_url("tasks/delete/artifacts/list")
981+
headers = {"Authorization": f"Bearer {self.credential}"}
982+
resp = self.http_client.post(url, headers=headers, json=req.to_dict())
983+
if resp.status_code == 200:
984+
r = ArtifactsDeleteByUuidsResp.model_validate(resp.json())
985+
return r
986+
else:
987+
self.logger.error(resp.text)
988+
raise Exception(
989+
f"Failed to batch delete artifacts by list, status code: {resp.status_code}, error: {resp.text}"
990+
)
991+
992+
def batch_delete_attachments_by_filter(
993+
self, group_name: str, req: AttachmentsDeleteByFilterReq
994+
) -> AttachmentsDeleteByFilterResp:
995+
"""Batch delete attachments by filter criteria."""
996+
url = self._get_url(f"groups/{group_name}/delete/attachments")
997+
headers = {"Authorization": f"Bearer {self.credential}"}
998+
resp = self.http_client.post(url, headers=headers, json=req.to_dict())
999+
if resp.status_code == 200:
1000+
r = AttachmentsDeleteByFilterResp.model_validate(resp.json())
1001+
return r
1002+
else:
1003+
self.logger.error(resp.text)
1004+
raise Exception(
1005+
f"Failed to batch delete attachments by filter for group {group_name}, status code: {resp.status_code}, error: {resp.text}"
1006+
)
1007+
1008+
def batch_delete_attachments_by_list(
1009+
self, group_name: str, req: AttachmentsDeleteByKeysReq
1010+
) -> AttachmentsDeleteByKeysResp:
1011+
"""Batch delete attachments by keys."""
1012+
url = self._get_url(f"groups/{group_name}/delete/attachments/list")
1013+
headers = {"Authorization": f"Bearer {self.credential}"}
1014+
resp = self.http_client.post(url, headers=headers, json=req.to_dict())
1015+
if resp.status_code == 200:
1016+
r = AttachmentsDeleteByKeysResp.model_validate(resp.json())
1017+
return r
1018+
else:
1019+
self.logger.error(resp.text)
1020+
raise Exception(
1021+
f"Failed to batch delete attachments by list for group {group_name}, status code: {resp.status_code}, error: {resp.text}"
1022+
)
1023+
1024+
def batch_submit_tasks(self, req: TasksSubmitReq) -> TasksSubmitResp:
1025+
"""Batch submit tasks."""
1026+
url = self._get_url("tasks/submit")
1027+
headers = {"Authorization": f"Bearer {self.credential}"}
1028+
resp = self.http_client.post(url, headers=headers, json=req.to_dict())
1029+
if resp.status_code == 200:
1030+
r = TasksSubmitResp.model_validate(resp.json())
1031+
return r
1032+
else:
1033+
self.logger.error(resp.text)
1034+
raise Exception(
1035+
f"Failed to batch submit tasks, status code: {resp.status_code}, error: {resp.text}"
1036+
)

src/pynetmito/schemas.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,3 +1168,152 @@ class AttachmentsDownloadListResp(BaseAPIModel):
11681168

11691169
downloads: list[AttachmentDownloadItem]
11701170
group_name: str
1171+
1172+
1173+
class ArtifactsDeleteByFilterReq(BaseAPIModel):
1174+
"""Request to batch delete artifacts by filter criteria."""
1175+
1176+
model_config = ConfigDict(
1177+
extra="allow",
1178+
validate_assignment=True,
1179+
use_enum_values=True,
1180+
)
1181+
creator_usernames: Optional[Set[str]] = Field(default=None)
1182+
group_name: Optional[str] = Field(default=None)
1183+
tags: Optional[Set[str]] = Field(default=None)
1184+
labels: Optional[Set[str]] = Field(default=None)
1185+
states: Optional[Set[TaskState]] = Field(default=None)
1186+
exit_status: Optional[str] = Field(default=None)
1187+
priority: Optional[str] = Field(default=None)
1188+
content_type: ArtifactContentType
1189+
1190+
@field_serializer("creator_usernames")
1191+
def serialize_creator_usernames(self, creator_usernames: Optional[Set[str]]):
1192+
return list(creator_usernames) if creator_usernames else None
1193+
1194+
@field_validator("creator_usernames", mode="before")
1195+
@classmethod
1196+
def deserialize_creator_usernames(cls, creator_usernames: Optional[list[str]]):
1197+
return set(creator_usernames) if creator_usernames else None
1198+
1199+
@field_serializer("tags")
1200+
def serialize_tags(self, tags: Optional[Set[str]]):
1201+
return list(tags) if tags else None
1202+
1203+
@field_validator("tags", mode="before")
1204+
@classmethod
1205+
def deserialize_tags(cls, tags: Optional[list[str]]):
1206+
return set(tags) if tags else None
1207+
1208+
@field_serializer("labels")
1209+
def serialize_labels(self, labels: Optional[Set[str]]):
1210+
return list(labels) if labels else None
1211+
1212+
@field_validator("labels", mode="before")
1213+
@classmethod
1214+
def deserialize_labels(cls, labels: Optional[list[str]]):
1215+
return set(labels) if labels else None
1216+
1217+
@field_serializer("states")
1218+
def serialize_states(self, states: Optional[Set[TaskState]]):
1219+
return list(states) if states else None
1220+
1221+
@field_validator("states", mode="before")
1222+
@classmethod
1223+
def deserialize_states(cls, states: Optional[list[TaskState]]):
1224+
return set(states) if states else None
1225+
1226+
1227+
class ArtifactsDeleteByFilterResp(BaseAPIModel):
1228+
"""Response for batch artifact deletion by filter."""
1229+
1230+
deleted_count: NonNegativeInt
1231+
1232+
1233+
class ArtifactsDeleteByUuidsReq(BaseAPIModel):
1234+
"""Request to batch delete artifacts by task UUIDs."""
1235+
1236+
uuids: list[UUID4]
1237+
content_type: ArtifactContentType
1238+
1239+
1240+
class ArtifactsDeleteByUuidsResp(BaseAPIModel):
1241+
"""Response for batch artifact deletion by UUIDs."""
1242+
1243+
deleted_count: NonNegativeInt
1244+
failed_uuids: list[UUID4]
1245+
1246+
1247+
class AttachmentsDeleteByFilterReq(BaseAPIModel):
1248+
"""Request to batch delete attachments by filter criteria."""
1249+
1250+
key: Optional[str] = Field(default=None)
1251+
limit: Optional[NonNegativeInt] = Field(default=None)
1252+
offset: Optional[NonNegativeInt] = Field(default=None)
1253+
1254+
1255+
class AttachmentsDeleteByFilterResp(BaseAPIModel):
1256+
"""Response for batch attachment deletion by filter."""
1257+
1258+
deleted_count: NonNegativeInt
1259+
group_name: str
1260+
1261+
1262+
class AttachmentsDeleteByKeysReq(BaseAPIModel):
1263+
"""Request to batch delete attachments by keys."""
1264+
1265+
keys: list[str]
1266+
1267+
1268+
class AttachmentsDeleteByKeysResp(BaseAPIModel):
1269+
"""Response for batch attachment deletion by keys."""
1270+
1271+
deleted_count: NonNegativeInt
1272+
failed_keys: list[str]
1273+
group_name: str
1274+
1275+
1276+
class TasksSubmitReq(BaseAPIModel):
1277+
"""Request to batch submit tasks."""
1278+
1279+
tasks: list[SubmitTaskReq]
1280+
1281+
1282+
class ErrorMsg(BaseAPIModel):
1283+
"""Error message wrapper."""
1284+
1285+
msg: str
1286+
1287+
1288+
class TasksSubmitResp(BaseAPIModel):
1289+
"""Response for batch task submission."""
1290+
1291+
results: list[Union[SubmitTaskResp, ErrorMsg]]
1292+
1293+
@model_validator(mode="before")
1294+
@classmethod
1295+
def deserialize_results(cls, data: Any):
1296+
if not isinstance(data, dict) or "results" not in data:
1297+
return data
1298+
1299+
results = data["results"]
1300+
parsed_results = []
1301+
1302+
for item in results:
1303+
if isinstance(item, dict):
1304+
# Handle Result enum format from Rust
1305+
if "Ok" in item:
1306+
parsed_results.append(SubmitTaskResp(**item["Ok"]))
1307+
elif "Err" in item:
1308+
parsed_results.append(ErrorMsg(**item["Err"]))
1309+
else:
1310+
# Direct object
1311+
if "msg" in item:
1312+
parsed_results.append(ErrorMsg(**item))
1313+
else:
1314+
parsed_results.append(SubmitTaskResp(**item))
1315+
else:
1316+
parsed_results.append(item)
1317+
1318+
data["results"] = parsed_results
1319+
return data

0 commit comments

Comments
 (0)