Skip to content

Commit 764c2ad

Browse files
committed
Added filters to low level API and updated tests
1 parent 632af93 commit 764c2ad

File tree

5 files changed

+252
-77
lines changed

5 files changed

+252
-77
lines changed
Lines changed: 129 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
import abc
2+
from collections.abc import Generator
23
import enum
4+
import json
35
import sys
46

7+
import pydantic
8+
59
if sys.version_info < (3, 11):
6-
from typing_extensions import Self
10+
from typing_extensions import Self, TYPE_CHECKING
711
else:
8-
from typing import Self
12+
from typing import Self, TYPE_CHECKING
13+
14+
15+
if TYPE_CHECKING:
16+
from .base import SimvueObject
17+
918

1019
class Status(str, enum.Enum):
1120
Created = "created"
@@ -22,6 +31,7 @@ class Time(str, enum.Enum):
2231
Modified = "modified"
2332
Ended = "ended"
2433

34+
2535
class System(str, enum.Enum):
2636
Working_Directory = "cwd"
2737
Hostname = "hostname"
@@ -36,16 +46,27 @@ class System(str, enum.Enum):
3646

3747

3848
class RestAPIFilter(abc.ABC):
39-
def __init__(self) -> None:
49+
"""RestAPI query filter object."""
50+
51+
def __init__(self, simvue_object: "type[SimvueObject] | None" = None) -> None:
52+
"""Initialise a query object using a Simvue object class."""
53+
self._sv_object: "type[SimvueObject] | None" = simvue_object
4054
self._filters: list[str] = []
4155
self._generate_members()
4256

43-
def _time_within(self, time_type: Time, *, hours: int=0, days: int=0, years: int=0) -> Self:
44-
if len(_non_zero := list(i for i in (hours, days, years) if i != 0 )) > 1:
45-
raise AssertionError("Only one duration type may be provided: hours, days or years")
57+
def _time_within(
58+
self, time_type: Time, *, hours: int = 0, days: int = 0, years: int = 0
59+
) -> Self:
60+
"""Define filter using time range."""
61+
if len(_non_zero := list(i for i in (hours, days, years) if i != 0)) > 1:
62+
raise AssertionError(
63+
"Only one duration type may be provided: hours, days or years"
64+
)
4665
if len(_non_zero) < 1:
47-
raise AssertionError(f"No duration provided for filter '{time_type.value}_within'")
48-
66+
raise AssertionError(
67+
f"No duration provided for filter '{time_type.value}_within'"
68+
)
69+
4970
if hours:
5071
self._filters.append(f"{time_type.value} < {hours}h")
5172
elif days:
@@ -56,133 +77,159 @@ def _time_within(self, time_type: Time, *, hours: int=0, days: int=0, years: int
5677

5778
@abc.abstractmethod
5879
def _generate_members(self) -> None:
59-
pass
80+
"""Generate filters using specified definitions."""
6081

6182
def has_name(self, name: str) -> Self:
83+
"""Filter based on absolute object name."""
6284
self._filters.append(f"name == {name}")
6385
return self
64-
86+
6587
def has_name_containing(self, name: str) -> Self:
88+
"""Filter base on object name containing a term."""
6689
self._filters.append(f"name contains {name}")
6790
return self
68-
69-
def created_within(self, *, hours: int=0, days: int=0, years: int=0) -> Self:
91+
92+
def created_within(self, *, hours: int = 0, days: int = 0, years: int = 0) -> Self:
93+
"""Find objects created within the last specified time period."""
7094
return self._time_within(Time.Created, hours=hours, days=days, years=years)
71-
95+
7296
def has_description_containing(self, search_str: str) -> Self:
97+
"""Return objects containing the specified term within the description."""
7398
self._filters.append(f"description contains {search_str}")
7499
return self
75-
100+
76101
def exclude_description_containing(self, search_str: str) -> Self:
102+
"""Find objects not containing the specified term in their description."""
77103
self._filters.append(f"description not contains {search_str}")
78104
return self
79-
105+
80106
def has_tag(self, tag: str) -> Self:
107+
"""Find objects with the given tag."""
81108
self._filters.append(f"has tag.{tag}")
82109
return self
83110

111+
def starred(self) -> Self:
112+
self._filters.append("starred")
113+
return self
114+
84115
def as_list(self) -> list[str]:
116+
"""Returns the filters as a list."""
85117
return self._filters
86118

87119
def clear(self) -> None:
120+
"""Clear all current filters."""
88121
self._filters = []
89122

123+
def get(
124+
self,
125+
count: pydantic.PositiveInt | None = None,
126+
offset: pydantic.NonNegativeInt | None = None,
127+
**kwargs,
128+
) -> Generator[tuple[str, "SimvueObject | None"], None, None]:
129+
"""Call the get method from the simvue object class."""
130+
if not self._sv_object:
131+
raise RuntimeError("No object type associated with filter.")
132+
_filters: str = json.dumps(self._filters)
133+
return self._sv_object.get(
134+
count=count, offset=offset, filters=_filters, **kwargs
135+
)
136+
90137

91138
class FoldersFilter(RestAPIFilter):
92139
def has_path(self, name: str) -> "FoldersFilter":
93140
self._filters.append(f"path == {name}")
94141
return self
95-
142+
96143
def has_path_containing(self, name: str) -> "FoldersFilter":
97144
self._filters.append(f"path contains {name}")
98145
return self
99-
146+
100147
def _generate_members(self) -> None:
101148
return super()._generate_members()
102149

103150

104151
class RunsFilter(RestAPIFilter):
105152
def _generate_members(self) -> None:
106-
_global_comparators = [
107-
self._value_contains,
108-
self._value_eq,
109-
self._value_neq
110-
]
153+
_global_comparators = [self._value_contains, self._value_eq, self._value_neq]
111154

112155
_numeric_comparators = [
113156
self._value_geq,
114157
self._value_leq,
115158
self._value_lt,
116-
self._value_gt
159+
self._value_gt,
117160
]
118161

119162
for label, system_spec in System.__members__.items():
120163
for function in _global_comparators:
121164
_label: str = label.lower()
122165
_func_name: str = function.__name__.replace("_value", _label)
123-
_out_func = lambda value, func=function: func("system", system_spec.value, value)
166+
167+
def _out_func(value: str | int | float, func=function) -> Self:
168+
return func("system", system_spec.value, value)
169+
124170
_out_func.__name__ = _func_name
125171
setattr(self, _func_name, _out_func)
126172

127173
for function in _global_comparators + _numeric_comparators:
128-
_func_name: str = function.__name__.replace("_value", "metadata")
129-
_out_func = lambda attribute, value, func=function: func("metadata", attribute, value)
174+
_func_name = function.__name__.replace("_value", "metadata")
175+
176+
def _out_func(
177+
attribute: str, value: str | int | float, func=function
178+
) -> Self:
179+
return func("metadata", attribute, value)
180+
130181
_out_func.__name__ = _func_name
131182
setattr(self, _func_name, _out_func)
132183

133-
def author(self, username: str="self") -> "RunsFilter":
184+
def author(self, username: str = "self") -> "RunsFilter":
134185
self._filters.append(f"user == {username}")
135186
return self
136187

137-
def exclude_author(self, username: str="self") -> "RunsFilter":
188+
def exclude_author(self, username: str = "self") -> "RunsFilter":
138189
self._filters.append(f"user != {username}")
139190
return self
140-
141-
def starred(self) -> "RunsFilter":
142-
self._filters.append("starred")
143-
return self
144-
145-
def has_name(self, name: str) -> "RunsFilter":
146-
self._filters.append(f"name == {name}")
147-
return self
148-
149-
def has_name_containing(self, name: str) -> "RunsFilter":
150-
self._filters.append(f"name contains {name}")
151-
return self
152191

153192
def has_status(self, status: Status) -> "RunsFilter":
154193
self._filters.append(f"status == {status.value}")
155194
return self
156-
195+
157196
def is_running(self) -> "RunsFilter":
158197
return self.has_status(Status.Running)
159-
198+
160199
def is_lost(self) -> "RunsFilter":
161200
return self.has_status(Status.Lost)
162-
201+
163202
def has_completed(self) -> "RunsFilter":
164203
return self.has_status(Status.Completed)
165-
204+
166205
def has_failed(self) -> "RunsFilter":
167206
return self.has_status(Status.Failed)
168-
169-
def has_alert(self, alert_name: str, is_critical: bool | None=None) -> "RunsFilter":
207+
208+
def has_alert(
209+
self, alert_name: str, is_critical: bool | None = None
210+
) -> "RunsFilter":
170211
self._filters.append(f"alert.name == {alert_name}")
171212
if is_critical is True:
172213
self._filters.append("alert.status == critical")
173214
elif is_critical is False:
174215
self._filters.append("alert.status == ok")
175216
return self
176217

177-
def started_within(self, *, hours: int=0, days: int=0, years: int=0) -> "RunsFilter":
218+
def started_within(
219+
self, *, hours: int = 0, days: int = 0, years: int = 0
220+
) -> "RunsFilter":
178221
return self._time_within(Time.Started, hours=hours, days=days, years=years)
179-
180-
def modified_within(self, *, hours: int=0, days: int=0, years: int=0) -> "RunsFilter":
222+
223+
def modified_within(
224+
self, *, hours: int = 0, days: int = 0, years: int = 0
225+
) -> "RunsFilter":
181226
return self._time_within(Time.Modified, hours=hours, days=days, years=years)
182-
183-
def ended_within(self, *, hours: int=0, days: int=0, years: int=0) -> "RunsFilter":
227+
228+
def ended_within(
229+
self, *, hours: int = 0, days: int = 0, years: int = 0
230+
) -> "RunsFilter":
184231
return self._time_within(Time.Ended, hours=hours, days=days, years=years)
185-
232+
186233
def in_folder(self, folder_name: str) -> "RunsFilter":
187234
self._filters.append(f"folder.path == {folder_name}")
188235
return self
@@ -194,37 +241,51 @@ def has_metadata_attribute(self, attribute: str) -> "RunsFilter":
194241
def exclude_metadata_attribute(self, attribute: str) -> "RunsFilter":
195242
self._filters.append(f"metadata.{attribute} not exists")
196243
return self
197-
198-
def _value_eq(self, category: str, attribute: str, value: str | int | float) -> "RunsFilter":
244+
245+
def _value_eq(
246+
self, category: str, attribute: str, value: str | int | float
247+
) -> "RunsFilter":
199248
self._filters.append(f"{category}.{attribute} == {value}")
200249
return self
201-
202-
def _value_neq(self, category: str, attribute: str, value: str | int | float) -> "RunsFilter":
250+
251+
def _value_neq(
252+
self, category: str, attribute: str, value: str | int | float
253+
) -> "RunsFilter":
203254
self._filters.append(f"{category}.{attribute} != {value}")
204255
return self
205-
206-
def _value_contains(self, category: str, attribute: str, value: str | int | float) -> "RunsFilter":
256+
257+
def _value_contains(
258+
self, category: str, attribute: str, value: str | int | float
259+
) -> "RunsFilter":
207260
self._filters.append(f"{category}.{attribute} contains {value}")
208261
return self
209-
210-
def _value_leq(self, category: str, attribute: str, value: int | float) -> "RunsFilter":
262+
263+
def _value_leq(
264+
self, category: str, attribute: str, value: int | float
265+
) -> "RunsFilter":
211266
self._filters.append(f"{category}.{attribute} <= {value}")
212267
return self
213-
214-
def _value_geq(self, category: str, attribute: str, value: int | float) -> "RunsFilter":
268+
269+
def _value_geq(
270+
self, category: str, attribute: str, value: int | float
271+
) -> "RunsFilter":
215272
self._filters.append(f"{category}.{attribute} >= {value}")
216273
return self
217-
218-
def _value_lt(self, category: str, attribute: str, value: int | float) -> "RunsFilter":
274+
275+
def _value_lt(
276+
self, category: str, attribute: str, value: int | float
277+
) -> "RunsFilter":
219278
self._filters.append(f"{category}.{attribute} < {value}")
220279
return self
221-
222-
def _value_gt(self, category: str, attribute: str, value: int | float) -> "RunsFilter":
280+
281+
def _value_gt(
282+
self, category: str, attribute: str, value: int | float
283+
) -> "RunsFilter":
223284
self._filters.append(f"{category}.{attribute} > {value}")
224285
return self
225-
286+
226287
def __str__(self) -> str:
227288
return " && ".join(self._filters) if self._filters else "None"
228-
289+
229290
def __repr__(self) -> str:
230291
return f"{super().__repr__()[:-1]}, filters={self._filters}>"

0 commit comments

Comments
 (0)