dex-connector-clickup 0.1.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.
- dex_connector_clickup-0.1.0.dist-info/METADATA +21 -0
- dex_connector_clickup-0.1.0.dist-info/RECORD +21 -0
- dex_connector_clickup-0.1.0.dist-info/WHEEL +4 -0
- dex_connector_clickup-0.1.0.dist-info/licenses/LICENSE +21 -0
- dex_connectors/clickup/__init__.py +71 -0
- dex_connectors/clickup/_generated/__init__.py +2 -0
- dex_connectors/clickup/_generated/config.py +25 -0
- dex_connectors/clickup/_generated/connection.py +175 -0
- dex_connectors/clickup/_generated/definitions.py +147 -0
- dex_connectors/clickup/_generated/mock.py +428 -0
- dex_connectors/clickup/_generated/models.py +425 -0
- dex_connectors/clickup/_generated/steps.py +402 -0
- dex_connectors/clickup/_generated/timestamps.py +60 -0
- dex_connectors/clickup/clickupmock/__init__.py +6 -0
- dex_connectors/clickup/client.py +602 -0
- dex_connectors/clickup/connection.py +8 -0
- dex_connectors/clickup/dex-connector.json +277 -0
- dex_connectors/clickup/models.py +24 -0
- dex_connectors/clickup/operations.py +1013 -0
- dex_connectors/clickup/py.typed +0 -0
- dex_connectors/clickup/steps.py +11 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dex-connector-clickup
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: ClickUp connector for Dex Flows
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: dex-connectors-sdk==0.1.0
|
|
9
|
+
Requires-Dist: dex-python-sdk>=1.28.0
|
|
10
|
+
Requires-Dist: httpx>=0.27
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# ClickUp Python connector
|
|
14
|
+
|
|
15
|
+
The package implements the ClickUp `searchTasks`, `getTask`, `createTask`, `updateTask`, `updateTaskTags`, `addComment`, and `findMemberByEmail` operations for Dex Flows. The `taskEvent` webhook Trigger remains Go-only. Install `dex-connector-clickup==0.1.0` alongside a Dex Python application and load its named connection from Dex project configuration. The connection uses a ClickUp personal API token and resolves it before each provider request.
|
|
16
|
+
|
|
17
|
+
The [getTask runnable example](examples/get_task/README.md) uses a static `clickup-workspace` connection and a real Dex Worker and Client. Its integration test exercises a retry and an unwired rejection branch against a local ClickUp stand-in. Run from this directory with `uv sync --locked`, then `uv run pytest tests/ examples/get_task/integration_test.py` while `dexcli dev` is running and `DEX_FLOW_SERVICE_ADDRESS` points to it.
|
|
18
|
+
|
|
19
|
+
`createTask` and `addComment` store a Dex heartbeat checkpoint before an unkeyed write. If a retry sees the checkpoint, it reads back instead of sending the write again; an unconfirmable outcome selects `uncertain`. The provider has no idempotency-key support for these calls. The Python package currently has no live ClickUp authorization or API verification.
|
|
20
|
+
|
|
21
|
+
`getTask` returns custom field JSON values only when Python can represent their numbers exactly. A value that would lose precision selects `invalidResponse` and identifies the field. `updateTaskTags` bounds its sequence of provider requests to 25 seconds; an incomplete sequence retries.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
dex_connectors/clickup/__init__.py,sha256=Eaaa0BjxIgjX3WTXHyLXRh60Sz9fpAsiXza2qoZ2zdg,2707
|
|
2
|
+
dex_connectors/clickup/client.py,sha256=1_ed8nbOiW5T1mhjJdXrQP5ZIX0NvTR-YJ4y8qTBFiU,23257
|
|
3
|
+
dex_connectors/clickup/connection.py,sha256=wFi4Yucb6tYGqUKV6MLyNTV9np84kd-mpiOYRGB1lkw,424
|
|
4
|
+
dex_connectors/clickup/dex-connector.json,sha256=cKiQD2qoPB4xT8ktLsuIPWdL2R4JHPdDt1rcXrJ7hcU,6419
|
|
5
|
+
dex_connectors/clickup/models.py,sha256=kp2R3g5rCEDwyilbmOsRoVIVX-3RPDGgY5H5X7fDJRY,1380
|
|
6
|
+
dex_connectors/clickup/operations.py,sha256=M8SCVE5Mz-2eq5mbL5bZy7ZnRDKgm5AlE6X8Tn0G6yc,41564
|
|
7
|
+
dex_connectors/clickup/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
dex_connectors/clickup/steps.py,sha256=ngOlQdc2Qvb9uzhmdlBZOWIJwUTMaIC04tHOOJ4zNj0,667
|
|
9
|
+
dex_connectors/clickup/_generated/__init__.py,sha256=ShfMsAGzSt4xuhCaf20ibgtfXl4pXomZb00ovUw6CeI,66
|
|
10
|
+
dex_connectors/clickup/_generated/config.py,sha256=A2ygvyxiyu7X-WQ2iBV7jEdItNlSNPhtxniVelSGrNA,544
|
|
11
|
+
dex_connectors/clickup/_generated/connection.py,sha256=EN4J1dYeizOhLonHHpitihD6QooRw9hGWdlccUMTOAI,8997
|
|
12
|
+
dex_connectors/clickup/_generated/definitions.py,sha256=dMjE5iVMbL31H-1_uTPH0e0ePD8vjPbR4I_Wu4CCPug,7435
|
|
13
|
+
dex_connectors/clickup/_generated/mock.py,sha256=h1su5PqovNUOd0I1cCY8o2pxHOrQkaXntz7WesaTxXc,24792
|
|
14
|
+
dex_connectors/clickup/_generated/models.py,sha256=_lUiEUxgzn0i4MhTMbSEpxcWcdYWIngmirRLuBg9hAg,24681
|
|
15
|
+
dex_connectors/clickup/_generated/steps.py,sha256=59KdwrQNzAfe7RrmBLEUXV3D9Ssx1ss89kXV_Z0WJe4,18804
|
|
16
|
+
dex_connectors/clickup/_generated/timestamps.py,sha256=Z8VwrE60w-33InIMgEMLiMPTFyRjj6oBq8tUro8Ssi8,3164
|
|
17
|
+
dex_connectors/clickup/clickupmock/__init__.py,sha256=sEODfqQ6BJ0_cxRmH7FUHlzuhGC26jntpGD0cmUtm_U,167
|
|
18
|
+
dex_connector_clickup-0.1.0.dist-info/METADATA,sha256=mQs1D691Ivgp2Tl8kKVMp4-i7bXY2_KCPTFKaXOIbzg,1888
|
|
19
|
+
dex_connector_clickup-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
20
|
+
dex_connector_clickup-0.1.0.dist-info/licenses/LICENSE,sha256=WNj87Zd5micrAShl1LujOPM6PDzDqjlNLrryDZ4hE0M,1070
|
|
21
|
+
dex_connector_clickup-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Super Durable
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Dex-native ClickUp task operations."""
|
|
4
|
+
|
|
5
|
+
from .client import Client as Client
|
|
6
|
+
from .client import Config as Config
|
|
7
|
+
from .client import Credentials as Credentials
|
|
8
|
+
from .connection import Connection as Connection
|
|
9
|
+
from .connection import new_connection as new_connection
|
|
10
|
+
from .connection import new_connection_with_operations as new_connection_with_operations
|
|
11
|
+
from .connection import new_project_connection as new_project_connection
|
|
12
|
+
from .models import AddCommentInput as AddCommentInput
|
|
13
|
+
from .models import AddCommentOutput as AddCommentOutput
|
|
14
|
+
from .models import CreateTaskInput as CreateTaskInput
|
|
15
|
+
from .models import CreateTaskOutput as CreateTaskOutput
|
|
16
|
+
from .models import CustomFieldInput as CustomFieldInput
|
|
17
|
+
from .models import CustomFieldValue as CustomFieldValue
|
|
18
|
+
from .models import FindMemberByEmailInput as FindMemberByEmailInput
|
|
19
|
+
from .models import GetTaskInput as GetTaskInput
|
|
20
|
+
from .models import Member as Member
|
|
21
|
+
from .models import SearchTasksInput as SearchTasksInput
|
|
22
|
+
from .models import SearchTasksOutput as SearchTasksOutput
|
|
23
|
+
from .models import Task as Task
|
|
24
|
+
from .models import TaskStatus as TaskStatus
|
|
25
|
+
from .models import UpdateTaskInput as UpdateTaskInput
|
|
26
|
+
from .models import UpdateTaskOutput as UpdateTaskOutput
|
|
27
|
+
from .models import UpdateTaskTagsInput as UpdateTaskTagsInput
|
|
28
|
+
from .models import UpdateTaskTagsOutput as UpdateTaskTagsOutput
|
|
29
|
+
from .models import User as User
|
|
30
|
+
from .steps import new_add_comment_step as new_add_comment_step
|
|
31
|
+
from .steps import new_create_task_step as new_create_task_step
|
|
32
|
+
from .steps import new_find_member_by_email_step as new_find_member_by_email_step
|
|
33
|
+
from .steps import new_get_task_step as new_get_task_step
|
|
34
|
+
from .steps import new_search_tasks_step as new_search_tasks_step
|
|
35
|
+
from .steps import new_update_task_step as new_update_task_step
|
|
36
|
+
from .steps import new_update_task_tags_step as new_update_task_tags_step
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"AddCommentInput",
|
|
40
|
+
"AddCommentOutput",
|
|
41
|
+
"Client",
|
|
42
|
+
"Config",
|
|
43
|
+
"Connection",
|
|
44
|
+
"CreateTaskInput",
|
|
45
|
+
"CreateTaskOutput",
|
|
46
|
+
"Credentials",
|
|
47
|
+
"CustomFieldInput",
|
|
48
|
+
"CustomFieldValue",
|
|
49
|
+
"FindMemberByEmailInput",
|
|
50
|
+
"GetTaskInput",
|
|
51
|
+
"Member",
|
|
52
|
+
"SearchTasksInput",
|
|
53
|
+
"SearchTasksOutput",
|
|
54
|
+
"Task",
|
|
55
|
+
"TaskStatus",
|
|
56
|
+
"UpdateTaskInput",
|
|
57
|
+
"UpdateTaskOutput",
|
|
58
|
+
"UpdateTaskTagsInput",
|
|
59
|
+
"UpdateTaskTagsOutput",
|
|
60
|
+
"User",
|
|
61
|
+
"new_add_comment_step",
|
|
62
|
+
"new_connection",
|
|
63
|
+
"new_connection_with_operations",
|
|
64
|
+
"new_create_task_step",
|
|
65
|
+
"new_find_member_by_email_step",
|
|
66
|
+
"new_get_task_step",
|
|
67
|
+
"new_project_connection",
|
|
68
|
+
"new_search_tasks_step",
|
|
69
|
+
"new_update_task_step",
|
|
70
|
+
"new_update_task_tags_step",
|
|
71
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
# ruff: noqa
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from dex_connectors.sdk import SecretString
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class Config:
|
|
12
|
+
|
|
13
|
+
max_response_bytes: int = 4194304
|
|
14
|
+
webhook_max_body_bytes: int = 1048576
|
|
15
|
+
|
|
16
|
+
setattr(Config, '__module__', 'dex_connectors.clickup.client')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class Credentials:
|
|
21
|
+
|
|
22
|
+
api_token: SecretString
|
|
23
|
+
webhook_secret: SecretString
|
|
24
|
+
|
|
25
|
+
setattr(Credentials, '__module__', 'dex_connectors.clickup.client')
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
# ruff: noqa
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from dex_connectors.sdk import ConnectionRef, Mutation, Query, Call, SecretString
|
|
9
|
+
from dex_connectors.sdk.projectconfig import ConnectionKey, LoadedProject
|
|
10
|
+
|
|
11
|
+
from ..client import Client, Config, Credentials
|
|
12
|
+
from .definitions import CONNECTOR_ID, ADD_COMMENT_DEFINITION, CREATE_TASK_DEFINITION, FIND_MEMBER_BY_EMAIL_DEFINITION, GET_TASK_DEFINITION, SEARCH_TASKS_DEFINITION, UPDATE_TASK_DEFINITION, UPDATE_TASK_TAGS_DEFINITION
|
|
13
|
+
from ..models import AddCommentInput, AddCommentOutput, CreateTaskInput, CreateTaskOutput, FindMemberByEmailInput, GetTaskInput, Member, SearchTasksInput, SearchTasksOutput, Task, UpdateTaskInput, UpdateTaskOutput, UpdateTaskTagsInput, UpdateTaskTagsOutput
|
|
14
|
+
|
|
15
|
+
class Connection:
|
|
16
|
+
|
|
17
|
+
__slots__ = ('_search_tasks', '_get_task', '_create_task', '_update_task', '_update_task_tags', '_add_comment', '_find_member_by_email', '_client', 'reference')
|
|
18
|
+
|
|
19
|
+
def __init__(self,
|
|
20
|
+
reference: ConnectionRef,
|
|
21
|
+
search_tasks: Query[SearchTasksInput, SearchTasksOutput],
|
|
22
|
+
get_task: Query[GetTaskInput, Task],
|
|
23
|
+
create_task: Mutation[CreateTaskInput, CreateTaskOutput],
|
|
24
|
+
update_task: Mutation[UpdateTaskInput, UpdateTaskOutput],
|
|
25
|
+
update_task_tags: Mutation[UpdateTaskTagsInput, UpdateTaskTagsOutput],
|
|
26
|
+
add_comment: Mutation[AddCommentInput, AddCommentOutput],
|
|
27
|
+
find_member_by_email: Query[FindMemberByEmailInput, Member],
|
|
28
|
+
client: Client | None = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
reference.validate()
|
|
31
|
+
if reference.provider != CONNECTOR_ID or search_tasks is None or get_task is None or create_task is None or update_task is None or update_task_tags is None or add_comment is None or find_member_by_email is None:
|
|
32
|
+
raise ValueError("ClickUp connection provider and operations are required")
|
|
33
|
+
if search_tasks.definition().operation != SEARCH_TASKS_DEFINITION.operation:
|
|
34
|
+
raise ValueError("ClickUp connection operation identity differs")
|
|
35
|
+
if get_task.definition().operation != GET_TASK_DEFINITION.operation:
|
|
36
|
+
raise ValueError("ClickUp connection operation identity differs")
|
|
37
|
+
if create_task.definition().operation != CREATE_TASK_DEFINITION.operation:
|
|
38
|
+
raise ValueError("ClickUp connection operation identity differs")
|
|
39
|
+
if update_task.definition().operation != UPDATE_TASK_DEFINITION.operation:
|
|
40
|
+
raise ValueError("ClickUp connection operation identity differs")
|
|
41
|
+
if update_task_tags.definition().operation != UPDATE_TASK_TAGS_DEFINITION.operation:
|
|
42
|
+
raise ValueError("ClickUp connection operation identity differs")
|
|
43
|
+
if add_comment.definition().operation != ADD_COMMENT_DEFINITION.operation:
|
|
44
|
+
raise ValueError("ClickUp connection operation identity differs")
|
|
45
|
+
if find_member_by_email.definition().operation != FIND_MEMBER_BY_EMAIL_DEFINITION.operation:
|
|
46
|
+
raise ValueError("ClickUp connection operation identity differs")
|
|
47
|
+
self.reference = reference
|
|
48
|
+
self._search_tasks = search_tasks
|
|
49
|
+
self._get_task = get_task
|
|
50
|
+
self._create_task = create_task
|
|
51
|
+
self._update_task = update_task
|
|
52
|
+
self._update_task_tags = update_task_tags
|
|
53
|
+
self._add_comment = add_comment
|
|
54
|
+
self._find_member_by_email = find_member_by_email
|
|
55
|
+
self._client = client
|
|
56
|
+
|
|
57
|
+
def search_tasks(self) -> Query[SearchTasksInput, SearchTasksOutput]:
|
|
58
|
+
return self._search_tasks
|
|
59
|
+
|
|
60
|
+
def get_task(self) -> Query[GetTaskInput, Task]:
|
|
61
|
+
return self._get_task
|
|
62
|
+
|
|
63
|
+
def create_task(self) -> Mutation[CreateTaskInput, CreateTaskOutput]:
|
|
64
|
+
return self._create_task
|
|
65
|
+
|
|
66
|
+
def update_task(self) -> Mutation[UpdateTaskInput, UpdateTaskOutput]:
|
|
67
|
+
return self._update_task
|
|
68
|
+
|
|
69
|
+
def update_task_tags(self) -> Mutation[UpdateTaskTagsInput, UpdateTaskTagsOutput]:
|
|
70
|
+
return self._update_task_tags
|
|
71
|
+
|
|
72
|
+
def add_comment(self) -> Mutation[AddCommentInput, AddCommentOutput]:
|
|
73
|
+
return self._add_comment
|
|
74
|
+
|
|
75
|
+
def find_member_by_email(self) -> Query[FindMemberByEmailInput, Member]:
|
|
76
|
+
return self._find_member_by_email
|
|
77
|
+
|
|
78
|
+
async def aclose(self) -> None:
|
|
79
|
+
if self._client is not None:
|
|
80
|
+
await self._client.aclose()
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
return "clickup.Connection([REDACTED])"
|
|
84
|
+
|
|
85
|
+
def __reduce__(self) -> tuple[object, ...]:
|
|
86
|
+
raise TypeError("ClickUp connections cannot be serialized")
|
|
87
|
+
|
|
88
|
+
setattr(Connection, '__module__', 'dex_connectors.clickup.connection')
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def new_connection(client: Client, reference: ConnectionRef) -> Connection:
|
|
92
|
+
if client is None:
|
|
93
|
+
raise ValueError("ClickUp client is required")
|
|
94
|
+
return Connection(reference, client.search_tasks(), client.get_task(), client.create_task(), client.update_task(), client.update_task_tags(), client.add_comment(), client.find_member_by_email(), client)
|
|
95
|
+
|
|
96
|
+
setattr(new_connection, '__module__', 'dex_connectors.clickup.connection')
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def new_connection_with_operations(
|
|
100
|
+
search_tasks: Query[SearchTasksInput, SearchTasksOutput],
|
|
101
|
+
get_task: Query[GetTaskInput, Task],
|
|
102
|
+
create_task: Mutation[CreateTaskInput, CreateTaskOutput],
|
|
103
|
+
update_task: Mutation[UpdateTaskInput, UpdateTaskOutput],
|
|
104
|
+
update_task_tags: Mutation[UpdateTaskTagsInput, UpdateTaskTagsOutput],
|
|
105
|
+
add_comment: Mutation[AddCommentInput, AddCommentOutput],
|
|
106
|
+
find_member_by_email: Query[FindMemberByEmailInput, Member],
|
|
107
|
+
reference: ConnectionRef,
|
|
108
|
+
) -> Connection:
|
|
109
|
+
return Connection(reference, search_tasks, get_task, create_task, update_task, update_task_tags, add_comment, find_member_by_email)
|
|
110
|
+
|
|
111
|
+
setattr(new_connection_with_operations, '__module__', 'dex_connectors.clickup.connection')
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class _ProjectCredentialSource:
|
|
115
|
+
|
|
116
|
+
def __init__(self, project: LoadedProject, key: ConnectionKey) -> None:
|
|
117
|
+
self.project = project
|
|
118
|
+
self.key = key
|
|
119
|
+
|
|
120
|
+
async def resolve(self, call: Call) -> Credentials:
|
|
121
|
+
material = await self.project.resolve_credential(self.key, str(call.id))
|
|
122
|
+
if not isinstance(material, dict):
|
|
123
|
+
raise ValueError("ClickUp stored credential does not match the connector credential fields")
|
|
124
|
+
if set(material) - set(('api_token', 'webhook_secret')):
|
|
125
|
+
raise ValueError("ClickUp stored credential does not match the connector credential fields")
|
|
126
|
+
api_token = material.get("api_token")
|
|
127
|
+
if api_token is not None and not (isinstance(api_token, str)):
|
|
128
|
+
raise ValueError("ClickUp credential api_token has an invalid type")
|
|
129
|
+
webhook_secret = material.get("webhook_secret")
|
|
130
|
+
if webhook_secret is not None and not (isinstance(webhook_secret, str)):
|
|
131
|
+
raise ValueError("ClickUp credential webhook_secret has an invalid type")
|
|
132
|
+
if not api_token:
|
|
133
|
+
raise ValueError("ClickUp credential api_token is required")
|
|
134
|
+
return Credentials(SecretString(api_token), SecretString(webhook_secret or ''))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def new_project_connection(
|
|
138
|
+
project: LoadedProject, connection_name: str, http_client: httpx.AsyncClient | None = None
|
|
139
|
+
) -> Connection:
|
|
140
|
+
if project is None:
|
|
141
|
+
raise ValueError("ClickUp connection requires loaded project configuration")
|
|
142
|
+
reference = ConnectionRef(CONNECTOR_ID, connection_name)
|
|
143
|
+
reference.validate()
|
|
144
|
+
key = ConnectionKey(CONNECTOR_ID, connection_name)
|
|
145
|
+
matching = [
|
|
146
|
+
entry
|
|
147
|
+
for entry in project.configuration.connections
|
|
148
|
+
if entry.connector_id == CONNECTOR_ID and entry.connection_name == connection_name
|
|
149
|
+
]
|
|
150
|
+
if len(matching) != 1:
|
|
151
|
+
raise ValueError("ClickUp project connection is missing or duplicated")
|
|
152
|
+
if matching[0].provider != CONNECTOR_ID:
|
|
153
|
+
raise ValueError("ClickUp project connection provider differs")
|
|
154
|
+
config = _decode_config(matching[0].configuration)
|
|
155
|
+
client = Client(config, _ProjectCredentialSource(project, key), http_client)
|
|
156
|
+
return new_connection(client, reference)
|
|
157
|
+
|
|
158
|
+
setattr(new_project_connection, '__module__', 'dex_connectors.clickup.connection')
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _decode_config(value: dict[str, Any]) -> Config:
|
|
162
|
+
if set(value) - {"maxResponseBytes", "webhookMaxBodyBytes"}:
|
|
163
|
+
raise ValueError("ClickUp configuration has unknown fields")
|
|
164
|
+
config_arguments: dict[str, Any] = {}
|
|
165
|
+
if "maxResponseBytes" in value and value["maxResponseBytes"] != '':
|
|
166
|
+
max_response_bytes = value["maxResponseBytes"]
|
|
167
|
+
if not (type(max_response_bytes) is int):
|
|
168
|
+
raise ValueError("ClickUp configuration maxResponseBytes has an invalid type")
|
|
169
|
+
config_arguments['max_response_bytes'] = max_response_bytes
|
|
170
|
+
if "webhookMaxBodyBytes" in value and value["webhookMaxBodyBytes"] != '':
|
|
171
|
+
webhook_max_body_bytes = value["webhookMaxBodyBytes"]
|
|
172
|
+
if not (type(webhook_max_body_bytes) is int):
|
|
173
|
+
raise ValueError("ClickUp configuration webhookMaxBodyBytes has an invalid type")
|
|
174
|
+
config_arguments['webhook_max_body_bytes'] = webhook_max_body_bytes
|
|
175
|
+
return Config(**config_arguments)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
# ruff: noqa
|
|
4
|
+
|
|
5
|
+
from datetime import timedelta
|
|
6
|
+
|
|
7
|
+
from dex_connectors.sdk import (
|
|
8
|
+
BranchDefinition,
|
|
9
|
+
MutationDefinition,
|
|
10
|
+
OperationRef,
|
|
11
|
+
QueryDefinition,
|
|
12
|
+
RetryPolicy,
|
|
13
|
+
StepDefaults,
|
|
14
|
+
default_connector_step_execute_retry_policy,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
CONNECTOR_ID = "clickup"
|
|
18
|
+
PROVIDER = "clickup"
|
|
19
|
+
SEARCH_TASKS_OPERATION_ID = "searchTasks"
|
|
20
|
+
GET_TASK_OPERATION_ID = "getTask"
|
|
21
|
+
CREATE_TASK_OPERATION_ID = "createTask"
|
|
22
|
+
UPDATE_TASK_OPERATION_ID = "updateTask"
|
|
23
|
+
UPDATE_TASK_TAGS_OPERATION_ID = "updateTaskTags"
|
|
24
|
+
ADD_COMMENT_OPERATION_ID = "addComment"
|
|
25
|
+
FIND_MEMBER_BY_EMAIL_OPERATION_ID = "findMemberByEmail"
|
|
26
|
+
ADDED = "added"
|
|
27
|
+
CREATED = "created"
|
|
28
|
+
DEFECT = "defect"
|
|
29
|
+
FOUND = "found"
|
|
30
|
+
INVALID_RESPONSE = "invalidResponse"
|
|
31
|
+
NOT_FOUND = "notFound"
|
|
32
|
+
PROVIDER_REJECTED = "providerRejected"
|
|
33
|
+
SEARCHED = "searched"
|
|
34
|
+
UNCERTAIN = "uncertain"
|
|
35
|
+
UPDATED = "updated"
|
|
36
|
+
|
|
37
|
+
SEARCH_TASKS_DEFINITION = QueryDefinition(
|
|
38
|
+
OperationRef(CONNECTOR_ID, SEARCH_TASKS_OPERATION_ID),
|
|
39
|
+
(
|
|
40
|
+
BranchDefinition(SEARCHED, "One page of matching tasks was read; it can be empty.", False),
|
|
41
|
+
BranchDefinition(NOT_FOUND, "The Workspace does not exist or the token's user is not a member of it.", True),
|
|
42
|
+
BranchDefinition(PROVIDER_REJECTED, "ClickUp conclusively rejected the search, such as an invalid filter or an invalid token.", True),
|
|
43
|
+
BranchDefinition(INVALID_RESPONSE, "ClickUp returned an invalid or oversized page of tasks.", True),
|
|
44
|
+
BranchDefinition(DEFECT, "Local input, connection configuration, or connector definition is invalid.", True),
|
|
45
|
+
),
|
|
46
|
+
StepDefaults(
|
|
47
|
+
timedelta(seconds=30),
|
|
48
|
+
2,
|
|
49
|
+
execute_retry=default_connector_step_execute_retry_policy(),
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
GET_TASK_DEFINITION = QueryDefinition(
|
|
54
|
+
OperationRef(CONNECTOR_ID, GET_TASK_OPERATION_ID),
|
|
55
|
+
(
|
|
56
|
+
BranchDefinition(FOUND, "The task was read.", False),
|
|
57
|
+
BranchDefinition(NOT_FOUND, "The task does not exist or the connection cannot see it.", True),
|
|
58
|
+
BranchDefinition(PROVIDER_REJECTED, "ClickUp conclusively rejected the task read.", True),
|
|
59
|
+
BranchDefinition(INVALID_RESPONSE, "ClickUp returned an invalid or oversized task.", True),
|
|
60
|
+
BranchDefinition(DEFECT, "Local input, connection configuration, or connector definition is invalid.", True),
|
|
61
|
+
),
|
|
62
|
+
StepDefaults(
|
|
63
|
+
timedelta(seconds=30),
|
|
64
|
+
2,
|
|
65
|
+
execute_retry=default_connector_step_execute_retry_policy(),
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
CREATE_TASK_DEFINITION = MutationDefinition(
|
|
70
|
+
OperationRef(CONNECTOR_ID, CREATE_TASK_OPERATION_ID),
|
|
71
|
+
(
|
|
72
|
+
BranchDefinition(CREATED, "ClickUp created the task; a retried attempt that found its own earlier task in the List reports it instead of creating another.", False),
|
|
73
|
+
BranchDefinition(NOT_FOUND, "The List does not exist or the connection cannot see it; nothing was created.", True),
|
|
74
|
+
BranchDefinition(PROVIDER_REJECTED, "ClickUp conclusively rejected the task, such as an unknown status, assignee, or custom field value, or missing permission; nothing was created.", True),
|
|
75
|
+
BranchDefinition(UNCERTAIN, "An earlier attempt sent the create but its outcome cannot be confirmed and the List shows no single matching task, so the task may exist; the connector never sends it again.", True),
|
|
76
|
+
BranchDefinition(DEFECT, "Local input, connection configuration, or connector definition is invalid.", True),
|
|
77
|
+
),
|
|
78
|
+
StepDefaults(
|
|
79
|
+
timedelta(seconds=30),
|
|
80
|
+
1,
|
|
81
|
+
execute_retry=RetryPolicy(timedelta(seconds=1), 2, timedelta(seconds=60), 5, timedelta(seconds=300)),
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
UPDATE_TASK_DEFINITION = MutationDefinition(
|
|
86
|
+
OperationRef(CONNECTOR_ID, UPDATE_TASK_OPERATION_ID),
|
|
87
|
+
(
|
|
88
|
+
BranchDefinition(UPDATED, "ClickUp applied every requested change, including when the task already had those values, and the Value holds the task it returned.", False),
|
|
89
|
+
BranchDefinition(NOT_FOUND, "The task does not exist or the connection cannot see it.", True),
|
|
90
|
+
BranchDefinition(PROVIDER_REJECTED, "ClickUp conclusively rejected the change, such as a status that is not in the task's List or missing permission.", True),
|
|
91
|
+
BranchDefinition(INVALID_RESPONSE, "ClickUp returned an invalid or oversized task after the update, so the change may have been applied.", True),
|
|
92
|
+
BranchDefinition(DEFECT, "Local input, connection configuration, or connector definition is invalid.", True),
|
|
93
|
+
),
|
|
94
|
+
StepDefaults(
|
|
95
|
+
timedelta(seconds=30),
|
|
96
|
+
2,
|
|
97
|
+
execute_retry=default_connector_step_execute_retry_policy(),
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
UPDATE_TASK_TAGS_DEFINITION = MutationDefinition(
|
|
102
|
+
OperationRef(CONNECTOR_ID, UPDATE_TASK_TAGS_OPERATION_ID),
|
|
103
|
+
(
|
|
104
|
+
BranchDefinition(UPDATED, "ClickUp applied every tag change, and the Value holds the task's tags read back.", False),
|
|
105
|
+
BranchDefinition(NOT_FOUND, "The task does not exist or the connection cannot see it.", True),
|
|
106
|
+
BranchDefinition(PROVIDER_REJECTED, "ClickUp conclusively rejected a tag change, such as missing permission; earlier changes in the same Step may have been applied.", True),
|
|
107
|
+
BranchDefinition(INVALID_RESPONSE, "ClickUp returned an invalid or oversized task when its tags were read back; the tag changes were applied.", True),
|
|
108
|
+
BranchDefinition(DEFECT, "Local input, connection configuration, or connector definition is invalid.", True),
|
|
109
|
+
),
|
|
110
|
+
StepDefaults(
|
|
111
|
+
timedelta(seconds=30),
|
|
112
|
+
2,
|
|
113
|
+
execute_retry=default_connector_step_execute_retry_policy(),
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
ADD_COMMENT_DEFINITION = MutationDefinition(
|
|
118
|
+
OperationRef(CONNECTOR_ID, ADD_COMMENT_OPERATION_ID),
|
|
119
|
+
(
|
|
120
|
+
BranchDefinition(ADDED, "The comment is on the task; a retried attempt that found its own earlier comment reports it instead of adding another.", False),
|
|
121
|
+
BranchDefinition(NOT_FOUND, "The task does not exist or the connection cannot see it; nothing was added.", True),
|
|
122
|
+
BranchDefinition(PROVIDER_REJECTED, "ClickUp conclusively rejected the comment, such as missing permission; nothing was added.", True),
|
|
123
|
+
BranchDefinition(UNCERTAIN, "An earlier attempt sent the comment but its outcome cannot be confirmed and the task shows no matching comment, so the comment may exist; the connector never sends it again.", True),
|
|
124
|
+
BranchDefinition(DEFECT, "Local input, connection configuration, or connector definition is invalid.", True),
|
|
125
|
+
),
|
|
126
|
+
StepDefaults(
|
|
127
|
+
timedelta(seconds=30),
|
|
128
|
+
1,
|
|
129
|
+
execute_retry=RetryPolicy(timedelta(seconds=1), 2, timedelta(seconds=60), 5, timedelta(seconds=300)),
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
FIND_MEMBER_BY_EMAIL_DEFINITION = QueryDefinition(
|
|
134
|
+
OperationRef(CONNECTOR_ID, FIND_MEMBER_BY_EMAIL_OPERATION_ID),
|
|
135
|
+
(
|
|
136
|
+
BranchDefinition(FOUND, "A member of the Workspace has the email address.", False),
|
|
137
|
+
BranchDefinition(NOT_FOUND, "No member of the Workspace has the email address, or the token's user is not a member of the Workspace.", True),
|
|
138
|
+
BranchDefinition(PROVIDER_REJECTED, "ClickUp conclusively rejected the Workspace read.", True),
|
|
139
|
+
BranchDefinition(INVALID_RESPONSE, "ClickUp returned an invalid or oversized list of Workspaces.", True),
|
|
140
|
+
BranchDefinition(DEFECT, "Local input, connection configuration, or connector definition is invalid.", True),
|
|
141
|
+
),
|
|
142
|
+
StepDefaults(
|
|
143
|
+
timedelta(seconds=30),
|
|
144
|
+
2,
|
|
145
|
+
execute_retry=default_connector_step_execute_retry_policy(),
|
|
146
|
+
),
|
|
147
|
+
)
|