taskbadger 1.3.4__tar.gz → 1.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- taskbadger-1.5.0/.gitignore +25 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/PKG-INFO +16 -22
- taskbadger-1.5.0/pyproject.toml +100 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/__init__.py +18 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/celery.py +32 -3
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/cli/__init__.py +2 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/cli/basics.py +50 -9
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/cli/list_tasks.py +16 -2
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/cli/utils.py +1 -1
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/cli/wrapper.py +15 -5
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/cli_main.py +8 -4
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/config.py +26 -9
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/decorators.py +19 -3
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/exceptions.py +4 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/integrations.py +7 -7
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/__init__.py +2 -1
- taskbadger-1.5.0/taskbadger/internal/api/__init__.py +1 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/action_endpoints/action_cancel.py +13 -17
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/action_endpoints/action_create.py +27 -26
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/action_endpoints/action_get.py +17 -21
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/action_endpoints/action_list.py +17 -21
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/action_endpoints/action_partial_update.py +37 -36
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/action_endpoints/action_update.py +37 -36
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/task_endpoints/task_cancel.py +13 -16
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/task_endpoints/task_create.py +37 -35
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/task_endpoints/task_get.py +17 -20
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/task_endpoints/task_list.py +26 -28
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/task_endpoints/task_partial_update.py +37 -35
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/task_endpoints/task_update.py +37 -35
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/client.py +21 -21
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/errors.py +4 -2
- taskbadger-1.5.0/taskbadger/internal/models/__init__.py +27 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/models/action.py +15 -23
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/models/action_request.py +12 -23
- taskbadger-1.5.0/taskbadger/internal/models/paginated_task_list.py +114 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/models/patched_action_request.py +12 -23
- taskbadger-1.5.0/taskbadger/internal/models/patched_task_request.py +249 -0
- taskbadger-1.5.0/taskbadger/internal/models/patched_task_request_tags.py +43 -0
- taskbadger-1.5.0/taskbadger/internal/models/task.py +320 -0
- taskbadger-1.5.0/taskbadger/internal/models/task_request.py +250 -0
- taskbadger-1.5.0/taskbadger/internal/models/task_request_tags.py +43 -0
- taskbadger-1.5.0/taskbadger/internal/models/task_tags.py +43 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/types.py +6 -4
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/mug.py +27 -9
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/sdk.py +100 -37
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/systems/__init__.py +1 -1
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/systems/celery.py +3 -1
- taskbadger-1.5.0/taskbadger/utils.py +15 -0
- taskbadger-1.3.4/pyproject.toml +0 -87
- taskbadger-1.3.4/taskbadger/internal/api/__init__.py +0 -1
- taskbadger-1.3.4/taskbadger/internal/models/__init__.py +0 -33
- taskbadger-1.3.4/taskbadger/internal/models/action_config.py +0 -44
- taskbadger-1.3.4/taskbadger/internal/models/action_request_config.py +0 -44
- taskbadger-1.3.4/taskbadger/internal/models/paginated_task_list.py +0 -91
- taskbadger-1.3.4/taskbadger/internal/models/patched_action_request_config.py +0 -44
- taskbadger-1.3.4/taskbadger/internal/models/patched_task_request.py +0 -173
- taskbadger-1.3.4/taskbadger/internal/models/patched_task_request_data.py +0 -44
- taskbadger-1.3.4/taskbadger/internal/models/task.py +0 -233
- taskbadger-1.3.4/taskbadger/internal/models/task_data.py +0 -44
- taskbadger-1.3.4/taskbadger/internal/models/task_request.py +0 -175
- taskbadger-1.3.4/taskbadger/internal/models/task_request_data.py +0 -44
- {taskbadger-1.3.4 → taskbadger-1.5.0}/LICENSE +0 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/README.md +0 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/action_endpoints/__init__.py +0 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/api/task_endpoints/__init__.py +0 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/models/status_enum.py +0 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/internal/py.typed +0 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/process.py +0 -0
- {taskbadger-1.3.4 → taskbadger-1.5.0}/taskbadger/safe_sdk.py +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
build/
|
|
3
|
+
dist/
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
|
|
7
|
+
# pyenv
|
|
8
|
+
.python-version
|
|
9
|
+
|
|
10
|
+
# Environments
|
|
11
|
+
.env
|
|
12
|
+
.venv
|
|
13
|
+
.envrc
|
|
14
|
+
.env.integration
|
|
15
|
+
|
|
16
|
+
# mypy
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.dmypy.json
|
|
19
|
+
dmypy.json
|
|
20
|
+
|
|
21
|
+
# JetBrains
|
|
22
|
+
.idea/
|
|
23
|
+
|
|
24
|
+
/coverage.xml
|
|
25
|
+
/.coverage
|
|
@@ -1,37 +1,32 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: taskbadger
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: The official Python SDK for Task Badger
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Project-URL: Changelog, https://github.com/taskbadger/taskbadger-python/releases
|
|
6
|
+
Project-URL: homepage, https://taskbadger.net/
|
|
7
|
+
Project-URL: repository, https://github.com/taskbadger/taskbadger-python
|
|
8
|
+
Project-URL: documentation, https://docs.taskbadger.net/
|
|
9
|
+
License-File: LICENSE
|
|
8
10
|
Classifier: Development Status :: 4 - Beta
|
|
9
11
|
Classifier: Environment :: Web Environment
|
|
10
12
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
13
|
Classifier: Operating System :: OS Independent
|
|
13
14
|
Classifier: Programming Language :: Python
|
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.9
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
22
19
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Requires-Dist: attrs>=21.3.0
|
|
22
|
+
Requires-Dist: httpx<0.28.0,>=0.20.0
|
|
23
|
+
Requires-Dist: importlib-metadata>=1.0; python_version < '3.8'
|
|
24
|
+
Requires-Dist: python-dateutil>=2.8.0
|
|
25
|
+
Requires-Dist: tomlkit>=0.12.5
|
|
26
|
+
Requires-Dist: typer[all]<0.10.0
|
|
27
|
+
Requires-Dist: typing-extensions>=4.7.1; python_version <= '3.9'
|
|
23
28
|
Provides-Extra: celery
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist: celery (>=4.0.0,<6.0.0) ; extra == "celery"
|
|
26
|
-
Requires-Dist: httpx (>=0.20.0,<0.28.0)
|
|
27
|
-
Requires-Dist: importlib-metadata (>=1.0,<2.0) ; python_version < "3.8"
|
|
28
|
-
Requires-Dist: python-dateutil (>=2.8.0,<3.0.0)
|
|
29
|
-
Requires-Dist: tomlkit (>=0.12.5,<0.13.0)
|
|
30
|
-
Requires-Dist: typer[all] (<0.10.0)
|
|
31
|
-
Requires-Dist: typing-extensions (>=4.7.1,<5.0.0) ; python_version == "3.9"
|
|
32
|
-
Project-URL: Changelog, https://github.com/taskbadger/taskbadger-python/releases
|
|
33
|
-
Project-URL: Documentation, https://docs.taskbadger.net/
|
|
34
|
-
Project-URL: Repository, https://github.com/taskbadger/taskbadger-python
|
|
29
|
+
Requires-Dist: celery<6.0.0,>=4.0.0; extra == 'celery'
|
|
35
30
|
Description-Content-Type: text/markdown
|
|
36
31
|
|
|
37
32
|
# Task Badger Python Client
|
|
@@ -143,4 +138,3 @@ $ taskbadger run "demo task" --action error email to:me@test.com -- path/to/scri
|
|
|
143
138
|
|
|
144
139
|
Task created: https://taskbadger.net/public/tasks/xyz/
|
|
145
140
|
```
|
|
146
|
-
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "taskbadger"
|
|
3
|
+
version = "1.5.0"
|
|
4
|
+
description = "The official Python SDK for Task Badger"
|
|
5
|
+
requires-python = ">=3.9"
|
|
6
|
+
authors = []
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Development Status :: 4 - Beta",
|
|
10
|
+
"Environment :: Web Environment",
|
|
11
|
+
"Intended Audience :: Developers",
|
|
12
|
+
"Operating System :: OS Independent",
|
|
13
|
+
"Programming Language :: Python",
|
|
14
|
+
"Programming Language :: Python :: 3.9",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
dependencies = [
|
|
22
|
+
"httpx >=0.20.0,<0.28.0",
|
|
23
|
+
"attrs >=21.3.0",
|
|
24
|
+
"python-dateutil >=2.8.0",
|
|
25
|
+
"typer[all] <0.10.0",
|
|
26
|
+
"tomlkit >=0.12.5",
|
|
27
|
+
"importlib-metadata >=1.0; python_version < '3.8'",
|
|
28
|
+
"typing-extensions >=4.7.1; python_version <= '3.9'",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["hatchling"]
|
|
33
|
+
build-backend = "hatchling.build"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.sdist]
|
|
36
|
+
include = [
|
|
37
|
+
"taskbadger",
|
|
38
|
+
"taskbadger/internal/py.typed",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
celery = [
|
|
43
|
+
"celery>=4.0.0,<6.0.0",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[tool.uv]
|
|
47
|
+
package = true
|
|
48
|
+
|
|
49
|
+
[project.urls]
|
|
50
|
+
"Changelog" = "https://github.com/taskbadger/taskbadger-python/releases"
|
|
51
|
+
homepage = "https://taskbadger.net/"
|
|
52
|
+
repository = "https://github.com/taskbadger/taskbadger-python"
|
|
53
|
+
documentation = "https://docs.taskbadger.net/"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
[dependency-groups]
|
|
57
|
+
dev = [
|
|
58
|
+
"black",
|
|
59
|
+
"isort",
|
|
60
|
+
"pre-commit",
|
|
61
|
+
"pytest",
|
|
62
|
+
"pytest-httpx",
|
|
63
|
+
"invoke",
|
|
64
|
+
"pytest-celery",
|
|
65
|
+
"redis",
|
|
66
|
+
"openapi-python-client",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[project.scripts]
|
|
70
|
+
taskbadger = "taskbadger.cli_main:app"
|
|
71
|
+
|
|
72
|
+
[tool.pytest.ini_options]
|
|
73
|
+
# don't run integration tests unless specifically requested
|
|
74
|
+
norecursedirs = ".* integration_tests"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
[tool.ruff]
|
|
78
|
+
exclude = [
|
|
79
|
+
".venv",
|
|
80
|
+
".git",
|
|
81
|
+
".ruff_cache",
|
|
82
|
+
]
|
|
83
|
+
line-length = 120
|
|
84
|
+
indent-width = 4
|
|
85
|
+
target-version = "py39"
|
|
86
|
+
|
|
87
|
+
[tool.ruff.lint]
|
|
88
|
+
select = ["E", "F", "I", "UP", "DJ", "PT"]
|
|
89
|
+
fixable = ["ALL"]
|
|
90
|
+
unfixable = []
|
|
91
|
+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
92
|
+
|
|
93
|
+
[tool.ruff.format]
|
|
94
|
+
quote-style = "double"
|
|
95
|
+
indent-style = "space"
|
|
96
|
+
skip-magic-trailing-comma = false
|
|
97
|
+
line-ending = "auto"
|
|
98
|
+
|
|
99
|
+
[tool.ruff.lint.per-file-ignores]
|
|
100
|
+
"taskbadger/internal/*" = ["E501"]
|
|
@@ -5,6 +5,24 @@ from .mug import Badger, Session
|
|
|
5
5
|
from .safe_sdk import create_task_safe, update_task_safe
|
|
6
6
|
from .sdk import DefaultMergeStrategy, Task, create_task, get_task, init, update_task
|
|
7
7
|
|
|
8
|
+
__all__ = [
|
|
9
|
+
"track",
|
|
10
|
+
"Action",
|
|
11
|
+
"EmailIntegration",
|
|
12
|
+
"WebhookIntegration",
|
|
13
|
+
"StatusEnum",
|
|
14
|
+
"Badger",
|
|
15
|
+
"Session",
|
|
16
|
+
"create_task_safe",
|
|
17
|
+
"update_task_safe",
|
|
18
|
+
"DefaultMergeStrategy",
|
|
19
|
+
"Task",
|
|
20
|
+
"create_task",
|
|
21
|
+
"get_task",
|
|
22
|
+
"init",
|
|
23
|
+
"update_task",
|
|
24
|
+
]
|
|
25
|
+
|
|
8
26
|
try:
|
|
9
27
|
import importlib.metadata as importlib_metadata
|
|
10
28
|
except ModuleNotFoundError:
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import collections
|
|
2
2
|
import functools
|
|
3
|
+
import json
|
|
3
4
|
import logging
|
|
4
5
|
|
|
5
6
|
import celery
|
|
6
|
-
from celery.signals import
|
|
7
|
+
from celery.signals import (
|
|
8
|
+
before_task_publish,
|
|
9
|
+
task_failure,
|
|
10
|
+
task_prerun,
|
|
11
|
+
task_retry,
|
|
12
|
+
task_success,
|
|
13
|
+
)
|
|
14
|
+
from kombu import serialization
|
|
7
15
|
|
|
8
16
|
from .internal.models import StatusEnum
|
|
9
17
|
from .mug import Badger
|
|
@@ -12,10 +20,15 @@ from .sdk import DefaultMergeStrategy, get_task
|
|
|
12
20
|
|
|
13
21
|
KWARG_PREFIX = "taskbadger_"
|
|
14
22
|
TB_KWARGS_ARG = f"{KWARG_PREFIX}kwargs"
|
|
15
|
-
IGNORE_ARGS = {TB_KWARGS_ARG, f"{KWARG_PREFIX}task", f"{KWARG_PREFIX}task_id"}
|
|
23
|
+
IGNORE_ARGS = {TB_KWARGS_ARG, f"{KWARG_PREFIX}task", f"{KWARG_PREFIX}task_id", f"{KWARG_PREFIX}record_task_args"}
|
|
16
24
|
TB_TASK_ID = f"{KWARG_PREFIX}task_id"
|
|
17
25
|
|
|
18
|
-
TERMINAL_STATES = {
|
|
26
|
+
TERMINAL_STATES = {
|
|
27
|
+
StatusEnum.SUCCESS,
|
|
28
|
+
StatusEnum.ERROR,
|
|
29
|
+
StatusEnum.CANCELLED,
|
|
30
|
+
StatusEnum.STALE,
|
|
31
|
+
}
|
|
19
32
|
|
|
20
33
|
log = logging.getLogger("taskbadger")
|
|
21
34
|
|
|
@@ -113,6 +126,8 @@ class Task(celery.Task):
|
|
|
113
126
|
if Badger.is_configured():
|
|
114
127
|
headers["taskbadger_track"] = True
|
|
115
128
|
headers[TB_KWARGS_ARG] = tb_kwargs
|
|
129
|
+
if "record_task_args" in tb_kwargs:
|
|
130
|
+
headers["taskbadger_record_task_args"] = tb_kwargs.pop("record_task_args")
|
|
116
131
|
|
|
117
132
|
result = super().apply_async(*args, **kwargs)
|
|
118
133
|
|
|
@@ -176,6 +191,20 @@ def task_publish_handler(sender=None, headers=None, body=None, **kwargs):
|
|
|
176
191
|
kwargs["status"] = StatusEnum.PENDING
|
|
177
192
|
name = kwargs.pop("name", headers["task"])
|
|
178
193
|
|
|
194
|
+
global_record_task_args = celery_system and celery_system.record_task_args
|
|
195
|
+
if headers.get("taskbadger_record_task_args", global_record_task_args):
|
|
196
|
+
data = {
|
|
197
|
+
"celery_task_args": body[0],
|
|
198
|
+
"celery_task_kwargs": body[1],
|
|
199
|
+
}
|
|
200
|
+
try:
|
|
201
|
+
_, _, value = serialization.dumps(data, serializer="json")
|
|
202
|
+
data = json.loads(value)
|
|
203
|
+
except Exception:
|
|
204
|
+
log.error("Error serializing task arguments for task '%s'", name)
|
|
205
|
+
else:
|
|
206
|
+
kwargs.setdefault("data", {}).update(data)
|
|
207
|
+
|
|
179
208
|
task = create_task_safe(name, **kwargs)
|
|
180
209
|
if task:
|
|
181
210
|
meta = {TB_TASK_ID: task.id}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import csv
|
|
2
2
|
import json
|
|
3
3
|
import sys
|
|
4
|
-
from typing import Tuple
|
|
5
4
|
|
|
6
5
|
import typer
|
|
7
6
|
from rich import print
|
|
8
7
|
|
|
9
8
|
from taskbadger import StatusEnum, create_task, get_task, update_task
|
|
10
|
-
from taskbadger.cli.utils import
|
|
9
|
+
from taskbadger.cli.utils import (
|
|
10
|
+
OutputFormat,
|
|
11
|
+
configure_api,
|
|
12
|
+
err_console,
|
|
13
|
+
get_actions,
|
|
14
|
+
merge_kv_json,
|
|
15
|
+
)
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
def get(
|
|
@@ -29,14 +34,22 @@ def get(
|
|
|
29
34
|
elif output_format == OutputFormat.csv:
|
|
30
35
|
writer = csv.writer(sys.stdout)
|
|
31
36
|
writer.writerow("Task ID,Created,Name,Status,Percent".split(","))
|
|
32
|
-
writer.writerow(
|
|
37
|
+
writer.writerow(
|
|
38
|
+
[
|
|
39
|
+
task.id,
|
|
40
|
+
task.created.isoformat(),
|
|
41
|
+
task.name,
|
|
42
|
+
task.status,
|
|
43
|
+
str(task.value_percent),
|
|
44
|
+
]
|
|
45
|
+
)
|
|
33
46
|
|
|
34
47
|
|
|
35
48
|
def create(
|
|
36
49
|
ctx: typer.Context,
|
|
37
50
|
name: str = typer.Argument(..., show_default=False, help="The task name."),
|
|
38
51
|
monitor_id: str = typer.Option(None, help="Associate this task with a monitor."),
|
|
39
|
-
action_def:
|
|
52
|
+
action_def: tuple[str, str, str] = typer.Option(
|
|
40
53
|
(None, None, None),
|
|
41
54
|
"--action",
|
|
42
55
|
"-a",
|
|
@@ -52,14 +65,27 @@ def create(
|
|
|
52
65
|
help="Metadata 'key=value' pair to associate with the task. Can be specified multiple times.",
|
|
53
66
|
),
|
|
54
67
|
metadata_json: str = typer.Option(
|
|
55
|
-
None,
|
|
68
|
+
None,
|
|
69
|
+
show_default=False,
|
|
70
|
+
help="Metadata to associate with the task. Must be valid JSON.",
|
|
71
|
+
),
|
|
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.",
|
|
56
81
|
),
|
|
57
82
|
quiet: bool = typer.Option(False, "--quiet", "-q", help="Minimal output. Only the Task ID."),
|
|
58
83
|
):
|
|
59
84
|
"""Create a task."""
|
|
60
85
|
configure_api(ctx)
|
|
61
86
|
actions = get_actions(action_def)
|
|
62
|
-
metadata =
|
|
87
|
+
metadata = merge_kv_json(metadata, metadata_json)
|
|
88
|
+
tags = merge_kv_json(tag, tags_json)
|
|
63
89
|
|
|
64
90
|
try:
|
|
65
91
|
task = create_task(
|
|
@@ -69,6 +95,7 @@ def create(
|
|
|
69
95
|
data=metadata,
|
|
70
96
|
actions=actions,
|
|
71
97
|
monitor_id=monitor_id,
|
|
98
|
+
tags=tags,
|
|
72
99
|
)
|
|
73
100
|
except Exception as e:
|
|
74
101
|
err_console.print(f"Error creating task: {e}")
|
|
@@ -83,7 +110,7 @@ def update(
|
|
|
83
110
|
ctx: typer.Context,
|
|
84
111
|
task_id: str = typer.Argument(..., show_default=False, help="The ID of the task to update."),
|
|
85
112
|
name: str = typer.Option(None, show_default=False, help="Update the name of the task."),
|
|
86
|
-
action_def:
|
|
113
|
+
action_def: tuple[str, str, str] = typer.Option(
|
|
87
114
|
(None, None, None),
|
|
88
115
|
"--action",
|
|
89
116
|
"-a",
|
|
@@ -100,14 +127,27 @@ def update(
|
|
|
100
127
|
help="Metadata 'key=value' pair to associate with the task. Can be specified multiple times.",
|
|
101
128
|
),
|
|
102
129
|
metadata_json: str = typer.Option(
|
|
103
|
-
None,
|
|
130
|
+
None,
|
|
131
|
+
show_default=False,
|
|
132
|
+
help="Metadata to associate with the task. Must be valid JSON.",
|
|
133
|
+
),
|
|
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.",
|
|
104
143
|
),
|
|
105
144
|
quiet: bool = typer.Option(False, "--quiet", "-q", help="No output."),
|
|
106
145
|
):
|
|
107
146
|
"""Update a task."""
|
|
108
147
|
configure_api(ctx)
|
|
109
148
|
actions = get_actions(action_def)
|
|
110
|
-
metadata =
|
|
149
|
+
metadata = merge_kv_json(metadata, metadata_json)
|
|
150
|
+
tags = merge_kv_json(tag, tags_json)
|
|
111
151
|
|
|
112
152
|
try:
|
|
113
153
|
task = update_task(
|
|
@@ -118,6 +158,7 @@ def update(
|
|
|
118
158
|
value_max=value_max,
|
|
119
159
|
data=metadata,
|
|
120
160
|
actions=actions,
|
|
161
|
+
tags=tags,
|
|
121
162
|
)
|
|
122
163
|
except Exception as e:
|
|
123
164
|
err_console.print(f"Error creating task: {e}")
|
|
@@ -47,7 +47,13 @@ def _render_pretty(ctx, result):
|
|
|
47
47
|
table.add_column("Percent", no_wrap=True)
|
|
48
48
|
|
|
49
49
|
for task in result.results:
|
|
50
|
-
table.add_row(
|
|
50
|
+
table.add_row(
|
|
51
|
+
task.id,
|
|
52
|
+
task.created.isoformat(),
|
|
53
|
+
task.name,
|
|
54
|
+
task.status,
|
|
55
|
+
str(task.value_percent),
|
|
56
|
+
)
|
|
51
57
|
Console().print(table)
|
|
52
58
|
|
|
53
59
|
cursor = _get_cursor(result.next_)
|
|
@@ -59,7 +65,15 @@ def _render_csv(ctx, result):
|
|
|
59
65
|
writer = csv.writer(sys.stdout)
|
|
60
66
|
writer.writerow("Task ID,Created,Name,Status,Percent".split(","))
|
|
61
67
|
for task in result.results:
|
|
62
|
-
writer.writerow(
|
|
68
|
+
writer.writerow(
|
|
69
|
+
[
|
|
70
|
+
task.id,
|
|
71
|
+
task.created.isoformat(),
|
|
72
|
+
task.name,
|
|
73
|
+
task.status,
|
|
74
|
+
str(task.value_percent),
|
|
75
|
+
]
|
|
76
|
+
)
|
|
63
77
|
|
|
64
78
|
cursor = _get_cursor(result.next_)
|
|
65
79
|
if cursor:
|
|
@@ -28,7 +28,7 @@ def get_actions(action_def: tuple[str, str, str]) -> list[Action]:
|
|
|
28
28
|
return []
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def
|
|
31
|
+
def merge_kv_json(metadata_kv: list[str], metadata_json: str) -> dict:
|
|
32
32
|
metadata = {}
|
|
33
33
|
for kv in metadata_kv:
|
|
34
34
|
k, v = kv.strip().split("=", 1)
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
from typing import Tuple
|
|
2
|
-
|
|
3
1
|
import typer
|
|
4
2
|
from rich import print
|
|
5
3
|
|
|
6
4
|
from taskbadger import DefaultMergeStrategy, Session, StatusEnum, Task
|
|
7
|
-
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
|
|
8
6
|
from taskbadger.process import ProcessRunner
|
|
9
7
|
|
|
10
8
|
|
|
@@ -13,7 +11,7 @@ def run(
|
|
|
13
11
|
name: str = typer.Argument(..., show_default=False, help="The task name"),
|
|
14
12
|
monitor_id: str = typer.Option(None, help="Associate this task with a monitor."),
|
|
15
13
|
update_frequency: int = typer.Option(5, metavar="SECONDS", min=5, max=300, help="Seconds between updates."),
|
|
16
|
-
action_def:
|
|
14
|
+
action_def: tuple[str, str, str] = typer.Option(
|
|
17
15
|
(None, None, None),
|
|
18
16
|
"--action",
|
|
19
17
|
"-a",
|
|
@@ -21,6 +19,11 @@ def run(
|
|
|
21
19
|
show_default=False,
|
|
22
20
|
help="Action definition e.g. 'success,error email to:me@email.com'",
|
|
23
21
|
),
|
|
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
|
+
),
|
|
24
27
|
capture_output: bool = typer.Option(False, help="Capture stdout and stderr."),
|
|
25
28
|
):
|
|
26
29
|
"""Execute a command using the CLI and create a Task to track its outcome.
|
|
@@ -36,6 +39,7 @@ def run(
|
|
|
36
39
|
"""
|
|
37
40
|
configure_api(ctx)
|
|
38
41
|
actions = get_actions(action_def)
|
|
42
|
+
tags = merge_kv_json(tag, "")
|
|
39
43
|
stale_timeout = update_frequency * 2
|
|
40
44
|
with Session():
|
|
41
45
|
try:
|
|
@@ -45,6 +49,7 @@ def run(
|
|
|
45
49
|
stale_timeout=stale_timeout,
|
|
46
50
|
actions=actions,
|
|
47
51
|
monitor_id=monitor_id,
|
|
52
|
+
tags=tags,
|
|
48
53
|
)
|
|
49
54
|
except Exception as e:
|
|
50
55
|
err_console.print(f"Error creating task: {e}")
|
|
@@ -53,7 +58,12 @@ def run(
|
|
|
53
58
|
print(f"Task created: {task.public_url}")
|
|
54
59
|
env = {"TASKBADGER_TASK_ID": task.id} if task else None
|
|
55
60
|
try:
|
|
56
|
-
process = ProcessRunner(
|
|
61
|
+
process = ProcessRunner(
|
|
62
|
+
ctx.args,
|
|
63
|
+
env,
|
|
64
|
+
capture_output=capture_output,
|
|
65
|
+
update_frequency=update_frequency,
|
|
66
|
+
)
|
|
57
67
|
for output in process.run():
|
|
58
68
|
_update_task(task, **(output or {}))
|
|
59
69
|
except Exception as e:
|
|
@@ -30,9 +30,9 @@ def version_callback(value: bool):
|
|
|
30
30
|
def configure(ctx: typer.Context):
|
|
31
31
|
"""Update CLI configuration."""
|
|
32
32
|
config = ctx.meta["tb_config"]
|
|
33
|
-
config.organization_slug = typer.prompt(
|
|
34
|
-
config.project_slug = typer.prompt(
|
|
35
|
-
config.token = typer.prompt(
|
|
33
|
+
config.organization_slug = typer.prompt("Organization slug", default=config.organization_slug)
|
|
34
|
+
config.project_slug = typer.prompt("Project slug", default=config.project_slug)
|
|
35
|
+
config.token = typer.prompt("API Key", default=config.token)
|
|
36
36
|
path = write_config(config)
|
|
37
37
|
print(f"Config written to [green]{path}[/green]")
|
|
38
38
|
|
|
@@ -71,7 +71,11 @@ def main(
|
|
|
71
71
|
help="Project Slug. This will override values from the config file and environment variables.",
|
|
72
72
|
),
|
|
73
73
|
version: Optional[bool] = typer.Option( # noqa
|
|
74
|
-
None,
|
|
74
|
+
None,
|
|
75
|
+
"--version",
|
|
76
|
+
callback=version_callback,
|
|
77
|
+
is_eager=True,
|
|
78
|
+
help="Show CLI Version",
|
|
75
79
|
),
|
|
76
80
|
):
|
|
77
81
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
-
import inspect
|
|
3
2
|
import os
|
|
3
|
+
import textwrap
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
import tomlkit
|
|
@@ -18,6 +18,7 @@ class Config:
|
|
|
18
18
|
organization_slug: str = None
|
|
19
19
|
project_slug: str = None
|
|
20
20
|
host: str = _TB_HOST
|
|
21
|
+
tags: dict = None
|
|
21
22
|
|
|
22
23
|
def is_valid(self):
|
|
23
24
|
return bool(self.token and self.organization_slug and self.project_slug)
|
|
@@ -51,18 +52,26 @@ class Config:
|
|
|
51
52
|
organization_slug=overrides.get("org") or _from_env("ORG", defaults.get("org")),
|
|
52
53
|
project_slug=overrides.get("project") or _from_env("PROJECT", defaults.get("project")),
|
|
53
54
|
host=overrides.get("host") or auth.get("host"),
|
|
55
|
+
tags=config_dict.get("tags", {}),
|
|
54
56
|
)
|
|
55
57
|
|
|
56
58
|
def __str__(self):
|
|
57
59
|
host = ""
|
|
58
|
-
if self.host != _TB_HOST:
|
|
59
|
-
host = f"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
if self.host and self.host != _TB_HOST:
|
|
61
|
+
host = f"Host: {self.host or '-'}\n"
|
|
62
|
+
tags = ""
|
|
63
|
+
if self.tags:
|
|
64
|
+
tags = "Tags:\n " + "\n ".join(f"{k}: {v}" for k, v in self.tags.items())
|
|
65
|
+
return (
|
|
66
|
+
textwrap.dedent(
|
|
67
|
+
f"""
|
|
68
|
+
Organization slug: {self.organization_slug or "-"}
|
|
69
|
+
Project slug: {self.project_slug or "-"}
|
|
70
|
+
Auth token: {self.token or "-"}
|
|
65
71
|
"""
|
|
72
|
+
)
|
|
73
|
+
+ host
|
|
74
|
+
+ tags
|
|
66
75
|
)
|
|
67
76
|
|
|
68
77
|
|
|
@@ -73,9 +82,17 @@ def _from_env(name, default=None, prefix="TASKBADGER_"):
|
|
|
73
82
|
def write_config(config):
|
|
74
83
|
doc = document()
|
|
75
84
|
|
|
76
|
-
doc.add(
|
|
85
|
+
doc.add(
|
|
86
|
+
"defaults",
|
|
87
|
+
table().add("org", config.organization_slug).add("project", config.project_slug),
|
|
88
|
+
)
|
|
77
89
|
|
|
78
90
|
doc.add("auth", table().add("token", config.token))
|
|
91
|
+
if config.tags:
|
|
92
|
+
tags = table()
|
|
93
|
+
for key, value in config.tags.items():
|
|
94
|
+
tags.add(key, value)
|
|
95
|
+
doc.add("tags", tags)
|
|
79
96
|
|
|
80
97
|
config_path = _get_config_path()
|
|
81
98
|
if not config_path.parent.exists():
|
|
@@ -8,7 +8,14 @@ from .sdk import StatusEnum
|
|
|
8
8
|
log = logging.getLogger("taskbadger")
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def track(
|
|
11
|
+
def track(
|
|
12
|
+
func=None,
|
|
13
|
+
*,
|
|
14
|
+
name: str = None,
|
|
15
|
+
monitor_id: str = None,
|
|
16
|
+
max_runtime: int = None,
|
|
17
|
+
**task_kwargs,
|
|
18
|
+
):
|
|
12
19
|
"""
|
|
13
20
|
Decorator to track a function as a task.
|
|
14
21
|
|
|
@@ -39,12 +46,21 @@ def track(func=None, *, name: str = None, monitor_id: str = None, max_runtime: i
|
|
|
39
46
|
@Session()
|
|
40
47
|
def _inner(*args, **kwargs):
|
|
41
48
|
task = create_task_safe(
|
|
42
|
-
task_name,
|
|
49
|
+
task_name,
|
|
50
|
+
status=StatusEnum.PROCESSING,
|
|
51
|
+
max_runtime=max_runtime,
|
|
52
|
+
monitor_id=monitor_id,
|
|
53
|
+
**task_kwargs,
|
|
43
54
|
)
|
|
44
55
|
try:
|
|
45
56
|
result = func(*args, **kwargs)
|
|
46
57
|
except Exception as e:
|
|
47
|
-
_update_task(
|
|
58
|
+
_update_task(
|
|
59
|
+
task,
|
|
60
|
+
status=StatusEnum.ERROR,
|
|
61
|
+
data={"exception": str(e)},
|
|
62
|
+
data_merge_strategy="default",
|
|
63
|
+
)
|
|
48
64
|
raise
|
|
49
65
|
|
|
50
66
|
_update_task(task, status=StatusEnum.SUCCESS)
|