Skip to content

Commit eb95c59

Browse files
authored
Merge pull request #243 from aldbr/main_FEAT_pagination
feat(jobs): pagination
2 parents 47037a3 + e06d2ab commit eb95c59

File tree

23 files changed

+722
-127
lines changed

23 files changed

+722
-127
lines changed

diracx-cli/src/diracx/cli/jobs.py

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
__all__ = ("app",)
55

66
import json
7-
from typing import Annotated
7+
import re
8+
from typing import Annotated, cast
89

910
from rich.console import Console
1011
from rich.table import Table
@@ -52,29 +53,66 @@ async def search(
5253
],
5354
condition: Annotated[list[SearchSpec], Option(parser=parse_condition)] = [],
5455
all: bool = False,
56+
page: int = 1,
57+
per_page: int = 10,
5558
):
5659
async with DiracClient() as api:
57-
jobs = await api.jobs.search(
60+
jobs, content_range = await api.jobs.search(
5861
parameters=None if all else parameter,
5962
search=condition if condition else None,
63+
page=page,
64+
per_page=per_page,
65+
cls=lambda _, jobs, headers: (
66+
jobs,
67+
ContentRange(headers.get("Content-Range", "jobs")),
68+
),
6069
)
61-
display(jobs, "jobs")
6270

63-
64-
def display(data, unit: str):
71+
display(jobs, cast(ContentRange, content_range))
72+
73+
74+
class ContentRange:
75+
unit: str | None = None
76+
start: int | None = None
77+
end: int | None = None
78+
total: int | None = None
79+
80+
def __init__(self, header: str):
81+
if match := re.fullmatch(r"(\w+) (\d+-\d+|\*)/(\d+|\*)", header):
82+
self.unit, range, total = match.groups()
83+
self.total = int(total)
84+
if range != "*":
85+
self.start, self.end = map(int, range.split("-"))
86+
elif match := re.fullmatch(r"\w+", header):
87+
self.unit = match.group()
88+
89+
@property
90+
def caption(self):
91+
if self.start is None and self.end is None:
92+
range_str = "all"
93+
else:
94+
range_str = (
95+
f"{self.start if self.start is not None else 'unknown'}-"
96+
f"{self.end if self.end is not None else 'unknown'} "
97+
f"of {self.total or 'unknown'}"
98+
)
99+
return f"Showing {range_str} {self.unit}"
100+
101+
102+
def display(data, content_range: ContentRange):
65103
output_format = get_diracx_preferences().output_format
66104
match output_format:
67105
case OutputFormats.JSON:
68106
print(json.dumps(data, indent=2))
69107
case OutputFormats.RICH:
70-
display_rich(data, unit)
108+
display_rich(data, content_range)
71109
case _:
72110
raise NotImplementedError(output_format)
73111

74112

75-
def display_rich(data, unit: str) -> None:
113+
def display_rich(data, content_range: ContentRange) -> None:
76114
if not data:
77-
print(f"No {unit} found")
115+
print(f"No {content_range.unit} found")
78116
return
79117

80118
console = Console()
@@ -83,7 +121,7 @@ def display_rich(data, unit: str) -> None:
83121
table = Table(
84122
"Parameter",
85123
"Value",
86-
caption=f"Showing {len(data)} of {len(data)} {unit}",
124+
caption=content_range.caption,
87125
caption_justify="right",
88126
)
89127
for job in data:
@@ -93,7 +131,7 @@ def display_rich(data, unit: str) -> None:
93131
else:
94132
table = Table(
95133
*columns,
96-
caption=f"Showing {len(data)} of {len(data)} {unit}",
134+
caption=content_range.caption,
97135
caption_justify="right",
98136
)
99137
for job in data:

diracx-cli/tests/test_jobs.py

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,122 @@
11
from __future__ import annotations
22

33
import json
4+
import os
5+
import tempfile
6+
7+
import pytest
8+
from pytest import raises
49

510
from diracx import cli
11+
from diracx.core.models import ScalarSearchSpec
12+
from diracx.core.preferences import get_diracx_preferences
613

14+
TEST_JDL = """
15+
Arguments = "jobDescription.xml -o LogLevel=INFO";
16+
Executable = "dirac-jobexec";
17+
JobGroup = jobGroup;
18+
JobName = jobName;
19+
JobType = User;
20+
LogLevel = INFO;
21+
OutputSandbox =
22+
{
23+
Script1_CodeOutput.log,
24+
std.err,
25+
std.out
26+
};
27+
Priority = 1;
28+
Site = ANY;
29+
StdError = std.err;
30+
StdOutput = std.out;
31+
"""
32+
33+
34+
@pytest.fixture
35+
async def jdl_file():
36+
with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8") as temp_file:
37+
temp_file.write(TEST_JDL)
38+
temp_file.flush()
39+
yield temp_file.name
40+
41+
42+
async def test_submit(with_cli_login, jdl_file, capfd):
43+
"""Test submitting a job using a JDL file."""
44+
45+
with open(jdl_file, "r") as temp_file:
46+
await cli.jobs.submit([temp_file])
747

8-
async def test_search(with_cli_login, capfd):
9-
await cli.jobs.search()
1048
cap = capfd.readouterr()
1149
assert cap.err == ""
50+
assert "Inserted 1 jobs with ids" in cap.out
51+
52+
53+
async def test_search(with_cli_login, jdl_file, capfd):
54+
"""Test searching for jobs."""
55+
56+
# Submit 20 jobs
57+
with open(jdl_file, "r") as temp_file:
58+
await cli.jobs.submit([temp_file] * 20)
59+
60+
cap = capfd.readouterr()
61+
1262
# By default the output should be in JSON format as capfd is not a TTY
13-
json.loads(cap.out)
63+
await cli.jobs.search()
64+
cap = capfd.readouterr()
65+
assert cap.err == ""
66+
jobs = json.loads(cap.out)
67+
68+
# There should be 10 jobs by default
69+
assert len(jobs) == 10
70+
assert "JobID" in jobs[0]
71+
assert "JobGroup" in jobs[0]
72+
73+
# Change per-page to a very large number to get all the jobs at once: the caption should change
74+
await cli.jobs.search(per_page=9999)
75+
cap = capfd.readouterr()
76+
assert cap.err == ""
77+
jobs = json.loads(cap.out)
78+
79+
# There should be 20 jobs at least now
80+
assert len(jobs) >= 20
81+
assert "JobID" in cap.out
82+
assert "JobGroup" in cap.out
83+
84+
# Search for a job that doesn't exist
85+
condition = ScalarSearchSpec(parameter="Status", operator="eq", value="nonexistent")
86+
await cli.jobs.search(condition=[condition])
87+
cap = capfd.readouterr()
88+
assert cap.err == ""
89+
assert "[]" == cap.out.strip()
90+
91+
# Switch to RICH output
92+
get_diracx_preferences.cache_clear()
93+
os.environ["DIRACX_OUTPUT_FORMAT"] = "RICH"
94+
95+
await cli.jobs.search()
96+
cap = capfd.readouterr()
97+
assert cap.err == ""
98+
99+
with raises(json.JSONDecodeError):
100+
json.loads(cap.out)
101+
102+
assert "JobID" in cap.out
103+
assert "JobGroup" in cap.out
104+
assert "Showing 0-9 of " in cap.out
105+
106+
# Change per-page to a very large number to get all the jobs at once: the caption should change
107+
await cli.jobs.search(per_page=9999)
108+
cap = capfd.readouterr()
109+
assert cap.err == ""
110+
111+
with raises(json.JSONDecodeError):
112+
json.loads(cap.out)
113+
114+
assert "JobID" in cap.out
115+
assert "JobGroup" in cap.out
116+
assert "Showing all jobs" in cap.out
117+
118+
# Search for a job that doesn't exist
119+
await cli.jobs.search(condition=[condition])
120+
cap = capfd.readouterr()
121+
assert cap.err == ""
122+
assert "No jobs found" in cap.out

diracx-client/src/diracx/client/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].9)
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].17)
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

diracx-client/src/diracx/client/_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].9)
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].17)
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

diracx-client/src/diracx/client/_configuration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].9)
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].17)
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

diracx-client/src/diracx/client/_vendor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# --------------------------------------------------------------------------
2-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].9)
2+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].17)
33
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
44
# --------------------------------------------------------------------------
55

diracx-client/src/diracx/client/aio/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].9)
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].17)
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

diracx-client/src/diracx/client/aio/_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].9)
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].17)
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

diracx-client/src/diracx/client/aio/_configuration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# coding=utf-8
22
# --------------------------------------------------------------------------
3-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].9)
3+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].17)
44
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
55
# --------------------------------------------------------------------------
66

diracx-client/src/diracx/client/aio/_vendor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# --------------------------------------------------------------------------
2-
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].9)
2+
# Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.2, generator: @autorest/[email protected].17)
33
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
44
# --------------------------------------------------------------------------
55

0 commit comments

Comments
 (0)