Skip to content

Commit c212793

Browse files
committed
add tag support to primary API
1 parent 466afa8 commit c212793

7 files changed

Lines changed: 80 additions & 9 deletions

File tree

taskbadger/cli/basics.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
configure_api,
1212
err_console,
1313
get_actions,
14-
get_metadata,
14+
merge_kv_json,
1515
)
1616

1717

@@ -69,12 +69,23 @@ def create(
6969
show_default=False,
7070
help="Metadata to associate with the task. Must be valid JSON.",
7171
),
72+
tag: list[str] = typer.Option(
73+
None,
74+
show_default=False,
75+
help="Metadata 'key=value' pair to associate with the task. Can be specified multiple times.",
76+
),
77+
tags_json: str = typer.Option(
78+
None,
79+
show_default=False,
80+
help="Tags to associate with the task. Must be valid JSON mapping name -> value.",
81+
),
7282
quiet: bool = typer.Option(False, "--quiet", "-q", help="Minimal output. Only the Task ID."),
7383
):
7484
"""Create a task."""
7585
configure_api(ctx)
7686
actions = get_actions(action_def)
77-
metadata = get_metadata(metadata, metadata_json)
87+
metadata = merge_kv_json(metadata, metadata_json)
88+
tags = merge_kv_json(tag, tags_json)
7889

7990
try:
8091
task = create_task(
@@ -84,6 +95,7 @@ def create(
8495
data=metadata,
8596
actions=actions,
8697
monitor_id=monitor_id,
98+
tags=tags,
8799
)
88100
except Exception as e:
89101
err_console.print(f"Error creating task: {e}")
@@ -119,12 +131,23 @@ def update(
119131
show_default=False,
120132
help="Metadata to associate with the task. Must be valid JSON.",
121133
),
134+
tag: list[str] = typer.Option(
135+
None,
136+
show_default=False,
137+
help="Metadata 'key=value' pair to associate with the task. Can be specified multiple times.",
138+
),
139+
tags_json: str = typer.Option(
140+
None,
141+
show_default=False,
142+
help="Tags to associate with the task. Must be valid JSON mapping name -> value.",
143+
),
122144
quiet: bool = typer.Option(False, "--quiet", "-q", help="No output."),
123145
):
124146
"""Update a task."""
125147
configure_api(ctx)
126148
actions = get_actions(action_def)
127-
metadata = get_metadata(metadata, metadata_json)
149+
metadata = merge_kv_json(metadata, metadata_json)
150+
tags = merge_kv_json(tag, tags_json)
128151

129152
try:
130153
task = update_task(
@@ -135,6 +158,7 @@ def update(
135158
value_max=value_max,
136159
data=metadata,
137160
actions=actions,
161+
tags=tags,
138162
)
139163
except Exception as e:
140164
err_console.print(f"Error creating task: {e}")

taskbadger/cli/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def get_actions(action_def: tuple[str, str, str]) -> list[Action]:
2828
return []
2929

3030

31-
def get_metadata(metadata_kv: list[str], metadata_json: str) -> dict:
31+
def merge_kv_json(metadata_kv: list[str], metadata_json: str) -> dict:
3232
metadata = {}
3333
for kv in metadata_kv:
3434
k, v = kv.strip().split("=", 1)

taskbadger/cli/wrapper.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from rich import print
33

44
from taskbadger import DefaultMergeStrategy, Session, StatusEnum, Task
5-
from taskbadger.cli.utils import configure_api, err_console, get_actions
5+
from taskbadger.cli.utils import configure_api, err_console, get_actions, merge_kv_json
66
from taskbadger.process import ProcessRunner
77

88

@@ -19,6 +19,11 @@ def run(
1919
show_default=False,
2020
help="Action definition e.g. 'success,error email to:me@email.com'",
2121
),
22+
tag: list[str] = typer.Option(
23+
None,
24+
show_default=False,
25+
help="Tags: 'name=value' pair to associate with the task. Can be specified multiple times.",
26+
),
2227
capture_output: bool = typer.Option(False, help="Capture stdout and stderr."),
2328
):
2429
"""Execute a command using the CLI and create a Task to track its outcome.
@@ -34,6 +39,7 @@ def run(
3439
"""
3540
configure_api(ctx)
3641
actions = get_actions(action_def)
42+
tags = merge_kv_json(tag, "")
3743
stale_timeout = update_frequency * 2
3844
with Session():
3945
try:
@@ -43,6 +49,7 @@ def run(
4349
stale_timeout=stale_timeout,
4450
actions=actions,
4551
monitor_id=monitor_id,
52+
tags=tags,
4653
)
4754
except Exception as e:
4855
err_console.print(f"Error creating task: {e}")

taskbadger/sdk.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
)
1919
from taskbadger.internal.models import (
2020
PatchedTaskRequest,
21+
PatchedTaskRequestTags,
2122
StatusEnum,
2223
TaskRequest,
24+
TaskRequestTags,
2325
)
2426
from taskbadger.internal.types import UNSET
2527
from taskbadger.mug import Badger, Session, Settings
@@ -95,6 +97,7 @@ def create_task(
9597
stale_timeout: int = None,
9698
actions: list[Action] = None,
9799
monitor_id: str = None,
100+
tags: dict[str, str] = None,
98101
) -> "Task":
99102
"""Create a Task.
100103
@@ -108,6 +111,7 @@ def create_task(
108111
stale_timeout: Maximum allowed time between updates (seconds).
109112
actions: Task actions.
110113
monitor_id: ID of the monitor to associate this task with.
114+
tags: Dictionary of namespace -> value tags.
111115
112116
Returns:
113117
Task: The created Task object.
@@ -132,6 +136,8 @@ def create_task(
132136
task.data = {**scope_data, **data}
133137
if actions:
134138
task.additional_properties = {"actions": [a.to_dict() for a in actions]}
139+
if tags:
140+
task.tags = TaskRequestTags.from_dict(tags)
135141
kwargs = _make_args(body=task)
136142
if monitor_id:
137143
kwargs["x_taskbadger_monitor"] = monitor_id
@@ -151,6 +157,7 @@ def update_task(
151157
max_runtime: int = None,
152158
stale_timeout: int = None,
153159
actions: list[Action] = None,
160+
tags: dict[str, str] = None,
154161
) -> "Task":
155162
"""Update a task.
156163
Requires only the task ID and fields to update.
@@ -165,6 +172,7 @@ def update_task(
165172
max_runtime: Maximum expected runtime (seconds).
166173
stale_timeout: Maximum allowed time between updates (seconds).
167174
actions: Task actions.
175+
tags: Dictionary of namespace -> value tags.
168176
169177
Returns:
170178
Task: The updated Task object.
@@ -189,6 +197,8 @@ def update_task(
189197
)
190198
if actions:
191199
body.additional_properties = {"actions": [a.to_dict() for a in actions]}
200+
if tags:
201+
body.tags = PatchedTaskRequestTags.from_dict(tags)
192202
kwargs = _make_args(id=task_id, body=body)
193203
with Session() as client:
194204
response = task_partial_update.sync_detailed(client=client, **kwargs)
@@ -245,6 +255,7 @@ def create(
245255
stale_timeout: int = None,
246256
actions: list[Action] = None,
247257
monitor_id: str = None,
258+
tags: dict[str, str] = None,
248259
) -> "Task":
249260
"""Create a new task"""
250261
return create_task(
@@ -257,6 +268,7 @@ def create(
257268
stale_timeout=stale_timeout,
258269
actions=actions,
259270
monitor_id=monitor_id,
271+
tags=tags,
260272
)
261273

262274
def __init__(self, task):
@@ -321,6 +333,7 @@ def update(
321333
max_runtime: int = None,
322334
stale_timeout: int = None,
323335
actions: list[Action] = None,
336+
tags: dict[str, str] = None,
324337
data_merge_strategy: Any = None,
325338
):
326339
"""Generic update method used to update any of the task fields.
@@ -345,13 +358,18 @@ def update(
345358
max_runtime=max_runtime,
346359
stale_timeout=stale_timeout,
347360
actions=actions,
361+
tags=tags,
348362
)
349363
self._task = task._task
350364

351365
def add_actions(self, actions: list[Action]):
352366
"""Add actions to the task."""
353367
self.update(actions=actions)
354368

369+
def tag(self, tags: dict[str, str]):
370+
"""Add tags to the task."""
371+
self.update(tags=tags)
372+
355373
def ping(self):
356374
"""Update the task without changing any values. This can be used in conjunction
357375
with 'stale_timeout' to indicate that the task is still running."""

tests/test_cli_create_update.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
from taskbadger.cli_main import app
99
from taskbadger.internal.models import (
1010
PatchedTaskRequest,
11+
PatchedTaskRequestTags,
1112
StatusEnum,
1213
TaskRequest,
14+
TaskRequestTags,
1315
)
1416
from taskbadger.internal.types import Response
1517
from tests.utils import task_for_test
@@ -47,6 +49,10 @@ def test_cli_create():
4749
"b=2",
4850
"--metadata",
4951
"a=3",
52+
"--tag",
53+
"name=foo",
54+
"--tag",
55+
"bar=baz",
5056
]
5157
result = runner.invoke(app, args, catch_exceptions=False)
5258
assert result.exit_code == 0, result.output
@@ -56,6 +62,7 @@ def test_cli_create():
5662
status=StatusEnum.PROCESSING,
5763
value_max=100,
5864
data={"b": "2", "a": 1, "c": 1},
65+
tags=TaskRequestTags.from_dict({"name": "foo", "bar": "baz"}),
5966
)
6067
create.assert_called_with(
6168
client=mock.ANY,
@@ -72,12 +79,14 @@ def test_cli_update():
7279

7380
result = runner.invoke(
7481
app,
75-
["update", "task123", "--status=success", "--value", "100"],
82+
["update", "task123", "--status=success", "--value", "100", "--tag", "name=foo"],
7683
catch_exceptions=False,
7784
)
7885
assert result.exit_code == 0, result.output
7986

80-
body = PatchedTaskRequest(status=StatusEnum.SUCCESS, value=100)
87+
body = PatchedTaskRequest(
88+
status=StatusEnum.SUCCESS, value=100, tags=PatchedTaskRequestTags.from_dict({"name": "foo"})
89+
)
8190

8291
update.assert_called_with(
8392
client=mock.ANY,

tests/test_cli_run.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
PatchedTaskRequest,
1111
StatusEnum,
1212
TaskRequest,
13+
TaskRequestTags,
1314
)
1415
from taskbadger.internal.types import UNSET, Response
1516
from taskbadger.mug import Badger
@@ -37,6 +38,15 @@ def test_cli_run_success():
3738
_test_cli_run(["echo", "test"], 0, args=["task_name"])
3839

3940

41+
def test_cli_run_tags():
42+
_test_cli_run(
43+
["echo", "test"],
44+
0,
45+
["task_name", "--tag", "name=value", "--tag", "name1=value1"],
46+
tags={"name": "value", "name1": "value1"},
47+
)
48+
49+
4050
def test_cli_long_run():
4151
def _should_update_task(last_update, update_frequency_seconds):
4252
return True
@@ -106,7 +116,7 @@ def test_cli_run_webhook():
106116
)
107117

108118

109-
def _test_cli_run(command, return_code, args=None, action=None, update_call_count=1):
119+
def _test_cli_run(command, return_code, args=None, action=None, tags=None, update_call_count=1):
110120
update_mock = mock.MagicMock()
111121

112122
def _update(*args, **kwargs):
@@ -133,6 +143,8 @@ def _update(*args, **kwargs):
133143
request = TaskRequest(name="task_name", status=StatusEnum.PROCESSING, stale_timeout=10)
134144
if action:
135145
request.additional_properties = {"actions": [action]}
146+
if tags:
147+
request.tags = TaskRequestTags.from_dict(tags)
136148
create.assert_called_with(
137149
client=mock.ANY,
138150
organization_slug="org",

tests/test_sdk_primatives.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
@pytest.fixture(autouse=True)
11-
def init_skd():
11+
def _init_skd():
1212
init("org", "project", "token")
1313

1414

@@ -75,6 +75,7 @@ def test_update_task(httpx_mock):
7575
"value": 150,
7676
"value_max": 150,
7777
"data": {"custom": "value"},
78+
"tags": {"tag": "value"},
7879
}
7980
httpx_mock.add_response(
8081
url="https://taskbadger.net/api/org/project/tasks/test_id/",

0 commit comments

Comments
 (0)