dex-connector-docusign 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.
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.5
2
+ Name: dex-connector-docusign
3
+ Version: 0.1.0
4
+ Summary: DocuSign eSignature connector for Dex Flows
5
+ Project-URL: Source, https://github.com/superdurable/dex-connectors-library/tree/main/connectors/docusign/python
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: dex-connectors-sdk==0.7.0
10
+ Requires-Dist: dex-python-sdk>=1.28.0
11
+ Requires-Dist: httpx>=0.27
12
+ Description-Content-Type: text/markdown
13
+
14
+ # DocuSign eSignature Python connector
15
+
16
+ This package implements `createEnvelopeFromTemplate`, `getEnvelope`,
17
+ `listEnvelopeRecipients`, `voidEnvelope`, and `downloadCombinedDocument` for
18
+ Dex Python Flows. The `envelopeEventReceived` Connect Trigger remains Go-only.
19
+ The manifest owns operation identities, retry policies, branches, configuration,
20
+ and OAuth methods. The Python package version is `v0.1.0` and requires
21
+ `dex-connectors-sdk==0.7.0`.
22
+
23
+ `new_project_connection` loads the static connection name from Dex project
24
+ configuration. It uses the generated OAuth credential decoder and refresh
25
+ driver for production and developer accounts. Each provider call resolves the
26
+ current access token, reads `/oauth/userinfo` to choose the account and regional
27
+ API host, and rejects a host outside the selected environment. The PDF operation
28
+ returns a digest, byte count, and optional store location; PDF bytes never enter
29
+ a Dex Result. `CombinedDocumentStore` accepts bounded PDF bytes outside Dex and
30
+ must be idempotent and atomic.
31
+
32
+ ## Runnable example
33
+
34
+ [`examples/envelope_status/flow.py`](examples/envelope_status/flow.py) reads
35
+ an envelope with the static `docusign-esignature` connection. It persists only
36
+ the request and safe envelope summary. Its optional `notFound`,
37
+ `providerRejected`, `invalidResponse`, and `defect` branches are intentionally
38
+ unwired, so Dex fails the Flow if one is selected.
39
+
40
+ From `connectors/docusign/python`, install dependencies and produce a strict
41
+ FDG 2.0 file for Dex Web:
42
+
43
+ ```sh
44
+ uv sync --locked
45
+ dexcli visualize examples/envelope_status/flow.py --language python \
46
+ --schema-version 2.0 --lint app --json --out build/envelope-status \
47
+ --python .venv/bin/python
48
+ ```
49
+
50
+ Start `dexcli dev` with that rendering directory. In Dex Web **Connections**,
51
+ create `docusign / docusign-esignature`, choose Production account or Developer
52
+ account, and follow the authorization guide in the connector manifest. Register
53
+ Dex Web's exact redirect URI in the DocuSign app. The OAuth scopes are
54
+ `signature` and `extended`; Dex handles access and refresh tokens. The account
55
+ ID may stay blank to use the authorizing user's default account. A Connect HMAC
56
+ key is optional for this read-only example. Use the owner-only
57
+ `connections.json` exported by Dex Web as `DEX_CONNECTOR_CONFIG_FILE`.
58
+
59
+ Run the Worker and start a Flow with the included sample input:
60
+
61
+ ```sh
62
+ DEX_FLOW_SERVICE_ADDRESS=127.0.0.1:40901 \
63
+ DEX_CONNECTOR_CONFIG_FILE=/path/to/connections.json \
64
+ uv run python -m examples.envelope_status.main
65
+
66
+ DEX_FLOW_SERVICE_ADDRESS=127.0.0.1:40901 \
67
+ DEX_CONNECTOR_CONFIG_FILE=/path/to/connections.json \
68
+ uv run python -m examples.envelope_status.start \
69
+ docusign-envelope-001 examples/envelope_status/sample-input.json
70
+ ```
71
+
72
+ The Worker binding in [`main.py`](examples/envelope_status/main.py) is the
73
+ application entrypoint:
74
+
75
+ ```python
76
+ project = await load_from_environment()
77
+ connection = new_project_connection(project, CONNECTION_NAME)
78
+ try:
79
+ await run_worker(EnvelopeStatusFlow(connection))
80
+ finally:
81
+ await connection.aclose()
82
+ await project.close()
83
+ ```
84
+
85
+ Run the real Dex integration test against a local `dexcli dev` server:
86
+
87
+ ```sh
88
+ DEX_FLOW_SERVICE_ADDRESS=127.0.0.1:40901 \
89
+ uv run pytest examples/envelope_status/integration_test.py
90
+ ```
91
+
92
+ The test binds its HTTP provider stand-in to an OS-assigned free port. It checks
93
+ retry after a 429 and failure on an unwired `notFound` branch through a real Dex
94
+ Worker and Client. The 52 Go-recorded conformance scenarios exercise every
95
+ operation's provider requests and exact SDK-encoded outputs. OAuth refresh
96
+ agreement has its own generated Go and Python suites.
97
+
98
+ Live DocuSign authorization, account selection, regional host resolution,
99
+ provider error codes, envelope read-back timing, PDF downloads, and Connect
100
+ configuration have not been verified with a real DocuSign account.
@@ -0,0 +1,22 @@
1
+ dex_connectors/docusign/__init__.py,sha256=Pm8B9FeNJeacB29fOUNyLrvA2m2ZdxmxNgWW51TfNRw,510
2
+ dex_connectors/docusign/client.py,sha256=S3NT6ARw79F6KUhx6AOUvKYd5uCpcPK4DqUalvn1zvo,23421
3
+ dex_connectors/docusign/connection.py,sha256=WtA9CQHZ8eHXN8YN2c5Cf_IjaMS9COaa2XRIofdo1Nc,386
4
+ dex_connectors/docusign/dex-connector.json,sha256=nNpiFnVxeYeZvo_eumEUz35R-11g6B-FbNidwCVk2x0,4848
5
+ dex_connectors/docusign/models.py,sha256=S1kENoabj3T4wd8SyCHLZYL8hjHj-SwuDvbirPemtoM,291
6
+ dex_connectors/docusign/operations.py,sha256=otjmkaoD6WsfJ6WnbQ-IzD7PKUBpu1zKjgjh-jiOrE0,41749
7
+ dex_connectors/docusign/py.typed,sha256=ShfMsAGzSt4xuhCaf20ibgtfXl4pXomZb00ovUw6CeI,66
8
+ dex_connectors/docusign/steps.py,sha256=GE9hwfyspRv7Y1CDqlX8aGmLzETjTB1uFhXZdOGXphA,189
9
+ dex_connectors/docusign/_generated/__init__.py,sha256=ShfMsAGzSt4xuhCaf20ibgtfXl4pXomZb00ovUw6CeI,66
10
+ dex_connectors/docusign/_generated/config.py,sha256=3I2gAtwjuUTUxBgfma-eqKNR9mbiBYCqn6wB_fq4SkE,828
11
+ dex_connectors/docusign/_generated/connection.py,sha256=liRG98Xr0wW5CulpM2aLM0bfv8bax7QKsz7r3UprLME,16241
12
+ dex_connectors/docusign/_generated/definitions.py,sha256=ehuGbI0wRejSJx0Ilcl8p1Xni5O2HiqDeklJ3SRfTpw,6289
13
+ dex_connectors/docusign/_generated/mock.py,sha256=iSYM3nBzb-jNtS8bWQznpBYtSra68Tk1hVIMOJ_W3pY,19618
14
+ dex_connectors/docusign/_generated/models.py,sha256=g4klgaeVWHth_jpZLI0KTas2yRoFLx5lluqsoS4b6lM,18983
15
+ dex_connectors/docusign/_generated/refresh.py,sha256=FN-xVR5_jsgrBqpJtT74GClodQw8YU5lJT86L01Myto,2562
16
+ dex_connectors/docusign/_generated/steps.py,sha256=Cs0asCwd7SKJsYbZ9ap_Yi44WqnYMpjFz8RUyzismaY,14269
17
+ dex_connectors/docusign/_generated/timestamps.py,sha256=Z8VwrE60w-33InIMgEMLiMPTFyRjj6oBq8tUro8Ssi8,3164
18
+ dex_connectors/docusign/docusignmock/__init__.py,sha256=sEODfqQ6BJ0_cxRmH7FUHlzuhGC26jntpGD0cmUtm_U,167
19
+ dex_connector_docusign-0.1.0.dist-info/METADATA,sha256=dxx2RmgU0_I2yPnR1ACfIr_pUOdIpZVMw7KN2YDvErc,4298
20
+ dex_connector_docusign-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
21
+ dex_connector_docusign-0.1.0.dist-info/licenses/LICENSE,sha256=WNj87Zd5micrAShl1LujOPM6PDzDqjlNLrryDZ4hE0M,1070
22
+ dex_connector_docusign-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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,17 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ """Dex-native DocuSign eSignature operations."""
4
+
5
+ from .client import Client, CombinedDocumentStore
6
+ from .connection import Connection, new_connection, new_connection_with_operations, new_project_connection
7
+ from .models import * # noqa: F403
8
+ from .steps import * # noqa: F403
9
+
10
+ __all__ = [
11
+ "Client",
12
+ "CombinedDocumentStore",
13
+ "Connection",
14
+ "new_connection",
15
+ "new_connection_with_operations",
16
+ "new_project_connection",
17
+ ]
@@ -0,0 +1,2 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
@@ -0,0 +1,31 @@
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
+ account_id: str = ''
14
+ max_response_bytes: int = 1048576
15
+ max_document_bytes: int = 67108864
16
+ connect_max_body_bytes: int = 2097152
17
+
18
+ setattr(Config, '__module__', 'dex_connectors.docusign.client')
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class Credentials:
23
+
24
+ auth_method_id: str = "docusign-oauth"
25
+ oauth_client_id: str | None = None
26
+ oauth_client_secret: SecretString | None = None
27
+ access_token: SecretString | None = None
28
+ refresh_token: SecretString | None = None
29
+ connect_hmac_key: SecretString = SecretString('')
30
+
31
+ setattr(Credentials, '__module__', 'dex_connectors.docusign.client')
@@ -0,0 +1,274 @@
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, CredentialRefreshDriver
9
+ from dex_connectors.sdk.projectconfig import ConnectionKey, LoadedProject
10
+ from dex_connectors.sdk.projectconfig.provider import RefreshingCredentialProvider
11
+ from dex_connectors.sdk.oauthtoken import ManifestCredentialRefreshDriver
12
+
13
+ from ..client import Client, Config, Credentials
14
+ from .definitions import CONNECTOR_ID, CREATE_ENVELOPE_FROM_TEMPLATE_DEFINITION, DOWNLOAD_COMBINED_DOCUMENT_DEFINITION, GET_ENVELOPE_DEFINITION, LIST_ENVELOPE_RECIPIENTS_DEFINITION, VOID_ENVELOPE_DEFINITION
15
+ from .refresh import AUTHORIZATION, CREDENTIAL_FIELDS
16
+ from ..models import CombinedDocument, CreateEnvelopeFromTemplateInput, CreatedEnvelope, DownloadCombinedDocumentInput, Envelope, EnvelopeRecipients, GetEnvelopeInput, ListEnvelopeRecipientsInput, VoidEnvelopeInput, VoidedEnvelope
17
+
18
+ class Connection:
19
+
20
+ __slots__ = ('_create_envelope_from_template', '_get_envelope', '_list_envelope_recipients', '_void_envelope', '_download_combined_document', '_client', 'reference')
21
+
22
+ def __init__(self,
23
+ reference: ConnectionRef,
24
+ create_envelope_from_template: Mutation[CreateEnvelopeFromTemplateInput, CreatedEnvelope],
25
+ get_envelope: Query[GetEnvelopeInput, Envelope],
26
+ list_envelope_recipients: Query[ListEnvelopeRecipientsInput, EnvelopeRecipients],
27
+ void_envelope: Mutation[VoidEnvelopeInput, VoidedEnvelope],
28
+ download_combined_document: Query[DownloadCombinedDocumentInput, CombinedDocument],
29
+ client: Client | None = None,
30
+ ) -> None:
31
+ reference.validate()
32
+ if reference.provider != CONNECTOR_ID or create_envelope_from_template is None or get_envelope is None or list_envelope_recipients is None or void_envelope is None or download_combined_document is None:
33
+ raise ValueError("DocuSign eSignature connection provider and operations are required")
34
+ if create_envelope_from_template.definition().operation != CREATE_ENVELOPE_FROM_TEMPLATE_DEFINITION.operation:
35
+ raise ValueError("DocuSign eSignature connection operation identity differs")
36
+ if get_envelope.definition().operation != GET_ENVELOPE_DEFINITION.operation:
37
+ raise ValueError("DocuSign eSignature connection operation identity differs")
38
+ if list_envelope_recipients.definition().operation != LIST_ENVELOPE_RECIPIENTS_DEFINITION.operation:
39
+ raise ValueError("DocuSign eSignature connection operation identity differs")
40
+ if void_envelope.definition().operation != VOID_ENVELOPE_DEFINITION.operation:
41
+ raise ValueError("DocuSign eSignature connection operation identity differs")
42
+ if download_combined_document.definition().operation != DOWNLOAD_COMBINED_DOCUMENT_DEFINITION.operation:
43
+ raise ValueError("DocuSign eSignature connection operation identity differs")
44
+ self.reference = reference
45
+ self._create_envelope_from_template = create_envelope_from_template
46
+ self._get_envelope = get_envelope
47
+ self._list_envelope_recipients = list_envelope_recipients
48
+ self._void_envelope = void_envelope
49
+ self._download_combined_document = download_combined_document
50
+ self._client = client
51
+
52
+ def create_envelope_from_template(self) -> Mutation[CreateEnvelopeFromTemplateInput, CreatedEnvelope]:
53
+ return self._create_envelope_from_template
54
+
55
+ def get_envelope(self) -> Query[GetEnvelopeInput, Envelope]:
56
+ return self._get_envelope
57
+
58
+ def list_envelope_recipients(self) -> Query[ListEnvelopeRecipientsInput, EnvelopeRecipients]:
59
+ return self._list_envelope_recipients
60
+
61
+ def void_envelope(self) -> Mutation[VoidEnvelopeInput, VoidedEnvelope]:
62
+ return self._void_envelope
63
+
64
+ def download_combined_document(self) -> Query[DownloadCombinedDocumentInput, CombinedDocument]:
65
+ return self._download_combined_document
66
+
67
+ async def aclose(self) -> None:
68
+ if self._client is not None:
69
+ await self._client.aclose()
70
+
71
+ def __repr__(self) -> str:
72
+ return "docusign.Connection([REDACTED])"
73
+
74
+ def __reduce__(self) -> tuple[object, ...]:
75
+ raise TypeError("DocuSign eSignature connections cannot be serialized")
76
+
77
+ setattr(Connection, '__module__', 'dex_connectors.docusign.connection')
78
+
79
+
80
+ def new_connection(client: Client, reference: ConnectionRef) -> Connection:
81
+ if client is None:
82
+ raise ValueError("DocuSign eSignature client is required")
83
+ return Connection(reference, client.create_envelope_from_template(), client.get_envelope(), client.list_envelope_recipients(), client.void_envelope(), client.download_combined_document(), client)
84
+
85
+ setattr(new_connection, '__module__', 'dex_connectors.docusign.connection')
86
+
87
+
88
+ def new_connection_with_operations(
89
+ create_envelope_from_template: Mutation[CreateEnvelopeFromTemplateInput, CreatedEnvelope],
90
+ get_envelope: Query[GetEnvelopeInput, Envelope],
91
+ list_envelope_recipients: Query[ListEnvelopeRecipientsInput, EnvelopeRecipients],
92
+ void_envelope: Mutation[VoidEnvelopeInput, VoidedEnvelope],
93
+ download_combined_document: Query[DownloadCombinedDocumentInput, CombinedDocument],
94
+ reference: ConnectionRef,
95
+ ) -> Connection:
96
+ return Connection(reference, create_envelope_from_template, get_envelope, list_envelope_recipients, void_envelope, download_combined_document)
97
+
98
+ setattr(new_connection_with_operations, '__module__', 'dex_connectors.docusign.connection')
99
+
100
+
101
+ def decode_credentials(material: dict[str, Any], *, shared: bool = False) -> Credentials:
102
+ if not isinstance(material, dict):
103
+ raise ValueError("DocuSign eSignature stored credential does not match the connector credential fields")
104
+ if set(material) - set(('oauth_client_id', 'oauth_client_secret', 'access_token', 'refresh_token', 'connect_hmac_key', 'auth_method')):
105
+ raise ValueError("DocuSign eSignature stored credential does not match the connector credential fields")
106
+ is_shared = shared
107
+ oauth_client_id = material.get("oauth_client_id")
108
+ if oauth_client_id is not None and not (isinstance(oauth_client_id, str)):
109
+ raise ValueError("DocuSign eSignature credential oauth_client_id has an invalid type")
110
+ oauth_client_secret = material.get("oauth_client_secret")
111
+ if oauth_client_secret is not None and not (isinstance(oauth_client_secret, str)):
112
+ raise ValueError("DocuSign eSignature credential oauth_client_secret has an invalid type")
113
+ access_token = material.get("access_token")
114
+ if access_token is not None and not (isinstance(access_token, str)):
115
+ raise ValueError("DocuSign eSignature credential access_token has an invalid type")
116
+ refresh_token = material.get("refresh_token")
117
+ if refresh_token is not None and not (isinstance(refresh_token, str)):
118
+ raise ValueError("DocuSign eSignature credential refresh_token has an invalid type")
119
+ connect_hmac_key = material.get("connect_hmac_key")
120
+ if connect_hmac_key is not None and not (isinstance(connect_hmac_key, str)):
121
+ raise ValueError("DocuSign eSignature credential connect_hmac_key has an invalid type")
122
+ selected_method = material.get('auth_method')
123
+ if selected_method is None:
124
+ selected_method = ''
125
+ if not isinstance(selected_method, str):
126
+ raise ValueError("DocuSign eSignature credential auth_method is invalid")
127
+ method = selected_method or "docusign-oauth"
128
+ values: dict[str, Any] = {'auth_method_id': method}
129
+ if method == "docusign-oauth":
130
+ if not oauth_client_id:
131
+ raise ValueError("DocuSign eSignature credential oauth_client_id is required")
132
+ if oauth_client_id:
133
+ values["oauth_client_id"] = oauth_client_id
134
+ if not oauth_client_secret and not is_shared:
135
+ raise ValueError("DocuSign eSignature credential oauth_client_secret is required")
136
+ if oauth_client_secret:
137
+ values["oauth_client_secret"] = SecretString(oauth_client_secret)
138
+ if not access_token:
139
+ raise ValueError("DocuSign eSignature credential access_token is required")
140
+ if access_token:
141
+ values["access_token"] = SecretString(access_token)
142
+ if not refresh_token and not is_shared:
143
+ raise ValueError("DocuSign eSignature credential refresh_token is required")
144
+ if refresh_token:
145
+ values["refresh_token"] = SecretString(refresh_token)
146
+ values["connect_hmac_key"] = SecretString(connect_hmac_key or '')
147
+ elif method == "docusign-developer-oauth":
148
+ if not oauth_client_id:
149
+ raise ValueError("DocuSign eSignature credential oauth_client_id is required")
150
+ if oauth_client_id:
151
+ values["oauth_client_id"] = oauth_client_id
152
+ if not oauth_client_secret and not is_shared:
153
+ raise ValueError("DocuSign eSignature credential oauth_client_secret is required")
154
+ if oauth_client_secret:
155
+ values["oauth_client_secret"] = SecretString(oauth_client_secret)
156
+ if not access_token:
157
+ raise ValueError("DocuSign eSignature credential access_token is required")
158
+ if access_token:
159
+ values["access_token"] = SecretString(access_token)
160
+ if not refresh_token and not is_shared:
161
+ raise ValueError("DocuSign eSignature credential refresh_token is required")
162
+ if refresh_token:
163
+ values["refresh_token"] = SecretString(refresh_token)
164
+ values["connect_hmac_key"] = SecretString(connect_hmac_key or '')
165
+ else:
166
+ raise ValueError("DocuSign eSignature credential auth_method is invalid")
167
+ return Credentials(**values)
168
+
169
+
170
+ def decode_shared_credentials(material: dict[str, Any]) -> Credentials:
171
+ return decode_credentials(material, shared=True)
172
+
173
+
174
+ def encode_credentials(credentials: Credentials) -> dict[str, Any]:
175
+ material: dict[str, Any] = {}
176
+ method = credentials.auth_method_id or "docusign-oauth"
177
+ material['auth_method'] = method
178
+ if method == "docusign-oauth":
179
+ if credentials.oauth_client_id:
180
+ material["oauth_client_id"] = credentials.oauth_client_id
181
+ if (credentials.oauth_client_secret.reveal() if credentials.oauth_client_secret is not None else ''):
182
+ material["oauth_client_secret"] = (credentials.oauth_client_secret.reveal() if credentials.oauth_client_secret is not None else '')
183
+ if (credentials.access_token.reveal() if credentials.access_token is not None else ''):
184
+ material["access_token"] = (credentials.access_token.reveal() if credentials.access_token is not None else '')
185
+ if (credentials.refresh_token.reveal() if credentials.refresh_token is not None else ''):
186
+ material["refresh_token"] = (credentials.refresh_token.reveal() if credentials.refresh_token is not None else '')
187
+ if (credentials.connect_hmac_key.reveal() if credentials.connect_hmac_key is not None else ''):
188
+ material["connect_hmac_key"] = (credentials.connect_hmac_key.reveal() if credentials.connect_hmac_key is not None else '')
189
+ elif method == "docusign-developer-oauth":
190
+ if credentials.oauth_client_id:
191
+ material["oauth_client_id"] = credentials.oauth_client_id
192
+ if (credentials.oauth_client_secret.reveal() if credentials.oauth_client_secret is not None else ''):
193
+ material["oauth_client_secret"] = (credentials.oauth_client_secret.reveal() if credentials.oauth_client_secret is not None else '')
194
+ if (credentials.access_token.reveal() if credentials.access_token is not None else ''):
195
+ material["access_token"] = (credentials.access_token.reveal() if credentials.access_token is not None else '')
196
+ if (credentials.refresh_token.reveal() if credentials.refresh_token is not None else ''):
197
+ material["refresh_token"] = (credentials.refresh_token.reveal() if credentials.refresh_token is not None else '')
198
+ if (credentials.connect_hmac_key.reveal() if credentials.connect_hmac_key is not None else ''):
199
+ material["connect_hmac_key"] = (credentials.connect_hmac_key.reveal() if credentials.connect_hmac_key is not None else '')
200
+ else:
201
+ raise ValueError("DocuSign eSignature credential auth_method is invalid")
202
+ decode_credentials(material)
203
+ return material
204
+
205
+
206
+ class _ProjectCredentialSource:
207
+ def __init__(self, project: LoadedProject, key: ConnectionKey, http_client: httpx.AsyncClient, settings: dict[str, Any]) -> None:
208
+ self.provider = RefreshingCredentialProvider(
209
+ project.connections, key, decode_credentials, encode_credentials, decode_shared_credentials
210
+ )
211
+ self.driver = ManifestCredentialRefreshDriver[Credentials]("DocuSign eSignature", AUTHORIZATION, CREDENTIAL_FIELDS, http_client, settings=settings)
212
+
213
+ async def resolve(self, call: Call) -> Credentials:
214
+ return await self.provider.resolve_with_refresh(call, self.driver)
215
+
216
+ async def resolve_with_refresh(self, call: Call, driver: CredentialRefreshDriver[Credentials] | None = None) -> Credentials:
217
+ return await self.provider.resolve_with_refresh(call, driver or self.driver)
218
+
219
+ async def resolve_after_rejection(self, call: Call, driver: CredentialRefreshDriver[Credentials] | None = None) -> Credentials:
220
+ return await self.provider.resolve_after_rejection(call, driver or self.driver)
221
+
222
+
223
+ def new_project_connection(
224
+ project: LoadedProject, connection_name: str, http_client: httpx.AsyncClient | None = None
225
+ ) -> Connection:
226
+ if project is None:
227
+ raise ValueError("DocuSign eSignature connection requires loaded project configuration")
228
+ reference = ConnectionRef(CONNECTOR_ID, connection_name)
229
+ reference.validate()
230
+ key = ConnectionKey(CONNECTOR_ID, connection_name)
231
+ matching = [
232
+ entry
233
+ for entry in project.configuration.connections
234
+ if entry.connector_id == CONNECTOR_ID and entry.connection_name == connection_name
235
+ ]
236
+ if len(matching) != 1:
237
+ raise ValueError("DocuSign eSignature project connection is missing or duplicated")
238
+ if matching[0].provider != CONNECTOR_ID:
239
+ raise ValueError("DocuSign eSignature project connection provider differs")
240
+ config = _decode_config(matching[0].configuration)
241
+ http_client = http_client or httpx.AsyncClient()
242
+ client = Client(config, _ProjectCredentialSource(project, key, http_client, matching[0].configuration), http_client)
243
+ return new_connection(client, reference)
244
+
245
+ setattr(new_project_connection, '__module__', 'dex_connectors.docusign.connection')
246
+
247
+
248
+ def _decode_config(value: dict[str, Any]) -> Config:
249
+ if set(value) - {"accountId", "maxResponseBytes", "maxDocumentBytes", "connectMaxBodyBytes"}:
250
+ raise ValueError("DocuSign eSignature configuration has unknown fields")
251
+ config_arguments: dict[str, Any] = {}
252
+ if "accountId" in value:
253
+ account_id = value["accountId"]
254
+ if not (isinstance(account_id, str)):
255
+ raise ValueError("DocuSign eSignature configuration accountId has an invalid type")
256
+ config_arguments['account_id'] = account_id
257
+ else:
258
+ config_arguments['account_id'] = ''
259
+ if "maxResponseBytes" in value:
260
+ max_response_bytes = value["maxResponseBytes"]
261
+ if not (type(max_response_bytes) is int):
262
+ raise ValueError("DocuSign eSignature configuration maxResponseBytes has an invalid type")
263
+ config_arguments['max_response_bytes'] = max_response_bytes
264
+ if "maxDocumentBytes" in value:
265
+ max_document_bytes = value["maxDocumentBytes"]
266
+ if not (type(max_document_bytes) is int):
267
+ raise ValueError("DocuSign eSignature configuration maxDocumentBytes has an invalid type")
268
+ config_arguments['max_document_bytes'] = max_document_bytes
269
+ if "connectMaxBodyBytes" in value:
270
+ connect_max_body_bytes = value["connectMaxBodyBytes"]
271
+ if not (type(connect_max_body_bytes) is int):
272
+ raise ValueError("DocuSign eSignature configuration connectMaxBodyBytes has an invalid type")
273
+ config_arguments['connect_max_body_bytes'] = connect_max_body_bytes
274
+ return Config(**config_arguments)
@@ -0,0 +1,117 @@
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 = "docusign"
18
+ PROVIDER = "docusign"
19
+ CREATE_ENVELOPE_FROM_TEMPLATE_OPERATION_ID = "createEnvelopeFromTemplate"
20
+ GET_ENVELOPE_OPERATION_ID = "getEnvelope"
21
+ LIST_ENVELOPE_RECIPIENTS_OPERATION_ID = "listEnvelopeRecipients"
22
+ VOID_ENVELOPE_OPERATION_ID = "voidEnvelope"
23
+ DOWNLOAD_COMBINED_DOCUMENT_OPERATION_ID = "downloadCombinedDocument"
24
+ CREATED = "created"
25
+ DEFECT = "defect"
26
+ DOWNLOADED = "downloaded"
27
+ FOUND = "found"
28
+ INVALID_RESPONSE = "invalidResponse"
29
+ LISTED = "listed"
30
+ NOT_FOUND = "notFound"
31
+ NOT_VOIDABLE = "notVoidable"
32
+ PROVIDER_REJECTED = "providerRejected"
33
+ TOO_LARGE = "tooLarge"
34
+ UNCERTAIN = "uncertain"
35
+ VOIDED = "voided"
36
+
37
+ CREATE_ENVELOPE_FROM_TEMPLATE_DEFINITION = MutationDefinition(
38
+ OperationRef(CONNECTOR_ID, CREATE_ENVELOPE_FROM_TEMPLATE_OPERATION_ID),
39
+ (
40
+ BranchDefinition(CREATED, "DocuSign holds the envelope this Step execution created; wasRecovered tells whether a later attempt found it by its custom field after an interrupted one.", False),
41
+ BranchDefinition(PROVIDER_REJECTED, "DocuSign conclusively rejected the envelope, such as an unknown template, an unfilled or unknown role, an invalid email address, an exhausted envelope allowance, or a user who may not send; nothing was created.", True),
42
+ BranchDefinition(UNCERTAIN, "The create request was sent but DocuSign's answer was lost, a server error, or unreadable, and no envelope with this Step's custom field could be found, so an envelope may exist that this Result does not name; do not create another automatically.", True),
43
+ BranchDefinition(DEFECT, "Local input, connection configuration, or connector definition is invalid.", True),
44
+ ),
45
+ StepDefaults(
46
+ timedelta(seconds=60),
47
+ 1,
48
+ execute_retry=RetryPolicy(timedelta(seconds=1), 2, timedelta(seconds=60), 6, timedelta(seconds=3900)),
49
+ ),
50
+ )
51
+
52
+ GET_ENVELOPE_DEFINITION = QueryDefinition(
53
+ OperationRef(CONNECTOR_ID, GET_ENVELOPE_OPERATION_ID),
54
+ (
55
+ BranchDefinition(FOUND, "DocuSign returned the envelope.", False),
56
+ BranchDefinition(NOT_FOUND, "The envelope does not exist in the connection's account or the user may not see it.", True),
57
+ BranchDefinition(PROVIDER_REJECTED, "DocuSign conclusively rejected the read, such as a revoked consent or a user without access to the account.", True),
58
+ BranchDefinition(INVALID_RESPONSE, "DocuSign returned a malformed or oversized response.", True),
59
+ BranchDefinition(DEFECT, "Local input, connection configuration, or connector definition is invalid.", True),
60
+ ),
61
+ StepDefaults(
62
+ timedelta(seconds=30),
63
+ 2,
64
+ execute_retry=RetryPolicy(timedelta(seconds=1), 2, timedelta(seconds=60), 0, timedelta(seconds=3900)),
65
+ ),
66
+ )
67
+
68
+ LIST_ENVELOPE_RECIPIENTS_DEFINITION = QueryDefinition(
69
+ OperationRef(CONNECTOR_ID, LIST_ENVELOPE_RECIPIENTS_OPERATION_ID),
70
+ (
71
+ BranchDefinition(LISTED, "DocuSign returned the envelope's recipients.", False),
72
+ BranchDefinition(NOT_FOUND, "The envelope does not exist in the connection's account or the user may not see it.", True),
73
+ BranchDefinition(PROVIDER_REJECTED, "DocuSign conclusively rejected the read, such as a revoked consent or a user without access to the account.", True),
74
+ BranchDefinition(INVALID_RESPONSE, "DocuSign returned a malformed or oversized response.", True),
75
+ BranchDefinition(DEFECT, "Local input, connection configuration, or connector definition is invalid.", True),
76
+ ),
77
+ StepDefaults(
78
+ timedelta(seconds=30),
79
+ 2,
80
+ execute_retry=RetryPolicy(timedelta(seconds=1), 2, timedelta(seconds=60), 0, timedelta(seconds=3900)),
81
+ ),
82
+ )
83
+
84
+ VOID_ENVELOPE_DEFINITION = MutationDefinition(
85
+ OperationRef(CONNECTOR_ID, VOID_ENVELOPE_OPERATION_ID),
86
+ (
87
+ BranchDefinition(VOIDED, "The envelope is voided, by this call or earlier; wasAlreadyVoided tells which.", False),
88
+ BranchDefinition(NOT_VOIDABLE, "The envelope is completed, declined, or still a draft, which DocuSign does not void; status names which.", True),
89
+ BranchDefinition(NOT_FOUND, "The envelope does not exist in the connection's account or the user may not see it.", True),
90
+ BranchDefinition(PROVIDER_REJECTED, "DocuSign conclusively rejected the void, such as a user who is not the sender or lacks permission.", True),
91
+ BranchDefinition(INVALID_RESPONSE, "DocuSign returned a malformed or oversized response.", 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=RetryPolicy(timedelta(seconds=1), 2, timedelta(seconds=60), 0, timedelta(seconds=3900)),
98
+ ),
99
+ )
100
+
101
+ DOWNLOAD_COMBINED_DOCUMENT_DEFINITION = QueryDefinition(
102
+ OperationRef(CONNECTOR_ID, DOWNLOAD_COMBINED_DOCUMENT_OPERATION_ID),
103
+ (
104
+ BranchDefinition(DOWNLOADED, "DocuSign returned the combined PDF and its digest was recorded, and stored when the client has a CombinedDocumentStore.", False),
105
+ BranchDefinition(NOT_FOUND, "The envelope does not exist in the connection's account or the user may not see it.", True),
106
+ BranchDefinition(TOO_LARGE, "The combined PDF exceeds the connection's maxDocumentBytes; nothing was stored.", True),
107
+ BranchDefinition(PROVIDER_REJECTED, "DocuSign conclusively rejected the download, such as a revoked consent or a user without access to the envelope.", True),
108
+ BranchDefinition(INVALID_RESPONSE, "DocuSign returned something other than a complete PDF, or the CombinedDocumentStore rejected it.", True),
109
+ BranchDefinition(DEFECT, "Local input, connection configuration, or connector definition is invalid.", True),
110
+ ),
111
+ StepDefaults(
112
+ timedelta(seconds=120),
113
+ 2,
114
+ heartbeat_timeout=timedelta(seconds=30),
115
+ execute_retry=RetryPolicy(timedelta(seconds=1), 2, timedelta(seconds=60), 0, timedelta(seconds=3900)),
116
+ ),
117
+ )