taskbadger 0.3.0__tar.gz → 0.8.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.
Files changed (60) hide show
  1. {taskbadger-0.3.0 → taskbadger-0.8.0}/PKG-INFO +17 -18
  2. {taskbadger-0.3.0 → taskbadger-0.8.0}/README.md +11 -11
  3. {taskbadger-0.3.0 → taskbadger-0.8.0}/pyproject.toml +11 -5
  4. taskbadger-0.8.0/taskbadger/__init__.py +16 -0
  5. taskbadger-0.8.0/taskbadger/cli/__init__.py +3 -0
  6. taskbadger-0.8.0/taskbadger/cli/basics.py +126 -0
  7. taskbadger-0.8.0/taskbadger/cli/list_tasks.py +79 -0
  8. taskbadger-0.8.0/taskbadger/cli/utils.py +46 -0
  9. taskbadger-0.8.0/taskbadger/cli/wrapper.py +82 -0
  10. taskbadger-0.8.0/taskbadger/cli_main.py +85 -0
  11. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/config.py +1 -1
  12. taskbadger-0.8.0/taskbadger/exceptions.py +26 -0
  13. taskbadger-0.8.0/taskbadger/integrations.py +78 -0
  14. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/__init__.py +1 -1
  15. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/action_endpoints/action_cancel.py +13 -22
  16. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/action_endpoints/action_create.py +14 -23
  17. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/action_endpoints/action_get.py +15 -24
  18. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/action_endpoints/action_list.py +18 -24
  19. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/action_endpoints/action_partial_update.py +15 -23
  20. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/action_endpoints/action_update.py +15 -23
  21. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/task_endpoints/task_cancel.py +12 -22
  22. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/task_endpoints/task_create.py +13 -23
  23. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/task_endpoints/task_get.py +14 -24
  24. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/task_endpoints/task_list.py +16 -22
  25. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/task_endpoints/task_partial_update.py +14 -23
  26. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/task_endpoints/task_update.py +14 -23
  27. taskbadger-0.8.0/taskbadger/internal/client.py +268 -0
  28. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/errors.py +5 -1
  29. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/action.py +4 -3
  30. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/action_config.py +4 -3
  31. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/action_request.py +4 -3
  32. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/action_request_config.py +4 -3
  33. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/paginated_task_list.py +4 -3
  34. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/patched_action_request.py +4 -3
  35. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/patched_action_request_config.py +4 -3
  36. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/patched_task_request.py +6 -5
  37. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/patched_task_request_data.py +4 -3
  38. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/task.py +6 -5
  39. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/task_data.py +4 -3
  40. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/task_request.py +6 -5
  41. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/task_request_data.py +4 -3
  42. taskbadger-0.8.0/taskbadger/internal/py.typed +1 -0
  43. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/types.py +5 -5
  44. taskbadger-0.8.0/taskbadger/mug.py +100 -0
  45. taskbadger-0.8.0/taskbadger/process.py +78 -0
  46. taskbadger-0.8.0/taskbadger/safe_sdk.py +52 -0
  47. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/sdk.py +62 -37
  48. taskbadger-0.3.0/setup.py +0 -47
  49. taskbadger-0.3.0/taskbadger/__init__.py +0 -3
  50. taskbadger-0.3.0/taskbadger/cli.py +0 -177
  51. taskbadger-0.3.0/taskbadger/exceptions.py +0 -6
  52. taskbadger-0.3.0/taskbadger/integrations.py +0 -41
  53. taskbadger-0.3.0/taskbadger/internal/client.py +0 -64
  54. taskbadger-0.3.0/taskbadger/internal/py.typed +0 -1
  55. {taskbadger-0.3.0 → taskbadger-0.8.0}/LICENSE +0 -0
  56. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/__init__.py +0 -0
  57. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/action_endpoints/__init__.py +0 -0
  58. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/api/task_endpoints/__init__.py +0 -0
  59. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/__init__.py +0 -0
  60. {taskbadger-0.3.0 → taskbadger-0.8.0}/taskbadger/internal/models/status_enum.py +4 -4
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: taskbadger
3
- Version: 0.3.0
3
+ Version: 0.8.0
4
4
  Summary: The official Python SDK for Task Badger
5
5
  Home-page: https://taskbadger.net/
6
6
  License: Apache-2.0
@@ -16,19 +16,18 @@ Classifier: Programming Language :: Python :: 3.8
16
16
  Classifier: Programming Language :: Python :: 3.9
17
17
  Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
- Classifier: Programming Language :: Python :: 3.10
20
- Classifier: Programming Language :: Python :: 3.11
21
19
  Classifier: Programming Language :: Python :: 3.6
22
20
  Classifier: Programming Language :: Python :: 3.7
23
- Classifier: Programming Language :: Python :: 3.8
24
- Classifier: Programming Language :: Python :: 3.9
25
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Provides-Extra: celery
26
23
  Requires-Dist: attrs (>=21.3.0)
27
- Requires-Dist: httpx (>=0.15.4,<0.24.0)
24
+ Requires-Dist: celery (>=4.0.0,<6.0.0) ; extra == "celery"
25
+ Requires-Dist: httpx (>=0.20.0,<0.25.0)
28
26
  Requires-Dist: importlib-metadata (>=1.0,<2.0) ; python_version < "3.8"
29
27
  Requires-Dist: python-dateutil (>=2.8.0,<3.0.0)
30
28
  Requires-Dist: tomlkit (>=0.11.6,<0.12.0)
31
- Requires-Dist: typer[all] (>=0.7.0,<0.8.0)
29
+ Requires-Dist: typer[all] (>=0.9.0,<0.10.0)
30
+ Requires-Dist: typing-extensions (>=4.7.1,<5.0.0) ; python_version == "3.9"
32
31
  Project-URL: Documentation, https://docs.taskbadger.net/
33
32
  Project-URL: Repository, https://github.com/taskbadger/taskbadger-docs
34
33
  Description-Content-Type: text/markdown
@@ -66,7 +65,7 @@ taskbadger.init(
66
65
  #### API Example
67
66
 
68
67
  ```python
69
- from taskbadger import Task, Action, EmailIntegration
68
+ from taskbadger import Task, Action, EmailIntegration, WebhookIntegration
70
69
 
71
70
  # create a new task with custom data and an action definition
72
71
  task = Task.create(
@@ -75,10 +74,8 @@ task = Task.create(
75
74
  "custom": "data"
76
75
  },
77
76
  actions=[
78
- Action(
79
- "*/10%,success,error",
80
- integration=EmailIntegration(to="me@example.com")
81
- )
77
+ Action("*/10%,success,error", integration=EmailIntegration(to="me@example.com")),
78
+ Action("cancelled", integration=WebhookIntegration(id="webhook:demo")),
82
79
  ]
83
80
  )
84
81
 
@@ -107,10 +104,12 @@ task.success()
107
104
 
108
105
  ```shell
109
106
  $ taskbadger configure
110
- Organization slug: my-org
111
- Project slug: project-x
112
- API Key: XYZ.ABC
113
- Config written to ~/.config/taskbadger/confi
107
+
108
+ Organization slug: my-org
109
+ Project slug: project-x
110
+ API Key: XYZ.ABC
111
+
112
+ Config written to ~/.config/taskbadger/config
114
113
  ```
115
114
 
116
115
  #### Usage Examples
@@ -118,8 +117,8 @@ Config written to ~/.config/taskbadger/confi
118
117
  The CLI `run` command executes your command whilst creating and updating a Task Badger task.
119
118
 
120
119
  ```shell
121
- $ taskbadger run "demo task" --action "error email to:me@test.com" -- path/to/script.sh
120
+ $ taskbadger run "demo task" --action error email to:me@test.com -- path/to/script.sh
121
+
122
122
  Task created: https://taskbadger.net/public/tasks/xyz/
123
123
  ```
124
124
 
125
-
@@ -31,7 +31,7 @@ taskbadger.init(
31
31
  #### API Example
32
32
 
33
33
  ```python
34
- from taskbadger import Task, Action, EmailIntegration
34
+ from taskbadger import Task, Action, EmailIntegration, WebhookIntegration
35
35
 
36
36
  # create a new task with custom data and an action definition
37
37
  task = Task.create(
@@ -40,10 +40,8 @@ task = Task.create(
40
40
  "custom": "data"
41
41
  },
42
42
  actions=[
43
- Action(
44
- "*/10%,success,error",
45
- integration=EmailIntegration(to="me@example.com")
46
- )
43
+ Action("*/10%,success,error", integration=EmailIntegration(to="me@example.com")),
44
+ Action("cancelled", integration=WebhookIntegration(id="webhook:demo")),
47
45
  ]
48
46
  )
49
47
 
@@ -72,10 +70,12 @@ task.success()
72
70
 
73
71
  ```shell
74
72
  $ taskbadger configure
75
- Organization slug: my-org
76
- Project slug: project-x
77
- API Key: XYZ.ABC
78
- Config written to ~/.config/taskbadger/confi
73
+
74
+ Organization slug: my-org
75
+ Project slug: project-x
76
+ API Key: XYZ.ABC
77
+
78
+ Config written to ~/.config/taskbadger/config
79
79
  ```
80
80
 
81
81
  #### Usage Examples
@@ -83,7 +83,7 @@ Config written to ~/.config/taskbadger/confi
83
83
  The CLI `run` command executes your command whilst creating and updating a Task Badger task.
84
84
 
85
85
  ```shell
86
- $ taskbadger run "demo task" --action "error email to:me@test.com" -- path/to/script.sh
86
+ $ taskbadger run "demo task" --action error email to:me@test.com -- path/to/script.sh
87
+
87
88
  Task created: https://taskbadger.net/public/tasks/xyz/
88
89
  ```
89
-
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "taskbadger"
3
- version = "0.3.0"
3
+ version = "0.8.0"
4
4
  description = "The official Python SDK for Task Badger"
5
5
  license = "Apache-2.0"
6
6
 
@@ -32,24 +32,30 @@ classifiers = [
32
32
 
33
33
  [tool.poetry.dependencies]
34
34
  python = "^3.8"
35
- httpx = ">=0.15.4,<0.24.0"
35
+ httpx = ">=0.20.0,<0.25.0"
36
36
  attrs = ">=21.3.0"
37
37
  python-dateutil = "^2.8.0"
38
- typer = {extras = ["all"], version = "^0.7.0"}
38
+ typer = {extras = ["all"], version = "^0.9.0"}
39
39
  tomlkit = "^0.11.6"
40
40
  importlib-metadata = {version = "^1.0", python = "<3.8"}
41
+ typing-extensions = {version = "^4.7.1", python = "3.9"}
42
+ celery = {version = ">=4.0.0,<6.0.0", optional = true}
43
+
44
+ [tool.poetry.extras]
45
+ celery = ["celery"]
41
46
 
42
47
  [tool.poetry.group.dev.dependencies]
43
- openapi-python-client = "^0.13.1"
48
+ openapi-python-client = "^0.15.1"
44
49
  pytest = "^7.2.1"
45
50
  isort = "^5.12.0"
46
51
  black = "^23.1.0"
47
52
  pre-commit = "^3.0.2"
48
53
  pytest-httpx = "^0.21.3"
49
54
  invoke = "^2.0.0"
55
+ pytest-celery = "^0.0.0"
50
56
 
51
57
  [tool.poetry.scripts]
52
- taskbadger = "taskbadger.cli:app"
58
+ taskbadger = "taskbadger.cli_main:app"
53
59
 
54
60
  [build-system]
55
61
  requires = ["poetry-core>=1.0.0"]
@@ -0,0 +1,16 @@
1
+ from .integrations import Action, EmailIntegration, WebhookIntegration
2
+ from .internal.models import StatusEnum
3
+ from .mug import Session
4
+ from .safe_sdk import create_task_safe, update_task_safe
5
+ from .sdk import DefaultMergeStrategy, Task, create_task, get_task, init, update_task
6
+
7
+ try:
8
+ import importlib.metadata as importlib_metadata
9
+ except ModuleNotFoundError:
10
+ import importlib_metadata
11
+
12
+
13
+ try:
14
+ __version__ = importlib_metadata.version(__name__)
15
+ except importlib_metadata.PackageNotFoundError:
16
+ __version__ = "dev"
@@ -0,0 +1,3 @@
1
+ from .basics import create, get, update
2
+ from .list_tasks import list_tasks_command
3
+ from .wrapper import run
@@ -0,0 +1,126 @@
1
+ import csv
2
+ import json
3
+ import sys
4
+ from typing import Tuple
5
+
6
+ import typer
7
+ from rich import print
8
+
9
+ from taskbadger import StatusEnum, create_task, get_task, update_task
10
+ from taskbadger.cli.utils import OutputFormat, configure_api, err_console, get_actions, get_metadata
11
+
12
+
13
+ def get(
14
+ ctx: typer.Context,
15
+ task_id: str = typer.Argument(..., show_default=False, help="The ID of the task."),
16
+ output_format: OutputFormat = typer.Option(OutputFormat.pretty, "--format", "-f", help="Output format"),
17
+ ):
18
+ """Get a task."""
19
+ configure_api(ctx)
20
+ task = get_task(task_id)
21
+ if output_format == OutputFormat.pretty:
22
+ print(f"Task ID: {task.id}")
23
+ print(f"Created: {task.created.isoformat()}")
24
+ print(f"Name: {task.name}")
25
+ print(f"Status: {task.status}")
26
+ print(f"Percent: {task.value_percent}%")
27
+ elif output_format == OutputFormat.json:
28
+ print(json.dumps(task.to_dict(), indent=2))
29
+ elif output_format == OutputFormat.csv:
30
+ writer = csv.writer(sys.stdout)
31
+ writer.writerow("Task ID,Created,Name,Status,Percent".split(","))
32
+ writer.writerow([task.id, task.created.isoformat(), task.name, task.status, str(task.value_percent)])
33
+
34
+
35
+ def create(
36
+ ctx: typer.Context,
37
+ name: str = typer.Argument(..., show_default=False, help="The task name."),
38
+ monitor_id: str = typer.Option(None, help="Associate this task with a monitor."),
39
+ action_def: Tuple[str, str, str] = typer.Option(
40
+ (None, None, None),
41
+ "--action",
42
+ "-a",
43
+ metavar="<trigger integration config>",
44
+ show_default=False,
45
+ help="Action definition e.g. 'success,error email to:me@email.com'",
46
+ ),
47
+ status: StatusEnum = typer.Option(StatusEnum.PROCESSING, help="The initial status of the task."),
48
+ value_max: int = typer.Option(100, help="The maximum value for the task."),
49
+ metadata: list[str] = typer.Option(
50
+ None,
51
+ show_default=False,
52
+ help="Metadata 'key=value' pair to associate with the task. Can be specified multiple times.",
53
+ ),
54
+ metadata_json: str = typer.Option(
55
+ None, show_default=False, help="Metadata to associate with the task. Must be valid JSON."
56
+ ),
57
+ quiet: bool = typer.Option(False, "--quiet", "-q", help="Minimal output. Only the Task ID."),
58
+ ):
59
+ """Create a task."""
60
+ configure_api(ctx)
61
+ actions = get_actions(action_def)
62
+ metadata = get_metadata(metadata, metadata_json)
63
+
64
+ try:
65
+ task = create_task(
66
+ name,
67
+ status=status,
68
+ value_max=value_max,
69
+ data=metadata,
70
+ actions=actions,
71
+ monitor_id=monitor_id,
72
+ )
73
+ except Exception as e:
74
+ err_console.print(f"Error creating task: {e}")
75
+ else:
76
+ if quiet:
77
+ print(task.id)
78
+ else:
79
+ print(f"Task created: {task.public_url}")
80
+
81
+
82
+ def update(
83
+ ctx: typer.Context,
84
+ task_id: str = typer.Argument(..., show_default=False, help="The ID of the task to update."),
85
+ name: str = typer.Option(None, show_default=False, help="Update the name of the task."),
86
+ action_def: Tuple[str, str, str] = typer.Option(
87
+ (None, None, None),
88
+ "--action",
89
+ "-a",
90
+ metavar="<trigger integration config>",
91
+ show_default=False,
92
+ help="Action definition e.g. 'success,error email to:me@email.com'",
93
+ ),
94
+ status: StatusEnum = typer.Option(StatusEnum.PROCESSING, help="The status of the task."),
95
+ value: int = typer.Option(None, show_default=False, help="The current task value (progress)."),
96
+ value_max: int = typer.Option(None, show_default=False, help="The maximum value for the task."),
97
+ metadata: list[str] = typer.Option(
98
+ None,
99
+ show_default=False,
100
+ help="Metadata 'key=value' pair to associate with the task. Can be specified multiple times.",
101
+ ),
102
+ metadata_json: str = typer.Option(
103
+ None, show_default=False, help="Metadata to associate with the task. Must be valid JSON."
104
+ ),
105
+ quiet: bool = typer.Option(False, "--quiet", "-q", help="No output."),
106
+ ):
107
+ """Update a task."""
108
+ configure_api(ctx)
109
+ actions = get_actions(action_def)
110
+ metadata = get_metadata(metadata, metadata_json)
111
+
112
+ try:
113
+ task = update_task(
114
+ task_id,
115
+ name=name,
116
+ status=status,
117
+ value=value,
118
+ value_max=value_max,
119
+ data=metadata,
120
+ actions=actions,
121
+ )
122
+ except Exception as e:
123
+ err_console.print(f"Error creating task: {e}")
124
+ else:
125
+ if not quiet:
126
+ print(f"Task updated: {task.public_url}")
@@ -0,0 +1,79 @@
1
+ import csv
2
+ import json
3
+ import sys
4
+ from urllib.parse import parse_qs, urlparse
5
+
6
+ import typer
7
+ from rich import print
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ from taskbadger.cli.utils import OutputFormat, configure_api
12
+ from taskbadger.sdk import list_tasks
13
+
14
+
15
+ def list_tasks_command(
16
+ ctx: typer.Context,
17
+ output_format: OutputFormat = typer.Option(OutputFormat.pretty, "--format", "-f", help="Output format"),
18
+ limit: int = typer.Option(100, help="Limit the number of results."),
19
+ start_token: str = typer.Option(None, show_default=False, help="Start token."),
20
+ ):
21
+ """List tasks."""
22
+ configure_api(ctx)
23
+ tasks = list_tasks(page_size=limit, cursor=start_token)
24
+ render(output_format, ctx, tasks)
25
+
26
+
27
+ def render(format_: OutputFormat, ctx, result):
28
+ if format_ == OutputFormat.pretty:
29
+ _render_pretty(ctx, result)
30
+ elif format_ == OutputFormat.json:
31
+ _render_json(ctx, result)
32
+ elif format_ == OutputFormat.csv:
33
+ _render_csv(ctx, result)
34
+ else:
35
+ raise ValueError(f"Unknown format: {format_}")
36
+
37
+
38
+ def _render_pretty(ctx, result):
39
+ table = Table(
40
+ title=f"Project: {ctx.meta['tb_config'].project_slug}, Organization: {ctx.meta['tb_config'].organization_slug}"
41
+ )
42
+
43
+ table.add_column("Task ID", no_wrap=True)
44
+ table.add_column("Created", no_wrap=True)
45
+ table.add_column("Name")
46
+ table.add_column("Status", no_wrap=True)
47
+ table.add_column("Percent", no_wrap=True)
48
+
49
+ for task in result.results:
50
+ table.add_row(task.id, task.created.isoformat(), task.name, task.status, str(task.value_percent))
51
+ Console().print(table)
52
+
53
+ cursor = _get_cursor(result.next_)
54
+ if cursor:
55
+ print(f"Next page token: [bold green]'{cursor}'[/]")
56
+
57
+
58
+ def _render_csv(ctx, result):
59
+ writer = csv.writer(sys.stdout)
60
+ writer.writerow("Task ID,Created,Name,Status,Percent".split(","))
61
+ for task in result.results:
62
+ writer.writerow([task.id, task.created.isoformat(), task.name, task.status, str(task.value_percent)])
63
+
64
+ cursor = _get_cursor(result.next_)
65
+ if cursor:
66
+ print(f"next_token,{cursor}")
67
+
68
+
69
+ def _render_json(ctx, result):
70
+ obj = result.to_dict()
71
+ ret = {"next_token": _get_cursor(result.next_), "results": obj["results"]}
72
+ print(json.dumps(ret, indent=2))
73
+
74
+
75
+ def _get_cursor(url):
76
+ if url:
77
+ qs = urlparse(url).query
78
+ query = parse_qs(qs)
79
+ return query["cursor"][0]
@@ -0,0 +1,46 @@
1
+ import json
2
+ from enum import Enum
3
+
4
+ import typer
5
+ from rich import print
6
+ from rich.console import Console
7
+
8
+ from taskbadger import Action, integrations
9
+ from taskbadger.exceptions import ConfigurationError
10
+
11
+
12
+ def configure_api(ctx):
13
+ config = ctx.meta["tb_config"]
14
+ try:
15
+ config.init_api()
16
+ except ConfigurationError as e:
17
+ print(f"[red]{str(e)}[/red]")
18
+ raise typer.Exit(code=1)
19
+
20
+
21
+ err_console = Console(stderr=True)
22
+
23
+
24
+ def get_actions(action_def: tuple[str, str, str]) -> list[Action]:
25
+ if any(action_def):
26
+ trigger, integration, config = action_def
27
+ return [Action(trigger, integrations.from_config(integration, config))]
28
+ return []
29
+
30
+
31
+ def get_metadata(metadata_kv: list[str], metadata_json: str) -> dict:
32
+ metadata = {}
33
+ for kv in metadata_kv:
34
+ k, v = kv.strip().split("=", 1)
35
+ metadata[k] = v
36
+
37
+ if metadata_json:
38
+ metadata.update(json.loads(metadata_json))
39
+
40
+ return metadata
41
+
42
+
43
+ class OutputFormat(str, Enum):
44
+ pretty = "pretty"
45
+ json = "json"
46
+ csv = "csv"
@@ -0,0 +1,82 @@
1
+ from typing import Tuple
2
+
3
+ import typer
4
+ from rich import print
5
+
6
+ from taskbadger import DefaultMergeStrategy, Session, StatusEnum, Task
7
+ from taskbadger.cli.utils import configure_api, err_console, get_actions
8
+ from taskbadger.process import ProcessRunner
9
+
10
+
11
+ def run(
12
+ ctx: typer.Context,
13
+ name: str = typer.Argument(..., show_default=False, help="The task name"),
14
+ monitor_id: str = typer.Option(None, help="Associate this task with a monitor."),
15
+ update_frequency: int = typer.Option(5, metavar="SECONDS", min=5, max=300, help="Seconds between updates."),
16
+ action_def: Tuple[str, str, str] = typer.Option(
17
+ (None, None, None),
18
+ "--action",
19
+ "-a",
20
+ metavar="<trigger integration config>",
21
+ show_default=False,
22
+ help="Action definition e.g. 'success,error email to:me@email.com'",
23
+ ),
24
+ capture_output: bool = typer.Option(False, help="Capture stdout and stderr."),
25
+ ):
26
+ """Execute a command using the CLI and create a Task to track its outcome.
27
+
28
+ This command makes it easy to track a process's outcome using Task Badger.
29
+
30
+ It will create a Task prior to executing your command and will update
31
+ the Task status when you command exits.
32
+
33
+ Example:
34
+
35
+ [on black]taskbadger run 'my task' -- ./my-script.sh arg -v[/]
36
+ """
37
+ configure_api(ctx)
38
+ actions = get_actions(action_def)
39
+ stale_timeout = update_frequency * 2
40
+ with Session():
41
+ try:
42
+ task = Task.create(
43
+ name,
44
+ status=StatusEnum.PROCESSING,
45
+ stale_timeout=stale_timeout,
46
+ actions=actions,
47
+ monitor_id=monitor_id,
48
+ )
49
+ except Exception as e:
50
+ err_console.print(f"Error creating task: {e}")
51
+ task = None
52
+ else:
53
+ print(f"Task created: {task.public_url}")
54
+ env = {"TASKBADGER_TASK_ID": task.id} if task else None
55
+ try:
56
+ process = ProcessRunner(ctx.args, env, capture_output=capture_output, update_frequency=update_frequency)
57
+ for output in process.run():
58
+ _update_task(task, **(output or {}))
59
+ except Exception as e:
60
+ _update_task(task, exception=str(e))
61
+ raise typer.Exit(1)
62
+
63
+ if task:
64
+ if process.returncode == 0:
65
+ task.success(value=100)
66
+ else:
67
+ _update_task(task, status=StatusEnum.ERROR, return_code=process.returncode)
68
+
69
+ if process.returncode != 0:
70
+ raise typer.Exit(process.returncode)
71
+
72
+
73
+ def _update_task(task, status=None, **data_kwargs):
74
+ """Update the task and merge the data"""
75
+ if not task:
76
+ return
77
+
78
+ merge_strategy = DefaultMergeStrategy(append_keys=("stdout", "stderr"))
79
+ try:
80
+ task.update(status=status, data=data_kwargs or None, data_merge_strategy=merge_strategy)
81
+ except Exception as e:
82
+ err_console.print(f"Error updating task status: {e!r}")
@@ -0,0 +1,85 @@
1
+ from typing import Optional
2
+
3
+ import typer
4
+ from rich import print
5
+
6
+ from taskbadger import __version__
7
+ from taskbadger.cli import create, get, list_tasks_command, run, update
8
+ from taskbadger.config import get_config, write_config
9
+
10
+ app = typer.Typer(
11
+ rich_markup_mode="rich",
12
+ context_settings={"help_option_names": ["-h", "--help"]},
13
+ )
14
+
15
+
16
+ app.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": False})(run)
17
+ app.command(context_settings={"ignore_unknown_options": False})(get)
18
+ app.command(context_settings={"ignore_unknown_options": False})(create)
19
+ app.command(context_settings={"ignore_unknown_options": False})(update)
20
+ app.command("list", context_settings={"ignore_unknown_options": False})(list_tasks_command)
21
+
22
+
23
+ def version_callback(value: bool):
24
+ if value:
25
+ print(f"Task Badger CLI Version: {__version__}")
26
+ raise typer.Exit()
27
+
28
+
29
+ @app.command()
30
+ def configure(ctx: typer.Context):
31
+ """Update CLI configuration."""
32
+ config = ctx.meta["tb_config"]
33
+ config.organization_slug = typer.prompt(f"Organization slug", default=config.organization_slug)
34
+ config.project_slug = typer.prompt(f"Project slug", default=config.project_slug)
35
+ config.token = typer.prompt(f"API Key", default=config.token)
36
+ path = write_config(config)
37
+ print(f"Config written to [green]{path}[/green]")
38
+
39
+
40
+ @app.command()
41
+ def docs():
42
+ """Open Task Badger docs in a browser."""
43
+ typer.launch("https://docs.taskbadger.net")
44
+
45
+
46
+ @app.command()
47
+ def info(ctx: typer.Context):
48
+ """Show CLI configuration."""
49
+ config = ctx.meta["tb_config"]
50
+ print(str(config))
51
+
52
+
53
+ @app.callback()
54
+ def main(
55
+ ctx: typer.Context,
56
+ org: Optional[str] = typer.Option(
57
+ None,
58
+ "--org",
59
+ "-o",
60
+ metavar="TASKBADGER_ORG",
61
+ show_default=False,
62
+ help="Organization Slug. This will override values from the config file and environment variables.",
63
+ ),
64
+ project: Optional[str] = typer.Option(
65
+ None,
66
+ "--project",
67
+ "-p",
68
+ show_envvar=False,
69
+ metavar="TASKBADGER_PROJECT",
70
+ show_default=False,
71
+ help="Project Slug. This will override values from the config file and environment variables.",
72
+ ),
73
+ version: Optional[bool] = typer.Option( # noqa
74
+ None, "--version", callback=version_callback, is_eager=True, help="Show CLI Version"
75
+ ),
76
+ ):
77
+ """
78
+ Task Badger CLI
79
+ """
80
+ config = get_config(org=org, project=project)
81
+ ctx.meta["tb_config"] = config
82
+
83
+
84
+ if __name__ == "__main__":
85
+ app()
@@ -47,7 +47,7 @@ class Config:
47
47
  defaults = config_dict.get("defaults", {})
48
48
  auth = config_dict.get("auth", {})
49
49
  return Config(
50
- token=overrides.get("token") or _from_env("TOKEN", auth.get("token")),
50
+ token=overrides.get("token") or _from_env("API_KEY", auth.get("token")),
51
51
  organization_slug=overrides.get("org") or _from_env("ORG", defaults.get("org")),
52
52
  project_slug=overrides.get("project") or _from_env("PROJECT", defaults.get("project")),
53
53
  host=overrides.get("host") or auth.get("host"),
@@ -0,0 +1,26 @@
1
+ class ConfigurationError(Exception):
2
+ def __init__(self, **kwargs):
3
+ self.missing = [name for name, arg in kwargs.items() if arg is None]
4
+
5
+ def __str__(self):
6
+ return f"Missing configuration parameters: {', '.join(self.missing)}"
7
+
8
+
9
+ class TaskbadgerException(Exception):
10
+ pass
11
+
12
+
13
+ class Unauthorized(TaskbadgerException):
14
+ pass
15
+
16
+
17
+ class UnexpectedStatus(TaskbadgerException):
18
+ def __init__(self, status_code: int, content: bytes):
19
+ self.status_code = status_code
20
+ self.content = content
21
+
22
+ super().__init__(f"Unexpected status code: {status_code}")
23
+
24
+
25
+ class ServerError(UnexpectedStatus):
26
+ pass