dart-tools 0.6.15__py3-none-any.whl → 0.7.0__py3-none-any.whl
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.
Potentially problematic release.
This version of dart-tools might be problematic. Click here for more details.
- dart/__init__.py +8 -6
- dart/dart.py +421 -698
- dart/generated/__init__.py +1 -1
- dart/generated/api/__init__.py +8 -0
- dart/generated/api/comment/__init__.py +1 -0
- dart/generated/api/{transactions/transactions_create.py → comment/create_comment.py} +57 -58
- dart/generated/api/config/__init__.py +1 -0
- dart/generated/api/config/get_config.py +141 -0
- dart/generated/api/dartboard/__init__.py +1 -0
- dart/generated/api/{attachments/attachments_list.py → dartboard/retrieve_dartboard.py} +58 -62
- dart/generated/api/doc/__init__.py +1 -0
- dart/generated/api/doc/create_doc.py +179 -0
- dart/generated/api/doc/delete_doc.py +165 -0
- dart/generated/api/{comments/comments_list.py → doc/list_docs.py} +115 -111
- dart/generated/api/{forms/forms_list.py → doc/retrieve_doc.py} +58 -62
- dart/generated/api/doc/update_doc.py +188 -0
- dart/generated/api/folder/__init__.py +1 -0
- dart/generated/api/{webhooks/webhooks_list.py → folder/retrieve_folder.py} +58 -62
- dart/generated/api/task/__init__.py +1 -0
- dart/generated/api/task/create_task.py +172 -0
- dart/generated/api/task/delete_task.py +165 -0
- dart/generated/api/task/list_tasks.py +497 -0
- dart/generated/api/{layouts/layouts_list.py → task/retrieve_task.py} +58 -62
- dart/generated/api/task/update_task.py +188 -0
- dart/generated/api/view/__init__.py +1 -0
- dart/generated/api/{tenants/tenants_list.py → view/retrieve_view.py} +58 -62
- dart/generated/client.py +13 -13
- dart/generated/models/__init__.py +34 -342
- dart/generated/models/comment.py +34 -150
- dart/generated/models/comment_create.py +18 -97
- dart/generated/models/concise_doc.py +84 -0
- dart/generated/models/concise_task.py +267 -0
- dart/generated/models/dartboard.py +37 -237
- dart/generated/models/doc.py +31 -183
- dart/generated/models/doc_create.py +22 -238
- dart/generated/models/doc_update.py +26 -233
- dart/generated/models/folder.py +39 -84
- dart/generated/models/list_docs_o_item.py +15 -0
- dart/generated/models/{paginated_attachment_list.py → paginated_concise_doc_list.py} +18 -17
- dart/generated/models/{paginated_dartboard_list.py → paginated_concise_task_list.py} +18 -17
- dart/generated/models/task.py +149 -406
- dart/generated/models/task_create.py +113 -428
- dart/generated/models/task_update.py +113 -421
- dart/generated/models/user.py +14 -192
- dart/generated/models/user_space_configuration.py +147 -0
- dart/generated/models/view.py +39 -149
- dart/generated/models/wrapped_comment.py +65 -0
- dart/generated/models/wrapped_comment_create.py +65 -0
- dart/generated/models/wrapped_dartboard.py +65 -0
- dart/generated/models/wrapped_doc.py +65 -0
- dart/generated/models/wrapped_doc_create.py +65 -0
- dart/generated/models/wrapped_doc_update.py +65 -0
- dart/generated/models/wrapped_folder.py +65 -0
- dart/generated/models/wrapped_task.py +65 -0
- dart/generated/models/wrapped_task_create.py +65 -0
- dart/generated/models/wrapped_task_update.py +65 -0
- dart/generated/models/wrapped_view.py +65 -0
- dart/generated/types.py +4 -3
- dart/old.py +216 -0
- dart/order_manager.py +4 -15
- dart/webhook.py +2 -7
- {dart_tools-0.6.15.dist-info → dart_tools-0.7.0.dist-info}/METADATA +15 -75
- dart_tools-0.7.0.dist-info/RECORD +71 -0
- {dart_tools-0.6.15.dist-info → dart_tools-0.7.0.dist-info}/WHEEL +1 -1
- dart/generated/api/attachments/__init__.py +0 -0
- dart/generated/api/comments/__init__.py +0 -0
- dart/generated/api/dartboards/__init__.py +0 -0
- dart/generated/api/dartboards/dartboards_list.py +0 -286
- dart/generated/api/dashboards/__init__.py +0 -0
- dart/generated/api/dashboards/dashboards_list.py +0 -199
- dart/generated/api/docs/__init__.py +0 -0
- dart/generated/api/docs/docs_list.py +0 -387
- dart/generated/api/folders/__init__.py +0 -0
- dart/generated/api/folders/folders_list.py +0 -249
- dart/generated/api/form_fields/__init__.py +0 -0
- dart/generated/api/form_fields/form_fields_list.py +0 -169
- dart/generated/api/forms/__init__.py +0 -0
- dart/generated/api/layouts/__init__.py +0 -0
- dart/generated/api/links/__init__.py +0 -0
- dart/generated/api/links/links_list.py +0 -169
- dart/generated/api/options/__init__.py +0 -0
- dart/generated/api/options/options_list.py +0 -244
- dart/generated/api/properties/__init__.py +0 -0
- dart/generated/api/properties/properties_list.py +0 -219
- dart/generated/api/reactions/__init__.py +0 -0
- dart/generated/api/reactions/reactions_list.py +0 -169
- dart/generated/api/relationship_kinds/__init__.py +0 -0
- dart/generated/api/relationship_kinds/relationship_kinds_list.py +0 -169
- dart/generated/api/relationships/__init__.py +0 -0
- dart/generated/api/relationships/relationships_list.py +0 -169
- dart/generated/api/spaces/__init__.py +0 -0
- dart/generated/api/spaces/spaces_list.py +0 -229
- dart/generated/api/statuses/__init__.py +0 -0
- dart/generated/api/statuses/statuses_list.py +0 -264
- dart/generated/api/task_doc_relationships/__init__.py +0 -0
- dart/generated/api/task_doc_relationships/task_doc_relationships_list.py +0 -169
- dart/generated/api/task_kinds/__init__.py +0 -0
- dart/generated/api/task_kinds/task_kinds_list.py +0 -219
- dart/generated/api/tasks/__init__.py +0 -0
- dart/generated/api/tasks/tasks_list.py +0 -461
- dart/generated/api/tenants/__init__.py +0 -0
- dart/generated/api/transactions/__init__.py +0 -0
- dart/generated/api/user_dartboard_layouts/__init__.py +0 -0
- dart/generated/api/user_dartboard_layouts/user_dartboard_layouts_list.py +0 -169
- dart/generated/api/user_data/__init__.py +0 -0
- dart/generated/api/user_data/user_data_entity_retrieve.py +0 -580
- dart/generated/api/users/__init__.py +0 -0
- dart/generated/api/users/users_list.py +0 -229
- dart/generated/api/views/__init__.py +0 -0
- dart/generated/api/views/views_list.py +0 -199
- dart/generated/api/webhooks/__init__.py +0 -0
- dart/generated/models/attachment.py +0 -112
- dart/generated/models/attachment_create.py +0 -121
- dart/generated/models/attachment_update.py +0 -125
- dart/generated/models/bar_chart_adtl.py +0 -98
- dart/generated/models/brainstorm.py +0 -149
- dart/generated/models/brainstorm_create.py +0 -134
- dart/generated/models/brainstorm_state.py +0 -10
- dart/generated/models/brainstorm_update.py +0 -153
- dart/generated/models/burn_up_chart_adtl.py +0 -103
- dart/generated/models/chart.py +0 -208
- dart/generated/models/chart_aggregation.py +0 -10
- dart/generated/models/chart_type.py +0 -14
- dart/generated/models/comment_reaction.py +0 -84
- dart/generated/models/comment_reaction_create.py +0 -82
- dart/generated/models/comment_reaction_update.py +0 -87
- dart/generated/models/comment_update.py +0 -148
- dart/generated/models/dartboard_create.py +0 -315
- dart/generated/models/dartboard_kind.py +0 -12
- dart/generated/models/dartboard_update.py +0 -317
- dart/generated/models/dartboards_list_kind.py +0 -12
- dart/generated/models/dashboard.py +0 -185
- dart/generated/models/dashboard_create.py +0 -171
- dart/generated/models/dashboard_update.py +0 -173
- dart/generated/models/discord_integration.py +0 -72
- dart/generated/models/doc_source_type.py +0 -13
- dart/generated/models/docs_list_o_item.py +0 -15
- dart/generated/models/entity_name.py +0 -22
- dart/generated/models/event.py +0 -420
- dart/generated/models/event_actor.py +0 -19
- dart/generated/models/event_create.py +0 -158
- dart/generated/models/event_kind.py +0 -88
- dart/generated/models/event_subscription.py +0 -74
- dart/generated/models/event_subscription_update.py +0 -173
- dart/generated/models/filter_applicability.py +0 -22
- dart/generated/models/filter_assignee.py +0 -116
- dart/generated/models/filter_connector.py +0 -9
- dart/generated/models/filter_group.py +0 -112
- dart/generated/models/filter_search.py +0 -82
- dart/generated/models/filter_set.py +0 -116
- dart/generated/models/folder_create.py +0 -150
- dart/generated/models/folder_kind.py +0 -10
- dart/generated/models/folder_update.py +0 -152
- dart/generated/models/folders_list_kind.py +0 -10
- dart/generated/models/form.py +0 -147
- dart/generated/models/form_create.py +0 -141
- dart/generated/models/form_field.py +0 -144
- dart/generated/models/form_field_create.py +0 -129
- dart/generated/models/form_field_update.py +0 -132
- dart/generated/models/form_update.py +0 -142
- dart/generated/models/github_integration.py +0 -163
- dart/generated/models/github_integration_tenant_extension_status.py +0 -11
- dart/generated/models/google_data.py +0 -94
- dart/generated/models/icon_kind.py +0 -10
- dart/generated/models/layout.py +0 -167
- dart/generated/models/layout_config.py +0 -70
- dart/generated/models/layout_create.py +0 -130
- dart/generated/models/layout_kind.py +0 -11
- dart/generated/models/layout_kind_config_map.py +0 -56
- dart/generated/models/layout_update.py +0 -130
- dart/generated/models/line_chart_adtl.py +0 -72
- dart/generated/models/models_response.py +0 -671
- dart/generated/models/notification.py +0 -120
- dart/generated/models/notification_update.py +0 -100
- dart/generated/models/notion_integration.py +0 -90
- dart/generated/models/notion_integration_tenant_extension_status.py +0 -10
- dart/generated/models/number_chart_adtl.py +0 -77
- dart/generated/models/operation.py +0 -874
- dart/generated/models/operation_kind.py +0 -12
- dart/generated/models/operation_model_kind.py +0 -36
- dart/generated/models/option.py +0 -118
- dart/generated/models/option_create.py +0 -105
- dart/generated/models/option_update.py +0 -107
- dart/generated/models/paginated_comment_list.py +0 -122
- dart/generated/models/paginated_comment_reaction_list.py +0 -122
- dart/generated/models/paginated_dashboard_list.py +0 -122
- dart/generated/models/paginated_doc_list.py +0 -122
- dart/generated/models/paginated_folder_list.py +0 -122
- dart/generated/models/paginated_form_field_list.py +0 -122
- dart/generated/models/paginated_form_list.py +0 -122
- dart/generated/models/paginated_layout_list.py +0 -122
- dart/generated/models/paginated_option_list.py +0 -122
- dart/generated/models/paginated_property_list.py +0 -122
- dart/generated/models/paginated_relationship_kind_list.py +0 -122
- dart/generated/models/paginated_relationship_list.py +0 -122
- dart/generated/models/paginated_space_list.py +0 -122
- dart/generated/models/paginated_status_list.py +0 -122
- dart/generated/models/paginated_task_doc_relationship_list.py +0 -122
- dart/generated/models/paginated_task_kind_list.py +0 -122
- dart/generated/models/paginated_task_link_list.py +0 -122
- dart/generated/models/paginated_task_list.py +0 -122
- dart/generated/models/paginated_tenant_list.py +0 -122
- dart/generated/models/paginated_user_dartboard_layout_list.py +0 -122
- dart/generated/models/paginated_user_list.py +0 -122
- dart/generated/models/paginated_view_list.py +0 -122
- dart/generated/models/paginated_webhook_list.py +0 -122
- dart/generated/models/pie_chart_adtl.py +0 -69
- dart/generated/models/pie_chart_display_metric.py +0 -9
- dart/generated/models/properties_list_kind.py +0 -32
- dart/generated/models/property_.py +0 -153
- dart/generated/models/property_create.py +0 -137
- dart/generated/models/property_kind.py +0 -32
- dart/generated/models/property_update.py +0 -146
- dart/generated/models/relationship.py +0 -74
- dart/generated/models/relationship_create.py +0 -93
- dart/generated/models/relationship_kind.py +0 -123
- dart/generated/models/relationship_kind_create.py +0 -117
- dart/generated/models/relationship_kind_kind.py +0 -12
- dart/generated/models/relationship_kind_update.py +0 -119
- dart/generated/models/report_kind.py +0 -9
- dart/generated/models/request_body.py +0 -80
- dart/generated/models/response_body.py +0 -72
- dart/generated/models/saml_config.py +0 -77
- dart/generated/models/saml_config_tenant_extension_status.py +0 -9
- dart/generated/models/slack_integration.py +0 -90
- dart/generated/models/slack_integration_tenant_extension_status.py +0 -10
- dart/generated/models/sort.py +0 -66
- dart/generated/models/space.py +0 -311
- dart/generated/models/space_create.py +0 -340
- dart/generated/models/space_kind.py +0 -10
- dart/generated/models/space_update.py +0 -341
- dart/generated/models/sprint_mode.py +0 -9
- dart/generated/models/status.py +0 -141
- dart/generated/models/status_create.py +0 -125
- dart/generated/models/status_kind.py +0 -12
- dart/generated/models/status_update.py +0 -135
- dart/generated/models/statuses_list_kind.py +0 -12
- dart/generated/models/subscription.py +0 -9
- dart/generated/models/subtask_display_mode.py +0 -10
- dart/generated/models/summary_statistic_kind.py +0 -14
- dart/generated/models/table_chart_adtl.py +0 -98
- dart/generated/models/task_detail_mode.py +0 -10
- dart/generated/models/task_doc_relationship.py +0 -96
- dart/generated/models/task_doc_relationship_create.py +0 -74
- dart/generated/models/task_kind.py +0 -149
- dart/generated/models/task_kind_create.py +0 -144
- dart/generated/models/task_kind_kind.py +0 -9
- dart/generated/models/task_kind_update.py +0 -153
- dart/generated/models/task_kinds_list_kind.py +0 -9
- dart/generated/models/task_link.py +0 -131
- dart/generated/models/task_link_create.py +0 -152
- dart/generated/models/task_link_kind.py +0 -19
- dart/generated/models/task_link_update.py +0 -155
- dart/generated/models/task_notion_document.py +0 -196
- dart/generated/models/task_notion_document_block_children_map_type_0.py +0 -43
- dart/generated/models/task_notion_document_block_map_type_0.py +0 -43
- dart/generated/models/task_notion_document_page_map_type_0.py +0 -43
- dart/generated/models/task_properties.py +0 -43
- dart/generated/models/task_source_type.py +0 -31
- dart/generated/models/tenant.py +0 -386
- dart/generated/models/tenant_update.py +0 -166
- dart/generated/models/theme.py +0 -10
- dart/generated/models/transaction.py +0 -158
- dart/generated/models/transaction_kind.py +0 -74
- dart/generated/models/transaction_response.py +0 -96
- dart/generated/models/user_dartboard_layout.py +0 -66
- dart/generated/models/user_dartboard_layout_create.py +0 -74
- dart/generated/models/user_data_entity_retrieve_entity_kind.py +0 -32
- dart/generated/models/user_role.py +0 -12
- dart/generated/models/user_status.py +0 -12
- dart/generated/models/user_update.py +0 -210
- dart/generated/models/validation_error_response.py +0 -64
- dart/generated/models/validation_error_response_items.py +0 -43
- dart/generated/models/view_create.py +0 -213
- dart/generated/models/view_kind.py +0 -11
- dart/generated/models/view_update.py +0 -215
- dart/generated/models/webhook.py +0 -96
- dart/generated/models/webhook_create.py +0 -77
- dart/generated/models/webhook_update.py +0 -78
- dart/generated/models/zapier_integration.py +0 -66
- dart_tools-0.6.15.dist-info/RECORD +0 -257
- dart_tools-0.6.15.dist-info/dist/dart-tools-0.3.3.tar.gz +0 -0
- {dart_tools-0.6.15.dist-info → dart_tools-0.7.0.dist-info}/entry_points.txt +0 -0
- {dart_tools-0.6.15.dist-info → dart_tools-0.7.0.dist-info/licenses}/LICENSE +0 -0
- {dart_tools-0.6.15.dist-info → dart_tools-0.7.0.dist-info}/top_level.txt +0 -0
dart/dart.py
CHANGED
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
# Required for type hinting compatibility when using Python 3.9
|
|
7
7
|
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
from functools import wraps
|
|
8
|
+
|
|
10
9
|
import json
|
|
11
10
|
import os
|
|
12
11
|
import random
|
|
@@ -15,46 +14,47 @@ import signal
|
|
|
15
14
|
import string
|
|
16
15
|
import subprocess
|
|
17
16
|
import sys
|
|
17
|
+
from argparse import ArgumentParser
|
|
18
18
|
from collections import defaultdict
|
|
19
19
|
from datetime import timezone
|
|
20
|
+
from functools import wraps
|
|
20
21
|
from importlib.metadata import version
|
|
21
|
-
from typing import
|
|
22
|
+
from typing import Callable, NoReturn, TypeVar
|
|
22
23
|
from webbrowser import open_new_tab
|
|
23
24
|
|
|
24
25
|
import dateparser
|
|
25
|
-
|
|
26
|
-
import requests
|
|
26
|
+
import httpx
|
|
27
27
|
import platformdirs
|
|
28
|
+
from pick import pick
|
|
29
|
+
|
|
30
|
+
from dart.generated.types import Response
|
|
28
31
|
|
|
29
32
|
from .exception import DartException
|
|
30
|
-
from .generated import Client
|
|
33
|
+
from .generated import Client, api
|
|
31
34
|
from .generated.models import (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
OperationModelKind,
|
|
35
|
+
Comment,
|
|
36
|
+
CommentCreate,
|
|
37
|
+
ConciseTask,
|
|
38
|
+
Doc,
|
|
39
|
+
DocCreate,
|
|
40
|
+
DocUpdate,
|
|
41
|
+
PaginatedConciseDocList,
|
|
42
|
+
PaginatedConciseTaskList,
|
|
41
43
|
Priority,
|
|
42
|
-
PropertyKind,
|
|
43
|
-
RequestBody,
|
|
44
|
-
SpaceKind,
|
|
45
|
-
StatusKind,
|
|
46
|
-
SprintMode,
|
|
47
44
|
Task,
|
|
48
45
|
TaskCreate,
|
|
49
|
-
TaskSourceType,
|
|
50
46
|
TaskUpdate,
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
UserSpaceConfiguration,
|
|
48
|
+
WrappedComment,
|
|
49
|
+
WrappedCommentCreate,
|
|
50
|
+
WrappedDoc,
|
|
51
|
+
WrappedDocCreate,
|
|
52
|
+
WrappedDocUpdate,
|
|
53
|
+
WrappedTask,
|
|
54
|
+
WrappedTaskCreate,
|
|
55
|
+
WrappedTaskUpdate,
|
|
53
56
|
)
|
|
54
|
-
from .generated.
|
|
55
|
-
from .generated.api.folders import folders_list
|
|
56
|
-
from .generated.api.transactions import transactions_create
|
|
57
|
-
from .order_manager import get_orders_between
|
|
57
|
+
from .generated.types import UNSET, Unset
|
|
58
58
|
|
|
59
59
|
_APP = "dart-tools"
|
|
60
60
|
_PROG = "dart"
|
|
@@ -64,49 +64,71 @@ _STAG_HOST = "https://stag.itsdart.com"
|
|
|
64
64
|
_DEV_HOST = "http://localhost:5173"
|
|
65
65
|
_HOST_MAP = {"prod": _PROD_HOST, "stag": _STAG_HOST, "dev": _DEV_HOST}
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
_SET_HOST_CMD = "sethost"
|
|
67
|
+
# Service commands
|
|
69
68
|
_LOGIN_CMD = "login"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
_SET_HOST_CMD = "sethost"
|
|
70
|
+
_VERSION_CMD = "--version"
|
|
71
|
+
# Task commands
|
|
72
|
+
_CREATE_TASK_CMD = "task-create"
|
|
73
|
+
_UPDATE_TASK_CMD = "task-update"
|
|
74
|
+
_DELETE_TASK_CMD = "task-delete"
|
|
75
|
+
_BEGIN_TASK_CMD = "task-begin"
|
|
76
|
+
# Doc commands
|
|
77
|
+
_CREATE_DOC_CMD = "doc-create"
|
|
78
|
+
_UPDATE_DOC_CMD = "doc-update"
|
|
79
|
+
_DELETE_DOC_CMD = "doc-delete"
|
|
80
|
+
# Comment commands
|
|
81
|
+
_CREATE_COMMENT_CMD = "comment-create"
|
|
73
82
|
|
|
74
83
|
_PROFILE_SETTINGS_URL_FRAG = "/?settings=account"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
_COPY_BRANCH_URL_FRAG = _ROOT_API_URL_FRAG + "/vcs/copy-branch-link"
|
|
79
|
-
_REPLICATE_SPACE_URL_FRAG_FMT = _ROOT_API_URL_FRAG + "/spaces/replicate/{duid}"
|
|
80
|
-
_REPLICATE_DARTBOARD_URL_FRAG_FMT = _ROOT_API_URL_FRAG + "/dartboards/replicate/{duid}"
|
|
84
|
+
_ROOT_PRIVATE_API_URL_FRAG = "/api/v0"
|
|
85
|
+
_ROOT_PUBLIC_API_URL_FRAG = f"{_ROOT_PRIVATE_API_URL_FRAG}/public"
|
|
86
|
+
_COPY_BRANCH_URL_FRAG = "/vcs/copy-branch-link"
|
|
81
87
|
|
|
82
88
|
_AUTH_TOKEN_ENVVAR_KEY = "DART_TOKEN"
|
|
83
89
|
_CONFIG_FPATH = platformdirs.user_config_path(_APP)
|
|
84
|
-
|
|
90
|
+
_CLIENT_ID_KEY = "clientId"
|
|
85
91
|
_HOST_KEY = "host"
|
|
86
92
|
_HOSTS_KEY = "hosts"
|
|
87
93
|
_AUTH_TOKEN_KEY = "authToken"
|
|
88
94
|
|
|
89
|
-
|
|
95
|
+
_ID_CHARS = string.ascii_lowercase + string.ascii_uppercase + string.digits
|
|
90
96
|
_NON_ALPHANUM_RE = re.compile(r"[^a-zA-Z0-9-]+")
|
|
91
97
|
_REPEATED_DASH_RE = re.compile(r"-{2,}")
|
|
92
|
-
_PRIORITY_MAP = {
|
|
98
|
+
_PRIORITY_MAP: dict[int, str] = {
|
|
93
99
|
0: Priority.CRITICAL,
|
|
94
100
|
1: Priority.HIGH,
|
|
95
101
|
2: Priority.MEDIUM,
|
|
96
102
|
3: Priority.LOW,
|
|
97
103
|
}
|
|
98
104
|
_SIZES = {1, 2, 3, 5, 8}
|
|
99
|
-
_COMPLETED_STATUS_KINDS = {"Finished", "Canceled"}
|
|
100
105
|
|
|
101
106
|
_VERSION = version(_APP)
|
|
102
107
|
_AUTH_TOKEN_ENVVAR = os.environ.get(_AUTH_TOKEN_ENVVAR_KEY)
|
|
103
108
|
|
|
109
|
+
|
|
110
|
+
def _get_help_text(fn: Callable) -> str:
|
|
111
|
+
if fn.__doc__ is None:
|
|
112
|
+
raise ValueError(f"Function {fn.__name__} has no docstring.")
|
|
113
|
+
return fn.__doc__.split("\n")[0].lower()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
_HELP_TEXT_TO_COMMAND = {
|
|
117
|
+
_CREATE_TASK_CMD: _get_help_text(api.create_task.sync_detailed),
|
|
118
|
+
_UPDATE_TASK_CMD: _get_help_text(api.update_task.sync_detailed),
|
|
119
|
+
_DELETE_TASK_CMD: _get_help_text(api.delete_task.sync_detailed),
|
|
120
|
+
_CREATE_DOC_CMD: _get_help_text(api.create_doc.sync_detailed),
|
|
121
|
+
_UPDATE_DOC_CMD: _get_help_text(api.update_doc.sync_detailed),
|
|
122
|
+
_DELETE_DOC_CMD: _get_help_text(api.delete_doc.sync_detailed),
|
|
123
|
+
_CREATE_COMMENT_CMD: _get_help_text(api.create_comment.sync_detailed),
|
|
124
|
+
}
|
|
125
|
+
|
|
104
126
|
_is_cli = False
|
|
105
127
|
|
|
106
128
|
|
|
107
129
|
# TODO dedupe these functions with other usages elsewhere
|
|
108
|
-
def
|
|
109
|
-
return "".join(random.choices(
|
|
130
|
+
def _make_id() -> str:
|
|
131
|
+
return "".join(random.choices(_ID_CHARS, k=12))
|
|
110
132
|
|
|
111
133
|
|
|
112
134
|
def trim_slug_str(s: str, length: int, max_under: int | None = None) -> str:
|
|
@@ -123,33 +145,13 @@ def slugify_str(s: str, lower: bool = False, trim_kwargs: dict | None = None) ->
|
|
|
123
145
|
lowered = s.lower() if lower else s
|
|
124
146
|
formatted = _NON_ALPHANUM_RE.sub("-", lowered.replace("'", ""))
|
|
125
147
|
formatted = _REPEATED_DASH_RE.sub("-", formatted).strip("-")
|
|
126
|
-
return (
|
|
127
|
-
trim_slug_str(formatted, **trim_kwargs)
|
|
128
|
-
if trim_kwargs is not None
|
|
129
|
-
else formatted
|
|
130
|
-
)
|
|
148
|
+
return trim_slug_str(formatted, **trim_kwargs) if trim_kwargs is not None else formatted
|
|
131
149
|
|
|
132
150
|
|
|
133
151
|
def _run_cmd(cmd: str) -> str:
|
|
134
152
|
return subprocess.check_output(cmd, shell=True).decode()
|
|
135
153
|
|
|
136
154
|
|
|
137
|
-
def _get_space_url(host: str, duid: str) -> str:
|
|
138
|
-
return f"{host}/s/{duid}"
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def _get_dartboard_url(host: str, duid: str) -> str:
|
|
142
|
-
return f"{host}/d/{duid}"
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def _get_task_url(host: str, duid: str) -> str:
|
|
146
|
-
return f"{host}/t/{duid}"
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def _get_folder_url(host: str, duid: str) -> str:
|
|
150
|
-
return f"{host}/f/{duid}"
|
|
151
|
-
|
|
152
|
-
|
|
153
155
|
def _suppress_exception(fn: Callable) -> Callable:
|
|
154
156
|
@wraps(fn)
|
|
155
157
|
def wrapper(*args, **kwargs):
|
|
@@ -177,6 +179,37 @@ def _log(s: str) -> None:
|
|
|
177
179
|
print(s)
|
|
178
180
|
|
|
179
181
|
|
|
182
|
+
T = TypeVar("T")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _get_response_parsed(response: Response[T], not_found_message="Not found.") -> T:
|
|
186
|
+
if response.parsed is not None:
|
|
187
|
+
return response.parsed
|
|
188
|
+
if response.status_code in {401, 403}:
|
|
189
|
+
_auth_failure_exit()
|
|
190
|
+
elif response.status_code == 404:
|
|
191
|
+
_dart_exit(not_found_message)
|
|
192
|
+
try:
|
|
193
|
+
response_content = json.loads(response.content)
|
|
194
|
+
error_message = response_content.get("detail") or " ".join(response_content.get("errors", []))
|
|
195
|
+
_dart_exit(error_message)
|
|
196
|
+
except (json.JSONDecodeError, AttributeError):
|
|
197
|
+
_unknown_failure_exit()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _handle_request_errors(fn: Callable) -> Callable:
|
|
201
|
+
@wraps(fn)
|
|
202
|
+
def wrapper(*args, **kwargs):
|
|
203
|
+
try:
|
|
204
|
+
return fn(*args, **kwargs)
|
|
205
|
+
except DartException as ex:
|
|
206
|
+
_dart_exit(ex)
|
|
207
|
+
except (httpx.TimeoutException, httpx.RequestError, httpx.ConnectError) as ex:
|
|
208
|
+
_dart_exit(f"Failed to execute API call. Reason: {ex}.")
|
|
209
|
+
|
|
210
|
+
return wrapper
|
|
211
|
+
|
|
212
|
+
|
|
180
213
|
class _Config:
|
|
181
214
|
def __init__(self):
|
|
182
215
|
self._content = {}
|
|
@@ -187,14 +220,14 @@ class _Config:
|
|
|
187
220
|
except OSError:
|
|
188
221
|
pass
|
|
189
222
|
self._content = {
|
|
190
|
-
|
|
223
|
+
_CLIENT_ID_KEY: _make_id(),
|
|
191
224
|
_HOST_KEY: _PROD_HOST,
|
|
192
225
|
_HOSTS_KEY: {},
|
|
193
226
|
} | self._content
|
|
194
227
|
self._content[_HOSTS_KEY] = defaultdict(dict, self._content[_HOSTS_KEY])
|
|
195
228
|
self._write()
|
|
196
229
|
|
|
197
|
-
def _write(self):
|
|
230
|
+
def _write(self) -> None:
|
|
198
231
|
try:
|
|
199
232
|
with open(_CONFIG_FPATH, "w+", encoding="UTF-8") as fout:
|
|
200
233
|
json.dump(self._content, fout, indent=2)
|
|
@@ -202,176 +235,140 @@ class _Config:
|
|
|
202
235
|
pass
|
|
203
236
|
|
|
204
237
|
@property
|
|
205
|
-
def
|
|
206
|
-
return self._content[
|
|
238
|
+
def client_id(self) -> str:
|
|
239
|
+
return self._content[_CLIENT_ID_KEY]
|
|
207
240
|
|
|
208
241
|
@property
|
|
209
|
-
def host(self):
|
|
242
|
+
def host(self) -> str:
|
|
210
243
|
return self._content[_HOST_KEY]
|
|
211
244
|
|
|
212
245
|
@host.setter
|
|
213
|
-
def host(self, v):
|
|
246
|
+
def host(self, v: str) -> None:
|
|
214
247
|
self._content[_HOST_KEY] = v
|
|
215
248
|
self._write()
|
|
216
249
|
|
|
217
|
-
def get(self, k):
|
|
250
|
+
def get(self, k: str) -> str | None:
|
|
218
251
|
return self._content[_HOSTS_KEY][self.host].get(k)
|
|
219
252
|
|
|
220
|
-
def set(self, k, v):
|
|
253
|
+
def set(self, k: str, v: str) -> None:
|
|
221
254
|
self._content[_HOSTS_KEY][self.host][k] = v
|
|
222
255
|
self._write()
|
|
223
256
|
|
|
224
257
|
|
|
225
|
-
class
|
|
258
|
+
class Dart:
|
|
226
259
|
def __init__(self, config=None):
|
|
227
260
|
self._config = config or _Config()
|
|
228
|
-
self.
|
|
261
|
+
self._init_clients()
|
|
229
262
|
|
|
230
|
-
def
|
|
263
|
+
def _init_clients(self) -> None:
|
|
264
|
+
self._private_api = Client(
|
|
265
|
+
base_url=self.get_base_url() + _ROOT_PRIVATE_API_URL_FRAG,
|
|
266
|
+
headers=self.get_headers(),
|
|
267
|
+
)
|
|
268
|
+
self._public_api = Client(
|
|
269
|
+
base_url=self.get_base_url() + _ROOT_PUBLIC_API_URL_FRAG,
|
|
270
|
+
headers=self.get_headers(),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def get_base_url(self) -> str:
|
|
231
274
|
return self._config.host
|
|
232
275
|
|
|
233
|
-
def
|
|
234
|
-
return self._config.
|
|
276
|
+
def get_client_id(self) -> str:
|
|
277
|
+
return self._config.client_id
|
|
235
278
|
|
|
236
|
-
def get_auth_token(self):
|
|
279
|
+
def get_auth_token(self) -> str | None:
|
|
237
280
|
result = self._config.get(_AUTH_TOKEN_KEY)
|
|
238
281
|
if result is not None:
|
|
239
282
|
return result
|
|
240
283
|
return _AUTH_TOKEN_ENVVAR
|
|
241
284
|
|
|
242
|
-
def get_headers(self):
|
|
285
|
+
def get_headers(self) -> dict[str, str]:
|
|
243
286
|
result = {
|
|
244
287
|
"Origin": self._config.host,
|
|
245
|
-
"client-duid": self.
|
|
288
|
+
"client-duid": self.get_client_id(),
|
|
246
289
|
}
|
|
247
290
|
if (auth_token := self.get_auth_token()) is not None:
|
|
248
291
|
result["Authorization"] = f"Bearer {auth_token}"
|
|
249
292
|
return result
|
|
250
293
|
|
|
251
|
-
def
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
kwargs["headers"] = self.get_headers() | kwargs.get("headers", {})
|
|
261
|
-
return self._session.post(self._config.host + url_frag, *args, **kwargs)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
class Dart(Client):
|
|
265
|
-
def __init__(self, session=None):
|
|
266
|
-
self._session = session or _Session()
|
|
267
|
-
super().__init__(
|
|
268
|
-
base_url=self._session.get_base_url(),
|
|
269
|
-
headers=self._session.get_headers(),
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
def transact(self, operations: list[Operation], kind: TransactionKind):
|
|
273
|
-
transaction = Transaction(
|
|
274
|
-
duid=_make_duid(),
|
|
275
|
-
kind=kind,
|
|
276
|
-
operations=operations,
|
|
277
|
-
)
|
|
278
|
-
request_body = RequestBody(
|
|
279
|
-
client_duid=self._session.get_client_duid(),
|
|
280
|
-
items=[transaction],
|
|
281
|
-
)
|
|
282
|
-
return transactions_create.sync(
|
|
283
|
-
client=self,
|
|
284
|
-
body=request_body,
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
class UserBundle:
|
|
289
|
-
def __init__(self, session):
|
|
290
|
-
_log("Loading active tasks")
|
|
291
|
-
response = session.get(_USER_DATA_URL_FRAG)
|
|
292
|
-
_check_request_response_and_maybe_exit(response)
|
|
293
|
-
self._raw = response.json()
|
|
294
|
-
if not self.is_logged_in:
|
|
295
|
-
_auth_failure_exit()
|
|
296
|
-
|
|
297
|
-
@property
|
|
298
|
-
def is_logged_in(self):
|
|
299
|
-
return self._raw["isLoggedIn"]
|
|
300
|
-
|
|
301
|
-
@property
|
|
302
|
-
def user(self):
|
|
303
|
-
return self._raw["user"]
|
|
304
|
-
|
|
305
|
-
@property
|
|
306
|
-
def users(self):
|
|
307
|
-
return self._raw["users"]
|
|
308
|
-
|
|
309
|
-
@property
|
|
310
|
-
def properties(self):
|
|
311
|
-
return self._raw["properties"]
|
|
312
|
-
|
|
313
|
-
@property
|
|
314
|
-
def task_kinds(self):
|
|
315
|
-
return self._raw["taskKinds"]
|
|
316
|
-
|
|
317
|
-
@property
|
|
318
|
-
def default_dartboard(self):
|
|
319
|
-
space = next(
|
|
320
|
-
(e for e in self.spaces if e["kind"] == SpaceKind.WORKSPACE),
|
|
321
|
-
self.spaces[0],
|
|
322
|
-
)
|
|
323
|
-
space_duid = space["duid"]
|
|
324
|
-
dartboard_kind = (
|
|
325
|
-
DartboardKind.ACTIVE
|
|
326
|
-
if space["sprintMode"] == SprintMode.ANBA
|
|
327
|
-
else DartboardKind.CUSTOM
|
|
328
|
-
)
|
|
329
|
-
return next(
|
|
330
|
-
e
|
|
331
|
-
for e in self.dartboards
|
|
332
|
-
if e["spaceDuid"] == space_duid and e["kind"] == dartboard_kind
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
@property
|
|
336
|
-
def default_statuses(self):
|
|
337
|
-
default_status_property_duid = next(
|
|
338
|
-
e["duid"]
|
|
339
|
-
for e in self.properties
|
|
340
|
-
if e["kind"] == PropertyKind.DEFAULT_STATUS
|
|
341
|
-
)
|
|
342
|
-
return [
|
|
343
|
-
e
|
|
344
|
-
for e in self._raw["statuses"]
|
|
345
|
-
if e["propertyDuid"] == default_status_property_duid
|
|
346
|
-
]
|
|
347
|
-
|
|
348
|
-
@property
|
|
349
|
-
def default_tags(self):
|
|
350
|
-
default_tags_property_duid = next(
|
|
351
|
-
e["duid"] for e in self.properties if e["kind"] == PropertyKind.DEFAULT_TAGS
|
|
352
|
-
)
|
|
353
|
-
return [
|
|
354
|
-
e
|
|
355
|
-
for e in self._raw["options"]
|
|
356
|
-
if e["propertyDuid"] == default_tags_property_duid
|
|
357
|
-
]
|
|
358
|
-
|
|
359
|
-
@property
|
|
360
|
-
def spaces(self):
|
|
361
|
-
return self._raw["spaces"]
|
|
362
|
-
|
|
363
|
-
@property
|
|
364
|
-
def dartboards(self):
|
|
365
|
-
return self._raw["dartboards"]
|
|
294
|
+
def is_logged_in(self) -> bool:
|
|
295
|
+
self._init_clients()
|
|
296
|
+
try:
|
|
297
|
+
config = api.get_config.sync(client=self._public_api)
|
|
298
|
+
if config is None:
|
|
299
|
+
return False
|
|
300
|
+
except:
|
|
301
|
+
return False
|
|
302
|
+
return True
|
|
366
303
|
|
|
367
|
-
@
|
|
368
|
-
def
|
|
369
|
-
|
|
304
|
+
@_handle_request_errors
|
|
305
|
+
def get_config(self) -> UserSpaceConfiguration:
|
|
306
|
+
response = api.get_config.sync_detailed(client=self._public_api)
|
|
307
|
+
return _get_response_parsed(response)
|
|
308
|
+
|
|
309
|
+
@_handle_request_errors
|
|
310
|
+
def create_task(self, body: WrappedTaskCreate) -> WrappedTask:
|
|
311
|
+
response = api.create_task.sync_detailed(client=self._public_api, body=body)
|
|
312
|
+
return _get_response_parsed(response)
|
|
313
|
+
|
|
314
|
+
@_handle_request_errors
|
|
315
|
+
def retrieve_task(self, id: str) -> WrappedTask:
|
|
316
|
+
response = api.retrieve_task.sync_detailed(id, client=self._public_api)
|
|
317
|
+
return _get_response_parsed(response, not_found_message=f"Task with ID {id} not found.")
|
|
318
|
+
|
|
319
|
+
@_handle_request_errors
|
|
320
|
+
def update_task(self, id: str, body: WrappedTaskUpdate) -> WrappedTask:
|
|
321
|
+
response = api.update_task.sync_detailed(id, client=self._public_api, body=body)
|
|
322
|
+
return _get_response_parsed(response, not_found_message=f"Task with ID {id} not found.")
|
|
323
|
+
|
|
324
|
+
@_handle_request_errors
|
|
325
|
+
def delete_task(self, id: str) -> WrappedTask:
|
|
326
|
+
response = api.delete_task.sync_detailed(id, client=self._public_api)
|
|
327
|
+
return _get_response_parsed(response, not_found_message=f"Task with ID {id} not found.")
|
|
328
|
+
|
|
329
|
+
@_handle_request_errors
|
|
330
|
+
def list_tasks(self, **kwargs) -> PaginatedConciseTaskList:
|
|
331
|
+
response = api.list_tasks.sync_detailed(client=self._public_api, **kwargs)
|
|
332
|
+
return _get_response_parsed(response)
|
|
333
|
+
|
|
334
|
+
@_handle_request_errors
|
|
335
|
+
def create_comment(self, body: WrappedCommentCreate) -> WrappedComment:
|
|
336
|
+
response = api.create_comment.sync_detailed(client=self._public_api, body=body)
|
|
337
|
+
return _get_response_parsed(response)
|
|
338
|
+
|
|
339
|
+
@_handle_request_errors
|
|
340
|
+
def create_doc(self, body: WrappedDocCreate) -> WrappedDoc:
|
|
341
|
+
response = api.create_doc.sync_detailed(client=self._public_api, body=body)
|
|
342
|
+
return _get_response_parsed(response)
|
|
343
|
+
|
|
344
|
+
@_handle_request_errors
|
|
345
|
+
def retrieve_doc(self, id: str) -> WrappedDoc:
|
|
346
|
+
response = api.retrieve_doc.sync_detailed(id, client=self._public_api)
|
|
347
|
+
return _get_response_parsed(response, not_found_message=f"Doc with ID {id} not found.")
|
|
348
|
+
|
|
349
|
+
@_handle_request_errors
|
|
350
|
+
def update_doc(self, id: str, body: WrappedDocUpdate) -> WrappedDoc:
|
|
351
|
+
response = api.update_doc.sync_detailed(id, client=self._public_api, body=body)
|
|
352
|
+
return _get_response_parsed(response, not_found_message=f"Doc with ID {id} not found.")
|
|
353
|
+
|
|
354
|
+
@_handle_request_errors
|
|
355
|
+
def delete_doc(self, id: str) -> WrappedDoc:
|
|
356
|
+
response = api.delete_doc.sync_detailed(id, client=self._public_api)
|
|
357
|
+
return _get_response_parsed(response, not_found_message=f"Doc with ID {id} not found.")
|
|
358
|
+
|
|
359
|
+
@_handle_request_errors
|
|
360
|
+
def list_docs(self, **kwargs) -> PaginatedConciseDocList:
|
|
361
|
+
response = api.list_docs.sync_detailed(client=self._public_api, **kwargs)
|
|
362
|
+
return _get_response_parsed(response)
|
|
363
|
+
|
|
364
|
+
@_handle_request_errors
|
|
365
|
+
def copy_branch_link(self, id: str) -> None:
|
|
366
|
+
self._private_api.get_httpx_client().post(_COPY_BRANCH_URL_FRAG, json={"duid": id})
|
|
370
367
|
|
|
371
368
|
|
|
372
369
|
class _Git:
|
|
373
370
|
@staticmethod
|
|
374
|
-
def _cmd_succeeds(cmd):
|
|
371
|
+
def _cmd_succeeds(cmd: str) -> bool:
|
|
375
372
|
try:
|
|
376
373
|
_run_cmd(f"{cmd} 2>&1")
|
|
377
374
|
except subprocess.CalledProcessError as ex:
|
|
@@ -381,29 +378,29 @@ class _Git:
|
|
|
381
378
|
return True
|
|
382
379
|
|
|
383
380
|
@staticmethod
|
|
384
|
-
def make_task_name(email, task):
|
|
381
|
+
def make_task_name(email: str, task: ConciseTask | Task) -> str:
|
|
385
382
|
username = slugify_str(email.split("@")[0], lower=True)
|
|
386
383
|
title = slugify_str(task.title, lower=True)
|
|
387
|
-
return trim_slug_str(f"{username}/{task.
|
|
384
|
+
return trim_slug_str(f"{username}/{task.id}-{title}", length=60)
|
|
388
385
|
|
|
389
386
|
@staticmethod
|
|
390
|
-
def get_current_branch():
|
|
387
|
+
def get_current_branch() -> str:
|
|
391
388
|
return _run_cmd("git rev-parse --abbrev-ref HEAD").strip()
|
|
392
389
|
|
|
393
390
|
@staticmethod
|
|
394
|
-
def ensure_in_repo():
|
|
391
|
+
def ensure_in_repo() -> None:
|
|
395
392
|
if _Git._cmd_succeeds("git rev-parse --is-inside-work-tree"):
|
|
396
393
|
return
|
|
397
394
|
_dart_exit("You are not in a git repo.")
|
|
398
395
|
|
|
399
396
|
@staticmethod
|
|
400
|
-
def ensure_no_unstaged_changes():
|
|
397
|
+
def ensure_no_unstaged_changes() -> None:
|
|
401
398
|
if _run_cmd("git status --porcelain") == "":
|
|
402
399
|
return
|
|
403
400
|
_dart_exit("You have uncommitted changes. Please commit or stash them.")
|
|
404
401
|
|
|
405
402
|
@staticmethod
|
|
406
|
-
def ensure_on_main_or_intended():
|
|
403
|
+
def ensure_on_main_or_intended() -> None:
|
|
407
404
|
branch = _Git.get_current_branch()
|
|
408
405
|
if branch == "main":
|
|
409
406
|
return
|
|
@@ -419,11 +416,11 @@ class _Git:
|
|
|
419
416
|
_run_cmd("git checkout main")
|
|
420
417
|
|
|
421
418
|
@staticmethod
|
|
422
|
-
def branch_exists(branch):
|
|
419
|
+
def branch_exists(branch: str) -> bool:
|
|
423
420
|
return _Git._cmd_succeeds(f"git rev-parse --verify {branch}")
|
|
424
421
|
|
|
425
422
|
@staticmethod
|
|
426
|
-
def checkout_branch(branch):
|
|
423
|
+
def checkout_branch(branch: str) -> None:
|
|
427
424
|
if _Git.branch_exists(branch):
|
|
428
425
|
_run_cmd(f"git checkout {branch}")
|
|
429
426
|
return
|
|
@@ -456,39 +453,17 @@ def set_host(host: str) -> bool:
|
|
|
456
453
|
|
|
457
454
|
|
|
458
455
|
def _auth_failure_exit() -> NoReturn:
|
|
459
|
-
_dart_exit(
|
|
456
|
+
_dart_exit(
|
|
457
|
+
f"Not logged in, run\n\n {_PROG} {_LOGIN_CMD}\n\nto log in."
|
|
458
|
+
if _is_cli
|
|
459
|
+
else "Not logged in, either run dart.login(token) or save the token into the DART_TOKEN environment variable."
|
|
460
|
+
)
|
|
460
461
|
|
|
461
462
|
|
|
462
463
|
def _unknown_failure_exit() -> NoReturn:
|
|
463
464
|
_dart_exit("Unknown failure, email\n\n support@itsdart.com\n\nfor help.")
|
|
464
465
|
|
|
465
466
|
|
|
466
|
-
def _check_request_response_and_maybe_exit(response) -> None:
|
|
467
|
-
try:
|
|
468
|
-
response.raise_for_status()
|
|
469
|
-
except requests.exceptions.HTTPError:
|
|
470
|
-
if response.status_code in {401, 403}:
|
|
471
|
-
_auth_failure_exit()
|
|
472
|
-
_unknown_failure_exit()
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
def _parse_transaction_response_and_maybe_exit(
|
|
476
|
-
response, model_kind: str, duid: str
|
|
477
|
-
) -> Any:
|
|
478
|
-
if (
|
|
479
|
-
response is None
|
|
480
|
-
or not hasattr(response, "results")
|
|
481
|
-
or len(response.results) == 0
|
|
482
|
-
or not response.results[0].success
|
|
483
|
-
):
|
|
484
|
-
_unknown_failure_exit()
|
|
485
|
-
models = getattr(response.results[0].models, f"{model_kind}s")
|
|
486
|
-
model = next((e for e in models if e.duid == duid), None)
|
|
487
|
-
if model is None:
|
|
488
|
-
_unknown_failure_exit()
|
|
489
|
-
return model
|
|
490
|
-
|
|
491
|
-
|
|
492
467
|
def print_version() -> str:
|
|
493
468
|
result = f"dart-tools version {_VERSION}"
|
|
494
469
|
_log(result)
|
|
@@ -503,9 +478,7 @@ def print_version_update_message_maybe() -> None:
|
|
|
503
478
|
.split("\n", maxsplit=1)[0]
|
|
504
479
|
.strip()
|
|
505
480
|
)
|
|
506
|
-
if latest == _VERSION or [int(e) for e in latest.split(".")] <= [
|
|
507
|
-
int(e) for e in _VERSION.split(".")
|
|
508
|
-
]:
|
|
481
|
+
if latest == _VERSION or [int(e) for e in latest.split(".")] <= [int(e) for e in _VERSION.split(".")]:
|
|
509
482
|
return
|
|
510
483
|
|
|
511
484
|
_log(
|
|
@@ -513,15 +486,9 @@ def print_version_update_message_maybe() -> None:
|
|
|
513
486
|
)
|
|
514
487
|
|
|
515
488
|
|
|
516
|
-
def _get_is_logged_in(session: _Session) -> bool:
|
|
517
|
-
response = session.get(_USER_STATUS_URL_FRAG)
|
|
518
|
-
return response.json().get("isLoggedIn", False)
|
|
519
|
-
|
|
520
|
-
|
|
521
489
|
def is_logged_in(should_raise: bool = False) -> bool:
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
result = _get_is_logged_in(session)
|
|
490
|
+
dart = Dart()
|
|
491
|
+
result = dart.is_logged_in()
|
|
525
492
|
|
|
526
493
|
if not result and should_raise:
|
|
527
494
|
_auth_failure_exit()
|
|
@@ -531,21 +498,19 @@ def is_logged_in(should_raise: bool = False) -> bool:
|
|
|
531
498
|
|
|
532
499
|
def login(token: str | None = None) -> bool:
|
|
533
500
|
config = _Config()
|
|
534
|
-
|
|
501
|
+
dart = Dart(config=config)
|
|
535
502
|
|
|
536
503
|
_log("Log in to Dart")
|
|
537
504
|
if token is None:
|
|
538
505
|
if not _is_cli:
|
|
539
506
|
_dart_exit("Login failed, token is required.")
|
|
540
|
-
_log(
|
|
541
|
-
"Dart is opening in your browser, log in if needed and copy your authentication token from the page"
|
|
542
|
-
)
|
|
507
|
+
_log("Dart is opening in your browser, log in if needed and copy your authentication token from the page")
|
|
543
508
|
open_new_tab(config.host + _PROFILE_SETTINGS_URL_FRAG)
|
|
544
509
|
token = input("Token: ")
|
|
545
510
|
|
|
546
511
|
config.set(_AUTH_TOKEN_KEY, token)
|
|
547
512
|
|
|
548
|
-
worked =
|
|
513
|
+
worked = dart.is_logged_in()
|
|
549
514
|
if not worked:
|
|
550
515
|
_dart_exit("Invalid token.")
|
|
551
516
|
|
|
@@ -553,507 +518,207 @@ def login(token: str | None = None) -> bool:
|
|
|
553
518
|
return True
|
|
554
519
|
|
|
555
520
|
|
|
556
|
-
def _begin_task(
|
|
557
|
-
config: _Config, session: _Session, user_email: str, get_task: Callable
|
|
558
|
-
) -> bool:
|
|
521
|
+
def _begin_task(dart: Dart, email: str, task: ConciseTask | Task) -> bool:
|
|
559
522
|
_Git.ensure_in_repo()
|
|
560
523
|
_Git.ensure_no_unstaged_changes()
|
|
561
524
|
_Git.ensure_on_main_or_intended()
|
|
562
525
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
response = session.post(_COPY_BRANCH_URL_FRAG, json={"duid": task.duid})
|
|
566
|
-
_check_request_response_and_maybe_exit(response)
|
|
526
|
+
dart = Dart()
|
|
527
|
+
dart.copy_branch_link(task.id)
|
|
567
528
|
|
|
568
|
-
branch_name = _Git.make_task_name(
|
|
529
|
+
branch_name = _Git.make_task_name(email, task)
|
|
569
530
|
_Git.checkout_branch(branch_name)
|
|
570
531
|
|
|
571
|
-
_log(
|
|
572
|
-
f"Started work on\n\n {task.title}\n {_get_task_url(config.host, task.duid)}\n"
|
|
573
|
-
)
|
|
532
|
+
_log(f"Started work on\n\n {task.title}\n {task.html_url}\n")
|
|
574
533
|
return True
|
|
575
534
|
|
|
576
535
|
|
|
577
536
|
def begin_task() -> bool:
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
user = user_bundle.user
|
|
583
|
-
|
|
584
|
-
def _get_task():
|
|
585
|
-
user_duid = user["duid"]
|
|
586
|
-
active_duid = user_bundle.default_dartboard["duid"]
|
|
587
|
-
incomplete_status_duids = {
|
|
588
|
-
e["duid"]
|
|
589
|
-
for e in user_bundle.default_statuses
|
|
590
|
-
if e["kind"] not in _COMPLETED_STATUS_KINDS
|
|
591
|
-
}
|
|
592
|
-
filtered_tasks = [
|
|
593
|
-
e
|
|
594
|
-
for e in user_bundle.tasks
|
|
595
|
-
if not e["inTrash"]
|
|
596
|
-
and e["dartboardDuid"] == active_duid
|
|
597
|
-
and user_duid in e["assigneeDuids"]
|
|
598
|
-
and e["statusDuid"] in incomplete_status_duids
|
|
599
|
-
and e["drafterDuid"] is None
|
|
600
|
-
]
|
|
601
|
-
filtered_tasks.sort(key=lambda e: e["order"])
|
|
537
|
+
dart = Dart()
|
|
538
|
+
config = dart.get_config()
|
|
539
|
+
user = config.user
|
|
540
|
+
filtered_tasks = dart.list_tasks(assignee=user.email, is_completed=False).results
|
|
602
541
|
|
|
603
|
-
|
|
604
|
-
|
|
542
|
+
if not filtered_tasks:
|
|
543
|
+
_dart_exit("No active, incomplete tasks found.")
|
|
605
544
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
return TaskCreate.from_dict(filtered_tasks[picked_idx])
|
|
545
|
+
picked_idx = pick(
|
|
546
|
+
[e.title for e in filtered_tasks],
|
|
547
|
+
"Which of your active, incomplete tasks are you beginning work on?",
|
|
548
|
+
"→",
|
|
549
|
+
)[1]
|
|
550
|
+
assert isinstance(picked_idx, int)
|
|
613
551
|
|
|
614
|
-
_begin_task(
|
|
552
|
+
_begin_task(dart, user.email, filtered_tasks[picked_idx])
|
|
615
553
|
|
|
616
554
|
_log("Done.")
|
|
617
555
|
return True
|
|
618
556
|
|
|
619
557
|
|
|
558
|
+
def _normalize_priority(priority_int: int | None | Unset) -> str | None | Unset:
|
|
559
|
+
if priority_int in (None, UNSET):
|
|
560
|
+
return priority_int
|
|
561
|
+
|
|
562
|
+
if priority_int not in _PRIORITY_MAP:
|
|
563
|
+
_dart_exit(f"Invalid priority {priority_int}. Valid values are {list(_PRIORITY_MAP.keys())}.")
|
|
564
|
+
|
|
565
|
+
return _PRIORITY_MAP[priority_int]
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def _get_due_at_from_str_arg(due_at_str: str | None | Unset) -> str | None | Unset:
|
|
569
|
+
if due_at_str in (None, UNSET):
|
|
570
|
+
return due_at_str
|
|
571
|
+
|
|
572
|
+
due_at = dateparser.parse(due_at_str)
|
|
573
|
+
if not due_at:
|
|
574
|
+
_dart_exit(f"Could not parse due date '{due_at_str}'.")
|
|
575
|
+
due_at = due_at.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=timezone.utc).isoformat()
|
|
576
|
+
|
|
577
|
+
return due_at
|
|
578
|
+
|
|
579
|
+
|
|
620
580
|
def create_task(
|
|
621
581
|
title: str,
|
|
622
582
|
*,
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
size_int: int | None = None,
|
|
631
|
-
due_at_str: str | None = None,
|
|
583
|
+
dartboard_title: str | Unset = UNSET,
|
|
584
|
+
status_title: str | Unset = UNSET,
|
|
585
|
+
assignee_emails: list[str] | Unset = UNSET,
|
|
586
|
+
tag_titles: list[str] | Unset = UNSET,
|
|
587
|
+
priority_int: int | None | Unset = UNSET,
|
|
588
|
+
size_int: int | None | Unset = UNSET,
|
|
589
|
+
due_at_str: str | None | Unset = UNSET,
|
|
632
590
|
should_begin: bool = False,
|
|
633
591
|
) -> Task:
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
if dartboard_title is not None:
|
|
646
|
-
dartboard_title_norm = dartboard_title.strip().lower()
|
|
647
|
-
dartboard = next(
|
|
648
|
-
(
|
|
649
|
-
e
|
|
650
|
-
for e in dartboards
|
|
651
|
-
if dartboard_title_norm in {e["title"].lower(), e["kind"].lower()}
|
|
652
|
-
),
|
|
653
|
-
None,
|
|
654
|
-
)
|
|
655
|
-
if dartboard is None:
|
|
656
|
-
_dart_exit(f"No dartboard found with title '{dartboard_title}'.")
|
|
657
|
-
else:
|
|
658
|
-
dartboard = user_bundle.default_dartboard
|
|
659
|
-
dartboard_duid = dartboard["duid"]
|
|
660
|
-
|
|
661
|
-
orders = [
|
|
662
|
-
e["order"] for e in user_bundle.tasks if e["dartboardDuid"] == dartboard_duid
|
|
663
|
-
]
|
|
664
|
-
first_order = min(orders) if len(orders) > 0 else None
|
|
665
|
-
order = get_orders_between(None, first_order, 1)[0]
|
|
666
|
-
|
|
667
|
-
kinds = user_bundle.task_kinds
|
|
668
|
-
if kind_title is not None:
|
|
669
|
-
kind_title_norm = kind_title.strip().lower()
|
|
670
|
-
kind = next((e for e in kinds if e["title"].lower() == kind_title_norm), None)
|
|
671
|
-
if kind is None:
|
|
672
|
-
_dart_exit(f"No status found with title '{status_title}'.")
|
|
673
|
-
else:
|
|
674
|
-
kind = next(e for e in kinds if e["locked"])
|
|
675
|
-
kind_duid = kind["duid"]
|
|
676
|
-
|
|
677
|
-
statuses = user_bundle.default_statuses
|
|
678
|
-
if status_title is not None:
|
|
679
|
-
status_title_norm = status_title.strip().lower()
|
|
680
|
-
status = next(
|
|
681
|
-
(e for e in statuses if e["title"].lower() == status_title_norm), None
|
|
682
|
-
)
|
|
683
|
-
if status is None:
|
|
684
|
-
_dart_exit(f"No status found with title '{status_title}'.")
|
|
685
|
-
else:
|
|
686
|
-
status = next(
|
|
687
|
-
e for e in statuses if e["kind"] == StatusKind.UNSTARTED and e["locked"]
|
|
688
|
-
)
|
|
689
|
-
status_duid = status["duid"]
|
|
690
|
-
|
|
691
|
-
users = user_bundle.users
|
|
692
|
-
user_emails_to_duids = {e["email"]: e["duid"] for e in users}
|
|
693
|
-
assignee_duids = []
|
|
694
|
-
subscriber_duids = []
|
|
695
|
-
if assignee_emails is not None:
|
|
696
|
-
for assignee_email in assignee_emails:
|
|
697
|
-
assignee_email_norm = assignee_email.strip().lower()
|
|
698
|
-
if assignee_email_norm not in user_emails_to_duids:
|
|
699
|
-
_dart_exit(f"No user found with email '{assignee_email}'.")
|
|
700
|
-
assignee_duids.append(user_emails_to_duids[assignee_email_norm])
|
|
701
|
-
subscriber_duids.append(user_emails_to_duids[assignee_email_norm])
|
|
702
|
-
else:
|
|
703
|
-
assignee_duids.append(user_duid)
|
|
704
|
-
assignee_duids = list(set(assignee_duids))
|
|
705
|
-
subscriber_duids.append(user_duid)
|
|
706
|
-
subscriber_duids = list(set(subscriber_duids))
|
|
707
|
-
|
|
708
|
-
tags = user_bundle.default_tags
|
|
709
|
-
tag_titles_to_duids = {e["title"]: e["duid"] for e in tags}
|
|
710
|
-
tag_duids = []
|
|
711
|
-
if tag_titles is not None:
|
|
712
|
-
for tag_title in tag_titles:
|
|
713
|
-
tag_title_norm = tag_title.strip().lower()
|
|
714
|
-
if tag_title_norm not in tag_titles_to_duids:
|
|
715
|
-
_dart_exit(f"No tag found with title '{tag_title}'.")
|
|
716
|
-
tag_duids.append(tag_titles_to_duids[tag_title_norm])
|
|
717
|
-
|
|
718
|
-
priority = None
|
|
719
|
-
if priority_int is not None:
|
|
720
|
-
priority = _PRIORITY_MAP[priority_int]
|
|
721
|
-
|
|
722
|
-
size = size_int
|
|
723
|
-
|
|
724
|
-
due_at = None
|
|
725
|
-
if due_at_str is not None:
|
|
726
|
-
due_at = dateparser.parse(due_at_str)
|
|
727
|
-
if due_at is None:
|
|
728
|
-
_dart_exit(f"Could not parse due date '{due_at_str}'.")
|
|
729
|
-
due_at = due_at.replace(
|
|
730
|
-
hour=9, minute=0, second=0, microsecond=0, tzinfo=timezone.utc
|
|
592
|
+
dart = Dart()
|
|
593
|
+
task_create = WrappedTaskCreate(
|
|
594
|
+
item=TaskCreate(
|
|
595
|
+
title,
|
|
596
|
+
dartboard=dartboard_title,
|
|
597
|
+
status=status_title,
|
|
598
|
+
assignees=assignee_emails if assignee_emails is not None else UNSET,
|
|
599
|
+
tags=tag_titles if tag_titles is not None else UNSET,
|
|
600
|
+
priority=_normalize_priority(priority_int),
|
|
601
|
+
size=size_int,
|
|
602
|
+
due_at=_get_due_at_from_str_arg(due_at_str),
|
|
731
603
|
)
|
|
732
|
-
|
|
733
|
-
task_create = TaskCreate(
|
|
734
|
-
duid=_make_duid(),
|
|
735
|
-
source_type=TaskSourceType.CLI,
|
|
736
|
-
drafter_duid=None,
|
|
737
|
-
dartboard_duid=dartboard_duid,
|
|
738
|
-
order=order,
|
|
739
|
-
kind_duid=kind_duid,
|
|
740
|
-
title=title,
|
|
741
|
-
status_duid=status_duid,
|
|
742
|
-
assignee_duids=assignee_duids,
|
|
743
|
-
subscriber_duids=subscriber_duids,
|
|
744
|
-
tag_duids=tag_duids,
|
|
745
|
-
priority=priority,
|
|
746
|
-
size=size,
|
|
747
|
-
due_at=due_at,
|
|
748
|
-
)
|
|
749
|
-
task_create_op = Operation(
|
|
750
|
-
model=OperationModelKind.TASK,
|
|
751
|
-
kind=OperationKind.CREATE,
|
|
752
|
-
data=task_create,
|
|
753
604
|
)
|
|
754
|
-
|
|
755
|
-
task
|
|
756
|
-
response, OperationModelKind.TASK, task_create.duid
|
|
757
|
-
)
|
|
758
|
-
|
|
759
|
-
_log(f"Created task {task.title} at {_get_task_url(config.host, task.duid)}")
|
|
605
|
+
task = dart.create_task(task_create).item
|
|
606
|
+
_log(f"Created task (ID: {task.id}) {task.title} at {task.html_url}")
|
|
760
607
|
|
|
761
608
|
if should_begin:
|
|
762
|
-
|
|
609
|
+
user = dart.get_config().user
|
|
610
|
+
_begin_task(dart, user.email, task)
|
|
763
611
|
|
|
764
612
|
_log("Done.")
|
|
765
613
|
return task
|
|
766
614
|
|
|
767
615
|
|
|
768
616
|
def update_task(
|
|
769
|
-
|
|
617
|
+
id: str,
|
|
770
618
|
*,
|
|
771
|
-
title:
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
due_at_str: str | None = None,
|
|
619
|
+
title: Unset | str = UNSET,
|
|
620
|
+
dartboard_title: str | Unset = UNSET,
|
|
621
|
+
status_title: str | Unset = UNSET,
|
|
622
|
+
assignee_emails: list[str] | Unset = UNSET,
|
|
623
|
+
tag_titles: list[str] | Unset = UNSET,
|
|
624
|
+
priority_int: int | None | Unset = UNSET,
|
|
625
|
+
size_int: int | None | Unset = UNSET,
|
|
626
|
+
due_at_str: str | None | Unset = UNSET,
|
|
780
627
|
) -> Task:
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
_dart_exit(f"No task found with DUID '{duid}'.")
|
|
794
|
-
|
|
795
|
-
task_update_kwargs = {"duid": duid}
|
|
796
|
-
|
|
797
|
-
if title is not None:
|
|
798
|
-
task_update_kwargs["title"] = title
|
|
799
|
-
|
|
800
|
-
dartboards = user_bundle.dartboards
|
|
801
|
-
if dartboard_duid is not None:
|
|
802
|
-
task_update_kwargs["dartboard_duid"] = dartboard_duid
|
|
803
|
-
elif dartboard_title is not None:
|
|
804
|
-
dartboard_title_norm = dartboard_title.strip().lower()
|
|
805
|
-
dartboard = next(
|
|
806
|
-
(
|
|
807
|
-
e
|
|
808
|
-
for e in dartboards
|
|
809
|
-
if dartboard_title_norm in {e["title"].lower(), e["kind"].lower()}
|
|
810
|
-
),
|
|
811
|
-
None,
|
|
812
|
-
)
|
|
813
|
-
if dartboard is None:
|
|
814
|
-
_dart_exit(f"No dartboard found with title '{dartboard_title}'.")
|
|
815
|
-
dartboard_duid = dartboard["duid"]
|
|
816
|
-
if dartboard_duid != old_task["dartboardDuid"]:
|
|
817
|
-
task_update_kwargs["dartboard_duid"] = dartboard_duid
|
|
818
|
-
|
|
819
|
-
statuses = user_bundle.default_statuses
|
|
820
|
-
if status_title is not None:
|
|
821
|
-
status_title_norm = status_title.strip().lower()
|
|
822
|
-
status = next(
|
|
823
|
-
(e for e in statuses if e["title"].lower() == status_title_norm), None
|
|
824
|
-
)
|
|
825
|
-
if status is None:
|
|
826
|
-
_dart_exit(f"No status found with title '{status_title}'.")
|
|
827
|
-
status_duid = status["duid"]
|
|
828
|
-
if status_duid != old_task["statusDuid"]:
|
|
829
|
-
task_update_kwargs["status_duid"] = status_duid
|
|
830
|
-
|
|
831
|
-
users = user_bundle.users
|
|
832
|
-
user_emails_to_duids = {e["email"]: e["duid"] for e in users}
|
|
833
|
-
subscriber_duids = []
|
|
834
|
-
if assignee_emails is not None:
|
|
835
|
-
assignee_duids = []
|
|
836
|
-
for assignee_email in assignee_emails:
|
|
837
|
-
assignee_email_norm = assignee_email.strip().lower()
|
|
838
|
-
if assignee_email_norm not in user_emails_to_duids:
|
|
839
|
-
_dart_exit(f"No user found with email '{assignee_email}'.")
|
|
840
|
-
assignee_duids.append(user_emails_to_duids[assignee_email_norm])
|
|
841
|
-
subscriber_duids.append(user_emails_to_duids[assignee_email_norm])
|
|
842
|
-
assignee_duids = sorted(set(assignee_duids))
|
|
843
|
-
if assignee_duids != old_task["assigneeDuids"]:
|
|
844
|
-
task_update_kwargs["assignee_duids"] = assignee_duids
|
|
845
|
-
|
|
846
|
-
# TODO do add to list operation rather than replace
|
|
847
|
-
subscriber_duids = list(
|
|
848
|
-
set(old_task["subscriberDuids"]) | set(subscriber_duids) | {user_duid}
|
|
849
|
-
)
|
|
850
|
-
if subscriber_duids != old_task["subscriberDuids"]:
|
|
851
|
-
task_update_kwargs["subscriber_duids"] = subscriber_duids
|
|
852
|
-
|
|
853
|
-
tags = user_bundle.default_tags
|
|
854
|
-
tag_titles_to_duids = {e["title"]: e["duid"] for e in tags}
|
|
855
|
-
if tag_titles is not None:
|
|
856
|
-
tag_duids = []
|
|
857
|
-
for tag_title in tag_titles:
|
|
858
|
-
tag_title_norm = tag_title.strip().lower()
|
|
859
|
-
if tag_title_norm not in tag_titles_to_duids:
|
|
860
|
-
_dart_exit(f"No tag found with title '{tag_title}'.")
|
|
861
|
-
tag_duids.append(tag_titles_to_duids[tag_title_norm])
|
|
862
|
-
task_update_kwargs["tag_duids"] = tag_duids
|
|
863
|
-
|
|
864
|
-
# TODO add a way for optional stuff to be removed
|
|
865
|
-
if priority_int is not None:
|
|
866
|
-
priority = _PRIORITY_MAP[priority_int]
|
|
867
|
-
if priority != old_task["priority"]:
|
|
868
|
-
task_update_kwargs["priority"] = priority
|
|
869
|
-
|
|
870
|
-
if size_int is not None:
|
|
871
|
-
size = size_int
|
|
872
|
-
if size != old_task["size"]:
|
|
873
|
-
task_update_kwargs["size"] = size
|
|
874
|
-
|
|
875
|
-
if due_at_str is not None:
|
|
876
|
-
due_at = dateparser.parse(due_at_str)
|
|
877
|
-
if due_at is None:
|
|
878
|
-
_dart_exit(f"Could not parse due date '{due_at_str}'.")
|
|
879
|
-
due_at = due_at.replace(
|
|
880
|
-
hour=9, minute=0, second=0, microsecond=0, tzinfo=timezone.utc
|
|
628
|
+
dart = Dart()
|
|
629
|
+
task_update = WrappedTaskUpdate(
|
|
630
|
+
item=TaskUpdate(
|
|
631
|
+
id,
|
|
632
|
+
title=title,
|
|
633
|
+
dartboard=dartboard_title,
|
|
634
|
+
status=status_title,
|
|
635
|
+
assignees=assignee_emails if assignee_emails is not None else UNSET,
|
|
636
|
+
tags=tag_titles if tag_titles is not None else UNSET,
|
|
637
|
+
priority=_normalize_priority(priority_int),
|
|
638
|
+
size=size_int,
|
|
639
|
+
due_at=_get_due_at_from_str_arg(due_at_str),
|
|
881
640
|
)
|
|
882
|
-
due_at = due_at.isoformat()[:-6] + ".000Z"
|
|
883
|
-
if due_at != old_task["dueAt"]:
|
|
884
|
-
task_update_kwargs["due_at"] = due_at
|
|
885
|
-
|
|
886
|
-
task_update = TaskUpdate(**task_update_kwargs)
|
|
887
|
-
task_update_op = Operation(
|
|
888
|
-
model=OperationModelKind.TASK,
|
|
889
|
-
kind=OperationKind.UPDATE,
|
|
890
|
-
data=task_update,
|
|
891
|
-
)
|
|
892
|
-
response = dart.transact([task_update_op], TransactionKind.TASK_UPDATE)
|
|
893
|
-
task = _parse_transaction_response_and_maybe_exit(
|
|
894
|
-
response, OperationModelKind.TASK, task_update.duid
|
|
895
641
|
)
|
|
642
|
+
task = dart.update_task(id, task_update).item
|
|
896
643
|
|
|
897
|
-
_log(f"Updated task {task.title} at {
|
|
644
|
+
_log(f"Updated task (ID: {task.id}) {task.title} at {task.html_url}")
|
|
898
645
|
_log("Done.")
|
|
899
646
|
return task
|
|
900
647
|
|
|
901
648
|
|
|
902
|
-
def
|
|
903
|
-
duid: str,
|
|
904
|
-
*,
|
|
905
|
-
title: str | None = None,
|
|
906
|
-
abrev: str | None = None,
|
|
907
|
-
color_hex: str | None = None,
|
|
908
|
-
accessible_by_team: bool | None = None,
|
|
909
|
-
accessor_duids: list[str] | None = None,
|
|
910
|
-
) -> str:
|
|
911
|
-
config = _Config()
|
|
912
|
-
session = _Session(config)
|
|
913
|
-
|
|
914
|
-
content = {}
|
|
915
|
-
if title is not None:
|
|
916
|
-
content["title"] = title
|
|
917
|
-
if abrev is not None:
|
|
918
|
-
content["abrev"] = abrev
|
|
919
|
-
if color_hex is not None:
|
|
920
|
-
content["colorHex"] = color_hex
|
|
921
|
-
if accessible_by_team is not None:
|
|
922
|
-
content["accessibleByTeam"] = accessible_by_team
|
|
923
|
-
if accessor_duids is not None:
|
|
924
|
-
content["accessorIds"] = accessor_duids
|
|
925
|
-
response = session.post(
|
|
926
|
-
_REPLICATE_SPACE_URL_FRAG_FMT.format(duid=duid), json=content
|
|
927
|
-
)
|
|
928
|
-
_check_request_response_and_maybe_exit(response)
|
|
929
|
-
|
|
930
|
-
space_duid = response.json()["duid"]
|
|
931
|
-
|
|
932
|
-
_log(f"Replicated space at {_get_space_url(config.host, space_duid)}")
|
|
933
|
-
_log("Done.")
|
|
934
|
-
return space_duid
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
def get_dartboards(space_duid: str, include_special: bool = False) -> list[Dartboard]:
|
|
649
|
+
def delete_task(id: str) -> Task:
|
|
938
650
|
dart = Dart()
|
|
651
|
+
task = dart.delete_task(id).item
|
|
939
652
|
|
|
940
|
-
|
|
941
|
-
dartboards = response.results if response is not None else []
|
|
942
|
-
if not include_special:
|
|
943
|
-
dartboards = [e for e in dartboards if e.kind == DartboardKind.CUSTOM]
|
|
944
|
-
|
|
945
|
-
_log(f"Got {len(dartboards)} dartboards")
|
|
653
|
+
_log(f"Deleted task (ID: {task.id}) {task.title} at {task.html_url}")
|
|
946
654
|
_log("Done.")
|
|
947
|
-
return
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
def replicate_dartboard(duid: str, *, title: str | None = None) -> str:
|
|
951
|
-
config = _Config()
|
|
952
|
-
session = _Session(config)
|
|
655
|
+
return task
|
|
953
656
|
|
|
954
|
-
content = {}
|
|
955
|
-
if title is not None:
|
|
956
|
-
content["title"] = title
|
|
957
|
-
response = session.post(
|
|
958
|
-
_REPLICATE_DARTBOARD_URL_FRAG_FMT.format(duid=duid), json=content
|
|
959
|
-
)
|
|
960
|
-
_check_request_response_and_maybe_exit(response)
|
|
961
657
|
|
|
962
|
-
|
|
658
|
+
def create_doc(
|
|
659
|
+
title: str,
|
|
660
|
+
*,
|
|
661
|
+
folder: str | Unset = UNSET,
|
|
662
|
+
text: str | Unset = UNSET,
|
|
663
|
+
) -> Doc:
|
|
664
|
+
dart = Dart()
|
|
665
|
+
doc_create = WrappedDocCreate(item=DocCreate(title=title, folder=folder, text=text))
|
|
666
|
+
doc = dart.create_doc(doc_create).item
|
|
963
667
|
|
|
964
|
-
_log(f"
|
|
668
|
+
_log(f"Created doc (ID: {doc.id}) {doc.title} at {doc.html_url}")
|
|
965
669
|
_log("Done.")
|
|
966
|
-
return
|
|
670
|
+
return doc
|
|
967
671
|
|
|
968
672
|
|
|
969
|
-
def
|
|
970
|
-
|
|
673
|
+
def update_doc(
|
|
674
|
+
id: str,
|
|
971
675
|
*,
|
|
972
|
-
title: str
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
dartboard_update_kwargs = {"duid": duid}
|
|
980
|
-
|
|
981
|
-
if title is not None:
|
|
982
|
-
dartboard_update_kwargs["title"] = title
|
|
983
|
-
if color_hex is not None:
|
|
984
|
-
dartboard_update_kwargs["color_hex"] = color_hex
|
|
985
|
-
|
|
986
|
-
dartboard_update = DartboardUpdate(**dartboard_update_kwargs)
|
|
987
|
-
dartboard_update_op = Operation(
|
|
988
|
-
model=OperationModelKind.DARTBOARD,
|
|
989
|
-
kind=OperationKind.UPDATE,
|
|
990
|
-
data=dartboard_update,
|
|
991
|
-
)
|
|
992
|
-
response = dart.transact([dartboard_update_op], TransactionKind.DARTBOARD_UPDATE)
|
|
993
|
-
dartboard = _parse_transaction_response_and_maybe_exit(
|
|
994
|
-
response, OperationModelKind.DARTBOARD, dartboard_update.duid
|
|
995
|
-
)
|
|
676
|
+
title: str,
|
|
677
|
+
folder: str | Unset = UNSET,
|
|
678
|
+
text: str | Unset = UNSET,
|
|
679
|
+
) -> Doc:
|
|
680
|
+
dart = Dart()
|
|
681
|
+
doc_update = WrappedDocUpdate(item=DocUpdate(id, title=title, folder=folder, text=text))
|
|
682
|
+
doc = dart.update_doc(id, doc_update).item
|
|
996
683
|
|
|
997
|
-
_log(
|
|
998
|
-
f"Updated dartboard {dartboard.title} at {_get_dartboard_url(config.host, dartboard.duid)}"
|
|
999
|
-
)
|
|
684
|
+
_log(f"Updated doc (ID: {doc.id}) {doc.title} at {doc.html_url}")
|
|
1000
685
|
_log("Done.")
|
|
1001
|
-
return
|
|
686
|
+
return doc
|
|
1002
687
|
|
|
1003
688
|
|
|
1004
|
-
def
|
|
689
|
+
def delete_doc(id: str) -> Doc:
|
|
1005
690
|
dart = Dart()
|
|
691
|
+
doc = dart.delete_doc(id).item
|
|
1006
692
|
|
|
1007
|
-
|
|
1008
|
-
folders = response.results if response is not None else []
|
|
1009
|
-
if not include_special:
|
|
1010
|
-
folders = [e for e in folders if e.kind == FolderKind.OTHER]
|
|
1011
|
-
|
|
1012
|
-
_log(f"Got {len(folders)} folders")
|
|
693
|
+
_log(f"Deleted doc (ID: {doc.id}) {doc.title} at {doc.html_url}")
|
|
1013
694
|
_log("Done.")
|
|
1014
|
-
return
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
def update_folder(
|
|
1018
|
-
duid: str,
|
|
1019
|
-
*,
|
|
1020
|
-
title: str | None = None,
|
|
1021
|
-
color_hex: str | None = None,
|
|
1022
|
-
) -> Folder:
|
|
1023
|
-
config = _Config()
|
|
1024
|
-
session = _Session(config)
|
|
1025
|
-
dart = Dart(session)
|
|
695
|
+
return doc
|
|
1026
696
|
|
|
1027
|
-
folder_update_kwargs = {"duid": duid}
|
|
1028
697
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
folder_update = FolderUpdate(**folder_update_kwargs)
|
|
1035
|
-
folder_update_op = Operation(
|
|
1036
|
-
model=OperationModelKind.FOLDER,
|
|
1037
|
-
kind=OperationKind.UPDATE,
|
|
1038
|
-
data=folder_update,
|
|
1039
|
-
)
|
|
1040
|
-
response = dart.transact([folder_update_op], TransactionKind.FOLDER_UPDATE)
|
|
1041
|
-
folder = _parse_transaction_response_and_maybe_exit(
|
|
1042
|
-
response, OperationModelKind.FOLDER, folder_update.duid
|
|
1043
|
-
)
|
|
1044
|
-
|
|
1045
|
-
_log(
|
|
1046
|
-
f"Updated folder {folder.title} at {_get_folder_url(config.host, folder.duid)}"
|
|
1047
|
-
)
|
|
698
|
+
def create_comment(id: str, text: str) -> Comment:
|
|
699
|
+
dart = Dart()
|
|
700
|
+
comment_create = WrappedCommentCreate(item=CommentCreate(task_id=id, text=text))
|
|
701
|
+
comment = dart.create_comment(comment_create).item
|
|
702
|
+
_log(f"Created comment (ID: {comment.id}) at {comment.html_url}")
|
|
1048
703
|
_log("Done.")
|
|
1049
|
-
return
|
|
704
|
+
return comment
|
|
1050
705
|
|
|
1051
706
|
|
|
1052
707
|
def _add_standard_task_arguments(parser: ArgumentParser) -> None:
|
|
1053
708
|
parser.add_argument(
|
|
1054
|
-
"-d",
|
|
709
|
+
"-d",
|
|
710
|
+
"--dartboard",
|
|
711
|
+
dest="dartboard_title",
|
|
712
|
+
help="dartboard title",
|
|
713
|
+
default=UNSET,
|
|
714
|
+
)
|
|
715
|
+
parser.add_argument(
|
|
716
|
+
"-s",
|
|
717
|
+
"--status",
|
|
718
|
+
dest="status_title",
|
|
719
|
+
help="status title",
|
|
720
|
+
default=UNSET,
|
|
1055
721
|
)
|
|
1056
|
-
parser.add_argument("-s", "--status", dest="status_title", help="status title")
|
|
1057
722
|
parser.add_argument(
|
|
1058
723
|
"-a",
|
|
1059
724
|
"--assignee",
|
|
@@ -1077,11 +742,24 @@ def _add_standard_task_arguments(parser: ArgumentParser) -> None:
|
|
|
1077
742
|
type=int,
|
|
1078
743
|
choices=_PRIORITY_MAP.keys(),
|
|
1079
744
|
help="priority",
|
|
745
|
+
default=UNSET,
|
|
746
|
+
)
|
|
747
|
+
parser.add_argument(
|
|
748
|
+
"-i",
|
|
749
|
+
"--size",
|
|
750
|
+
dest="size_int",
|
|
751
|
+
type=int,
|
|
752
|
+
choices=_SIZES,
|
|
753
|
+
help="size",
|
|
754
|
+
default=UNSET,
|
|
1080
755
|
)
|
|
1081
756
|
parser.add_argument(
|
|
1082
|
-
"-
|
|
757
|
+
"-r",
|
|
758
|
+
"--duedate",
|
|
759
|
+
dest="due_at_str",
|
|
760
|
+
help="due date",
|
|
761
|
+
default=UNSET,
|
|
1083
762
|
)
|
|
1084
|
-
parser.add_argument("-r", "--duedate", dest="due_at_str", help="due date")
|
|
1085
763
|
|
|
1086
764
|
|
|
1087
765
|
def cli() -> None:
|
|
@@ -1096,24 +774,35 @@ def cli() -> None:
|
|
|
1096
774
|
return
|
|
1097
775
|
|
|
1098
776
|
parser = ArgumentParser(prog=_PROG, description="A CLI to interact with Dart")
|
|
777
|
+
metavar = ",".join(
|
|
778
|
+
[
|
|
779
|
+
_LOGIN_CMD,
|
|
780
|
+
_CREATE_TASK_CMD,
|
|
781
|
+
_UPDATE_TASK_CMD,
|
|
782
|
+
_DELETE_TASK_CMD,
|
|
783
|
+
_BEGIN_TASK_CMD,
|
|
784
|
+
_CREATE_DOC_CMD,
|
|
785
|
+
_UPDATE_DOC_CMD,
|
|
786
|
+
_DELETE_DOC_CMD,
|
|
787
|
+
_CREATE_COMMENT_CMD,
|
|
788
|
+
]
|
|
789
|
+
)
|
|
1099
790
|
subparsers = parser.add_subparsers(
|
|
1100
791
|
title="command",
|
|
1101
792
|
required=True,
|
|
1102
|
-
metavar=f"{{{
|
|
793
|
+
metavar=f"{{{metavar}}}",
|
|
1103
794
|
)
|
|
1104
795
|
|
|
1105
|
-
set_host_parser = subparsers.add_parser(_SET_HOST_CMD, aliases="
|
|
796
|
+
set_host_parser = subparsers.add_parser(_SET_HOST_CMD, aliases=["h"])
|
|
1106
797
|
set_host_parser.add_argument("host", help="the new host: {prod|stag|dev|[URL]}")
|
|
1107
798
|
set_host_parser.set_defaults(func=set_host)
|
|
1108
799
|
|
|
1109
|
-
login_parser = subparsers.add_parser(_LOGIN_CMD, aliases="l", help="login")
|
|
1110
|
-
login_parser.add_argument(
|
|
1111
|
-
"-t", "--token", dest="token", help="your authentication token"
|
|
1112
|
-
)
|
|
800
|
+
login_parser = subparsers.add_parser(_LOGIN_CMD, aliases=["l"], help="login")
|
|
801
|
+
login_parser.add_argument("-t", "--token", dest="token", help="your authentication token")
|
|
1113
802
|
login_parser.set_defaults(func=login)
|
|
1114
803
|
|
|
1115
804
|
create_task_parser = subparsers.add_parser(
|
|
1116
|
-
_CREATE_TASK_CMD, aliases="
|
|
805
|
+
_CREATE_TASK_CMD, aliases=["tc"], help=_HELP_TEXT_TO_COMMAND[_CREATE_TASK_CMD]
|
|
1117
806
|
)
|
|
1118
807
|
create_task_parser.add_argument("title", help="title of the task")
|
|
1119
808
|
create_task_parser.add_argument(
|
|
@@ -1127,18 +816,52 @@ def cli() -> None:
|
|
|
1127
816
|
create_task_parser.set_defaults(func=create_task)
|
|
1128
817
|
|
|
1129
818
|
update_task_parser = subparsers.add_parser(
|
|
1130
|
-
_UPDATE_TASK_CMD, aliases="
|
|
819
|
+
_UPDATE_TASK_CMD, aliases=["tu"], help=_HELP_TEXT_TO_COMMAND[_UPDATE_TASK_CMD]
|
|
1131
820
|
)
|
|
1132
|
-
update_task_parser.add_argument("
|
|
1133
|
-
update_task_parser.add_argument("-e", "--title", dest="title", help="task title")
|
|
821
|
+
update_task_parser.add_argument("id", help="ID of the task")
|
|
822
|
+
update_task_parser.add_argument("-e", "--title", dest="title", help="task title", default=UNSET)
|
|
1134
823
|
_add_standard_task_arguments(update_task_parser)
|
|
1135
824
|
update_task_parser.set_defaults(func=update_task)
|
|
1136
825
|
|
|
1137
|
-
|
|
1138
|
-
|
|
826
|
+
delete_task_parser = subparsers.add_parser(
|
|
827
|
+
_DELETE_TASK_CMD, aliases=["td"], help=_HELP_TEXT_TO_COMMAND[_DELETE_TASK_CMD]
|
|
1139
828
|
)
|
|
829
|
+
delete_task_parser.add_argument("id", help="ID of the task")
|
|
830
|
+
delete_task_parser.set_defaults(func=delete_task)
|
|
831
|
+
|
|
832
|
+
begin_task_parser = subparsers.add_parser(_BEGIN_TASK_CMD, aliases=["tb"], help="begin work on a task")
|
|
1140
833
|
begin_task_parser.set_defaults(func=begin_task)
|
|
1141
834
|
|
|
835
|
+
create_doc_parser = subparsers.add_parser(
|
|
836
|
+
_CREATE_DOC_CMD, aliases=["dc"], help=_HELP_TEXT_TO_COMMAND[_CREATE_DOC_CMD]
|
|
837
|
+
)
|
|
838
|
+
create_doc_parser.add_argument("title", help="title of the doc")
|
|
839
|
+
create_doc_parser.add_argument("-f", "--folder", dest="folder", help="doc folder", default=UNSET)
|
|
840
|
+
create_doc_parser.add_argument("-t", "--text", dest="text", help="doc text", default=UNSET)
|
|
841
|
+
create_doc_parser.set_defaults(func=create_doc)
|
|
842
|
+
|
|
843
|
+
update_doc_parser = subparsers.add_parser(
|
|
844
|
+
_UPDATE_DOC_CMD, aliases=["du"], help=_HELP_TEXT_TO_COMMAND[_UPDATE_DOC_CMD]
|
|
845
|
+
)
|
|
846
|
+
update_doc_parser.add_argument("id", help="ID of the doc")
|
|
847
|
+
update_doc_parser.add_argument("-e", "--title", dest="title", help="doc title", default=UNSET)
|
|
848
|
+
update_doc_parser.add_argument("-f", "--folder", dest="folder", help="doc folder", default=UNSET)
|
|
849
|
+
update_doc_parser.add_argument("-t", "--text", dest="text", help="doc text", default=UNSET)
|
|
850
|
+
update_doc_parser.set_defaults(func=update_doc)
|
|
851
|
+
|
|
852
|
+
delete_doc_parser = subparsers.add_parser(
|
|
853
|
+
_DELETE_DOC_CMD, aliases=["dd"], help=_HELP_TEXT_TO_COMMAND[_DELETE_DOC_CMD]
|
|
854
|
+
)
|
|
855
|
+
delete_doc_parser.add_argument("id", help="ID of the doc")
|
|
856
|
+
delete_doc_parser.set_defaults(func=delete_doc)
|
|
857
|
+
|
|
858
|
+
create_comment_parser = subparsers.add_parser(
|
|
859
|
+
_CREATE_COMMENT_CMD, aliases=["cc"], help=_HELP_TEXT_TO_COMMAND[_CREATE_COMMENT_CMD]
|
|
860
|
+
)
|
|
861
|
+
create_comment_parser.add_argument("id", help="ID of the task")
|
|
862
|
+
create_comment_parser.add_argument("text", help="text of the comment")
|
|
863
|
+
create_comment_parser.set_defaults(func=create_comment)
|
|
864
|
+
|
|
1142
865
|
args = vars(parser.parse_args())
|
|
1143
866
|
func = args.pop("func")
|
|
1144
867
|
func(**args)
|