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.
- dex_connector_docusign-0.1.0.dist-info/METADATA +100 -0
- dex_connector_docusign-0.1.0.dist-info/RECORD +22 -0
- dex_connector_docusign-0.1.0.dist-info/WHEEL +4 -0
- dex_connector_docusign-0.1.0.dist-info/licenses/LICENSE +21 -0
- dex_connectors/docusign/__init__.py +17 -0
- dex_connectors/docusign/_generated/__init__.py +2 -0
- dex_connectors/docusign/_generated/config.py +31 -0
- dex_connectors/docusign/_generated/connection.py +274 -0
- dex_connectors/docusign/_generated/definitions.py +117 -0
- dex_connectors/docusign/_generated/mock.py +326 -0
- dex_connectors/docusign/_generated/models.py +318 -0
- dex_connectors/docusign/_generated/refresh.py +9 -0
- dex_connectors/docusign/_generated/steps.py +299 -0
- dex_connectors/docusign/_generated/timestamps.py +60 -0
- dex_connectors/docusign/client.py +585 -0
- dex_connectors/docusign/connection.py +12 -0
- dex_connectors/docusign/dex-connector.json +206 -0
- dex_connectors/docusign/docusignmock/__init__.py +6 -0
- dex_connectors/docusign/models.py +7 -0
- dex_connectors/docusign/operations.py +1028 -0
- dex_connectors/docusign/py.typed +2 -0
- dex_connectors/docusign/steps.py +5 -0
|
@@ -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,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,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
|
+
)
|