Skip to content

Commit f504526

Browse files
committed
feat: two endpoints for listing job info #71
1 parent 8edc003 commit f504526

File tree

4 files changed

+240
-31
lines changed

4 files changed

+240
-31
lines changed

src/kiara/interfaces/python_api/base_api.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
from kiara.interfaces.python_api.models.info import (
4545
DataTypeClassesInfo,
4646
DataTypeClassInfo,
47+
JobInfo,
48+
JobsInfo,
4749
KiaraPluginInfo,
4850
KiaraPluginInfos,
4951
ModuleTypeInfo,
@@ -3216,6 +3218,28 @@ def get_job_record(self, job_id: Union[str, uuid.UUID]) -> Union["JobRecord", No
32163218
job_record = self.context.job_registry.get_job_record(job_id=job_id)
32173219
return job_record
32183220

3221+
@tag("kiara_api")
3222+
def retrieve_job_info(self, job_id: Union[str, uuid.UUID]) -> Union[JobInfo, None]:
3223+
"""Retrieve the detailed job record for the specified job id.
3224+
3225+
If no job can be found, 'None' is returned.
3226+
"""
3227+
3228+
job_record = self.get_job_record(job_id=job_id)
3229+
if job_record is None:
3230+
return None
3231+
3232+
job_info = JobInfo.create_from_instance(kiara=self.context, instance=job_record)
3233+
return job_info
3234+
3235+
@tag("kiara_api")
3236+
def retrieve_jobs_info(self, **matcher_params: Any) -> JobsInfo:
3237+
3238+
job_records = self.list_job_records(**matcher_params)
3239+
3240+
infos: JobsInfo = JobsInfo.create_from_instances(kiara=self.context, instances={str(j_id): j for j_id, j in job_records.items()}) # type: ignore
3241+
return infos
3242+
32193243
def render_value(
32203244
self,
32213245
value: Union[str, uuid.UUID, Value],

src/kiara/interfaces/python_api/kiara_api.py

Lines changed: 212 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Iterable, List, Mapping, Union
77
from uuid import UUID
88

9+
from rich import box
10+
911
if TYPE_CHECKING:
1012
from kiara.interfaces.python_api.models.info import (
1113
DataTypeClassesInfo,
1214
DataTypeClassInfo,
15+
JobInfo,
16+
JobsInfo,
1317
KiaraPluginInfo,
1418
KiaraPluginInfos,
1519
ModuleTypeInfo,
@@ -237,7 +241,9 @@ def get_job_comment(self, job_id: Union[str, uuid.UUID]) -> Union[str, None]:
237241
)
238242
return metadata.comment
239243

240-
def retrieve_augmented_value_lineage(self, value: Union[str, "UUID", "Value", "Path"]) -> Dict[int, Dict[str, Any]]:
244+
def retrieve_augmented_value_lineage(
245+
self, value: Union[str, "UUID", "Value", "Path"]
246+
) -> Dict[int, Dict[str, Any]]:
241247
"""Retrieve lineage data for the specified value, augmented with additional metadata.
242248
243249
The format of the returned data is a dictionary with the following keys:
@@ -261,44 +267,208 @@ def retrieve_augmented_value_lineage(self, value: Union[str, "UUID", "Value", "P
261267
nodes = graph.nodes.data()
262268
augmented_nodes = {}
263269

264-
265270
def get_info(node):
266-
# all this is terribly inefficient
271+
# all this is terribly inefficient
267272

268-
if node[1]["node_type"] == "operation":
273+
if node[1]["node_type"] == "operation":
269274

270-
result = self.retrieve_module_type_info(node[1]["module_type"]).model_dump()
275+
result = self.retrieve_module_type_info(
276+
node[1]["module_type"]
277+
).model_dump()
271278

272-
elif node[1]["node_type"] == "value":
279+
elif node[1]["node_type"] == "value":
273280

274-
value_id = node[0][6:]
281+
value_id = node[0][6:]
275282

276-
v = self.get_value(value_id)
277-
if v.is_set:
278-
render_result = self._api.render_value(value=v, target_format="string").rendered
283+
v = self.get_value(value_id)
284+
if v.is_set:
285+
render_result = self._api.render_value(
286+
value=v, target_format="string"
287+
).rendered
279288

280-
else:
281-
render_result = "None"
282-
283-
result = {
284-
"preview": render_result
285-
}
286289
else:
287-
raise Exception(f"Unknown node type: {node[1]}")
290+
render_result = "None"
291+
292+
result = {"preview": render_result}
293+
else:
294+
raise Exception(f"Unknown node type: {node[1]}")
288295

289-
return result
296+
return result
290297

291298
for idx, node in enumerate(nodes):
292-
node_dict = {
293-
"id": node[0],
294-
"desc": node[1],
295-
"parentIds": list(graph.predecessors(node[0])),
296-
"info": get_info(node)
297-
}
298-
augmented_nodes[idx] = node_dict
299+
node_dict = {
300+
"id": node[0],
301+
"desc": node[1],
302+
"parentIds": list(graph.predecessors(node[0])),
303+
"info": get_info(node),
304+
}
305+
augmented_nodes[idx] = node_dict
299306

300307
return augmented_nodes
301308

309+
def print_all_jobs_info_data(self, aliases: bool = True, max_char: int = 0, show_inputs: bool=False, show_outputs: bool=False) -> None:
310+
"""Prints a table with all jobs info data.
311+
312+
If max_char > 0, the value previews will be truncated to max_char characters, unless aliases is True, in which case the aliases of a value will be shown (if available).
313+
314+
Arguments:
315+
max_char: the maximum number of characters to show for value previews
316+
aliases: whether to show aliases for values (if max_char is exceeded by preview)
317+
show_inputs: whether to show the inputs of the jobs
318+
show_outputs: whether to show the outputs of the jobs
319+
"""
320+
321+
from rich.table import Table
322+
323+
from kiara.utils.cli import terminal_print
324+
325+
data = self.get_all_jobs_info_data(aliases=aliases, max_char=max_char, add_inputs_preview=show_inputs, add_outputs_preview=show_outputs)
326+
327+
table = Table(show_lines=True, box=box.SIMPLE)
328+
table.add_column("Module name")
329+
table.add_column("Comment")
330+
table.add_column("Time submitted")
331+
table.add_column("Runtime")
332+
if show_inputs:
333+
table.add_column("Inputs")
334+
if show_outputs:
335+
table.add_column("Outputs")
336+
337+
for row_data in data:
338+
339+
row = [
340+
row_data["module_name"],
341+
row_data["comment"],
342+
str(row_data["time_submitted"]),
343+
str(row_data["runtime"]),
344+
]
345+
if show_inputs:
346+
inputs = row_data["inputs"]
347+
inputs_table = Table(
348+
show_lines=False, box=box.SIMPLE, show_header=False
349+
)
350+
inputs_table.add_column("Field", style="b")
351+
inputs_table.add_column("Value")
352+
for input_name, input_value in inputs.items():
353+
inputs_table.add_row(input_name, str(input_value))
354+
355+
row.append(inputs_table)
356+
357+
if show_outputs:
358+
outputs = row_data["outputs"]
359+
outputs_table = Table(
360+
show_lines=False, box=box.SIMPLE, show_header=False
361+
)
362+
outputs_table.add_column("Field", style="b")
363+
outputs_table.add_column("Value")
364+
for output_name, output_value in outputs.items():
365+
outputs_table.add_row(output_name, str(output_value))
366+
row.append(outputs_table)
367+
368+
table.add_row(*row)
369+
370+
terminal_print(table)
371+
372+
def get_all_jobs_info_data(self, aliases: bool = True, max_char: int = 0, add_inputs_preview: bool=False, add_outputs_preview: bool=False) -> List[Dict[str, Any]]:
373+
"""Retrieve all job info as a list of dicts.
374+
375+
If max_char > 0, the value previews will be truncated to max_char characters, unless aliases is True, in which case the aliases of a value will be shown (if available).
376+
377+
The result list items are dicts with the following keys:
378+
379+
- job_id: the job id
380+
- module_name: the module name
381+
- module_config: the module config that was used (if applicable, otherwise this key will not be present)
382+
- time_submitted: the time the job was submitted
383+
- runtime: the runtime of the job
384+
- comment: the comment for the job
385+
- inputs: a dict of input field names and values (when 'add_inputs_preview' is set)
386+
- outputs: a dict of output field names and values (when 'add_outputs_preview' is set)
387+
388+
Arguments:
389+
max_char: the maximum number of characters to show for value previews
390+
aliases: whether to show aliases for values (if max_char is exceeded by preview)
391+
add_inputs_preview: whether to add preview data for input fields
392+
add_outputs_preview: whether to add preview data for output fields
393+
"""
394+
395+
job_infos = self.retrieve_jobs_info(allow_internal=False)
396+
397+
def get_value_str(value_info: "ValueInfo") -> str:
398+
399+
if not value_info._value:
400+
value_obj = self.get_value(value_info.value_id)
401+
else:
402+
value_obj = value_info._value
403+
404+
if value_obj.is_set:
405+
rendered: str = self._api.render_value(
406+
value=value_obj, target_format="string"
407+
).rendered # type: ignore
408+
409+
if max_char > 0 and len(rendered) <= max_char:
410+
return rendered
411+
412+
if aliases is True:
413+
value_aliases = value_info.aliases
414+
if value_aliases:
415+
value_aliases_str = ", ".join(value_aliases)
416+
return value_aliases_str
417+
418+
# means no aliases
419+
if max_char > 0:
420+
return rendered[0:max_char] + "..."
421+
else:
422+
return rendered
423+
424+
else:
425+
return value_obj.value_status.value
426+
427+
result = []
428+
for job_id, job_info in job_infos.item_infos.items():
429+
430+
module_name = job_info.job_record.module_type
431+
module_config = job_info.job_record.module_config
432+
comment = self.get_job_comment(job_info.job_record.job_id)
433+
time_submitted = job_info.job_record.job_submitted
434+
runtime_details = job_info.job_record.runtime_details
435+
if not runtime_details:
436+
runtime = "n/a"
437+
else:
438+
runtime = str(runtime_details.runtime)
439+
440+
inputs = {}
441+
if add_inputs_preview:
442+
for field_name, value_info in job_info.inputs.items():
443+
value_str = get_value_str(value_info)
444+
inputs[field_name] = value_str
445+
outputs = {}
446+
if add_outputs_preview:
447+
for field_name, value_info in job_info.outputs.items():
448+
value_str = get_value_str(value_info)
449+
outputs[field_name] = value_str
450+
451+
result_item = {
452+
"job_id": str(job_id),
453+
"module_name": module_name,
454+
"time_submitted": time_submitted,
455+
"comment": comment,
456+
"runtime": runtime,
457+
}
458+
459+
if add_inputs_preview:
460+
result_item["inputs"] = inputs
461+
if add_outputs_preview:
462+
result_item["outputs"] = outputs
463+
464+
465+
if module_config:
466+
result_item["module_config"] = module_config
467+
468+
result.append(result_item)
469+
470+
return sorted(result, key=lambda x: x["time_submitted"])
471+
302472
# BEGIN IMPORTED-ENDPOINTS
303473
def list_available_plugin_names(
304474
self, regex: str = r"^kiara[-_]plugin\..*"
@@ -757,9 +927,6 @@ def retrieve_value_info(
757927
result: "ValueInfo" = self._api.retrieve_value_info(value=value)
758928
return result
759929

760-
761-
762-
763930
def retrieve_values_info(self, **matcher_params: Any) -> "ValuesInfo":
764931
"""Retrieve information about the matching values.
765932
@@ -995,6 +1162,8 @@ def export_values(
9951162
target_archive: Union[str, "Path"],
9961163
values: Union[
9971164
str,
1165+
"Value",
1166+
"UUID",
9981167
Mapping[str, Union[str, "UUID", "Value"]],
9991168
Iterable[Union[str, "UUID", "Value"]],
10001169
],
@@ -1233,4 +1402,19 @@ def get_job_record(self, job_id: Union[str, "UUID"]) -> Union["JobRecord", None]
12331402
result: Union["JobRecord", None] = self._api.get_job_record(job_id=job_id)
12341403
return result
12351404

1405+
def retrieve_job_info(self, job_id: Union[str, "UUID"]) -> Union["JobInfo", None]:
1406+
"""Retrieve the detailed job record for the specified job id.
1407+
1408+
If no job can be found, 'None' is returned.
1409+
"""
1410+
1411+
result: Union["JobInfo", None] = self._api.retrieve_job_info(job_id=job_id)
1412+
return result
1413+
1414+
def retrieve_jobs_info(self, **matcher_params: Any) -> "JobsInfo":
1415+
""" """
1416+
1417+
result: "JobsInfo" = self._api.retrieve_jobs_info(**matcher_params)
1418+
return result
1419+
12361420
# END IMPORTED-ENDPOINTS

src/kiara/interfaces/python_api/models/info.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2158,7 +2158,7 @@ def base_instance_class(cls) -> Type[JobRecord]:
21582158
return JobRecord
21592159

21602160
@classmethod
2161-
def create_from_instance(cls, kiara: "Kiara", instance: JobRecord, **kwargs):
2161+
def create_from_instance(cls, kiara: "Kiara", instance: JobRecord, **kwargs) -> "JobInfo":
21622162

21632163
type_name = str(instance.job_id)
21642164

@@ -2225,7 +2225,7 @@ def create_renderable(self, **config: Any) -> RenderableType:
22252225
return table
22262226

22272227

2228-
class JobInfos(InfoItemGroup[JobInfo]):
2228+
class JobsInfo(InfoItemGroup[JobInfo]):
22292229
@classmethod
22302230
def base_info_class(cls) -> Type[JobInfo]:
22312231
return JobInfo

src/kiara/registries/metadata/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,8 @@ def register_job_metadata_items(
380380

381381
def retrieve_job_metadata_items(self, job_id: uuid.UUID):
382382

383-
pass
383+
raise NotImplementedError("Job metadata items retrieval is not yet implemented")
384+
384385

385386
def retrieve_job_metadata_item(
386387
self, job_id: uuid.UUID, key: str, store: Union[str, uuid.UUID, None] = None

0 commit comments

Comments
 (0)