Skip to content

Commit df5e923

Browse files
authored
Allow to filter by tags (#101)
1 parent e779cab commit df5e923

6 files changed

Lines changed: 95 additions & 4 deletions

File tree

docs/reference/task_plugin.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ They can be imported from `fluid.scheduler.db`:
5353
from fluid.scheduler.db import TaskHistoryQuery, TaskRunHistory, TaskRunHistoryPage
5454
```
5555

56+
### Filtering by tags
57+
58+
The `tags` field of [TaskHistoryQuery][fluid.scheduler.db.TaskHistoryQuery]
59+
filters runs by the tags of their [Task][fluid.scheduler.Task]. A run matches
60+
when its task carries **at least one** of the supplied tags (OR semantics, the
61+
same as the `tags` query parameter on the task list endpoint). Tags are
62+
resolved against the live task registry at query time, so they always reflect
63+
each task's *current* tags rather than the tags it had when the run executed.
64+
65+
When combined with the `task` filter, the two are applied together (AND): the
66+
run's task must match the name *and* carry one of the tags. Tags that match no
67+
registered task return an empty result.
68+
5669
::: fluid.scheduler.db.TaskHistoryQuery
5770

5871
::: fluid.scheduler.db.TaskRunHistory

docs/release-notes.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Release Notes
22

3+
## v2.4.1
4+
5+
Task history can now be filtered by task tags.
6+
7+
- Added a `tags` field to task history queries. Runs match when their task
8+
carries at least one of the given tags, resolved against the live registry.
9+
([#101](https://github.com/quantmind/aio-fluid/pull/101))
10+
311
## v2.4.0
412

513
Lazy settings via pydantic-settings, JSONB params filtering for task

fluid/scheduler/db.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def __init__(
8080

8181
def register(self, task_manager: TaskManager) -> None:
8282
task_manager.state.task_db_plugin = self
83+
self.task_manager = task_manager
8384

8485
if is_in_cpu_process():
8586
return
@@ -140,9 +141,21 @@ async def get_history(
140141
) -> TaskRunHistoryPage:
141142
"""Get task run history based on the provided query parameters."""
142143
table = self.db.tables[self.table_name]
144+
filters = q.filters()
145+
if q.tags:
146+
wanted = set(q.tags)
147+
names = {
148+
name
149+
for name, task in self.task_manager.registry.items()
150+
if wanted & task.tags
151+
}
152+
# AND with an explicit task filter; empty set → IN () → no rows
153+
if "name" in filters:
154+
names &= {filters["name"]}
155+
filters["name"] = list(names)
143156
pagination = Pagination.create(
144157
"queued",
145-
filters=q.filters(),
158+
filters=filters,
146159
limit=q.limit,
147160
cursor=q.cursor,
148161
desc=True,
@@ -289,6 +302,15 @@ class TaskHistoryQuery(BaseModel):
289302
Query(description="Filter by params using JSON containment"),
290303
Doc("Filter by params using JSON containment when provided"),
291304
] = None
305+
tags: Annotated[
306+
list[str] | None,
307+
Query(description="Filter by task tags (matches any of the given tags)"),
308+
Doc(
309+
"Filter runs whose task has at least one of these tags when provided. "
310+
"Tags are resolved against the live task registry, so they reflect "
311+
"each task's current tags."
312+
),
313+
] = None
292314

293315
@model_validator(mode="before")
294316
@classmethod
@@ -319,7 +341,7 @@ def filters(self) -> dict:
319341
return {
320342
self._filter_map.get(k, k): v
321343
for k, v in self.model_dump(
322-
exclude_none=True, exclude={"limit", "cursor"}
344+
exclude_none=True, exclude={"limit", "cursor", "tags"}
323345
).items()
324346
}
325347

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "aio-fluid"
3-
version = "2.4.0"
3+
version = "2.4.1"
44
description = "Tools for backend python services"
55
authors = [
66
{ name = "Luca Sbardella", email = "luca@quantmind.com" },

tests/scheduler/test_db_plugin.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,54 @@ async def test_get_history_filter_by_params_http(
304304
assert all(9.0 == item["params"].get("a") for item in data)
305305

306306

307+
async def test_get_history_filter_by_tags(
308+
cli_db: TaskClient, task_manager_db: TaskConsumer, db_plugin: TaskDbPlugin
309+
) -> None:
310+
task_run = await task_manager_db.queue_and_wait("dummy", timeout=5)
311+
await wait_for_task_run(db_plugin, task_run.id)
312+
# "dummy" carries the "slow" tag, "add" has no tags
313+
data = await get_history(cli_db, tags=["slow"])
314+
assert any(item["id"] == task_run.id for item in data)
315+
assert all(item["task"] == "dummy" for item in data)
316+
317+
318+
async def test_get_history_filter_by_tags_any(
319+
cli_db: TaskClient, task_manager_db: TaskConsumer, db_plugin: TaskDbPlugin
320+
) -> None:
321+
dummy_run = await task_manager_db.queue_and_wait("dummy", timeout=5)
322+
fast_run = await task_manager_db.queue_and_wait("fast", timeout=5)
323+
await wait_for_task_run(db_plugin, dummy_run.id)
324+
await wait_for_task_run(db_plugin, fast_run.id)
325+
# OR semantics across tags: "fast" and "dummy" share the "test" tag
326+
data = await get_history(cli_db, tags=["fast", "slow"])
327+
assert all(item["task"] in {"fast", "dummy"} for item in data)
328+
ids = {item["id"] for item in data}
329+
assert dummy_run.id in ids
330+
assert fast_run.id in ids
331+
332+
333+
async def test_get_history_filter_by_tags_no_match(
334+
cli_db: TaskClient, task_manager_db: TaskConsumer, db_plugin: TaskDbPlugin
335+
) -> None:
336+
task_run = await task_manager_db.queue_and_wait("dummy", timeout=5)
337+
await wait_for_task_run(db_plugin, task_run.id)
338+
data = await get_history(cli_db, tags=["does-not-exist"])
339+
assert data == []
340+
341+
342+
async def test_get_history_filter_by_tags_and_task(
343+
cli_db: TaskClient, task_manager_db: TaskConsumer, db_plugin: TaskDbPlugin
344+
) -> None:
345+
task_run = await task_manager_db.queue_and_wait("dummy", timeout=5)
346+
await wait_for_task_run(db_plugin, task_run.id)
347+
# task + tags combine with AND: "dummy" has "slow" but not "fast"
348+
data = await get_history(cli_db, task="dummy", tags=["slow"])
349+
assert any(item["id"] == task_run.id for item in data)
350+
assert all(item["task"] == "dummy" for item in data)
351+
data_empty = await get_history(cli_db, task="dummy", tags=["fast"])
352+
assert data_empty == []
353+
354+
307355
async def test_get_history_filter_by_params_http_negative(
308356
cli_db: TaskClient, task_manager_db: TaskConsumer, db_plugin: TaskDbPlugin
309357
) -> None:

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)