dex-connector-google-docs 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dex_connector_google_docs-0.1.0/.gitignore +16 -0
- dex_connector_google_docs-0.1.0/LICENSE +21 -0
- dex_connector_google_docs-0.1.0/PKG-INFO +25 -0
- dex_connector_google_docs-0.1.0/README.md +12 -0
- dex_connector_google_docs-0.1.0/examples/policy_read/README.md +30 -0
- dex_connector_google_docs-0.1.0/examples/policy_read/__init__.py +3 -0
- dex_connector_google_docs-0.1.0/examples/policy_read/flow.py +68 -0
- dex_connector_google_docs-0.1.0/examples/policy_read/integration_test.py +93 -0
- dex_connector_google_docs-0.1.0/examples/policy_read/start.py +52 -0
- dex_connector_google_docs-0.1.0/examples/policy_read/worker.py +50 -0
- dex_connector_google_docs-0.1.0/pyproject.toml +41 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/__init__.py +50 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/_generated/__init__.py +2 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/_generated/config.py +32 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/_generated/connection.py +254 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/_generated/definitions.py +102 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/_generated/mock.py +323 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/_generated/models.py +211 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/_generated/refresh.py +9 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/_generated/steps.py +255 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/_generated/timestamps.py +60 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/client.py +271 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/connection.py +12 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/create.py +547 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/dex-connector.json +183 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/docsmock/__init__.py +6 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/document.py +222 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/json_values.py +23 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/models.py +31 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/operations.py +630 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/query_values.py +16 -0
- dex_connector_google_docs-0.1.0/src/dex_connectors/google_docs/steps.py +17 -0
- dex_connector_google_docs-0.1.0/tests/refresh_agreement_cases.json +1309 -0
- dex_connector_google_docs-0.1.0/tests/test_conformance.py +47 -0
- dex_connector_google_docs-0.1.0/tests/test_create_timestamp.py +17 -0
- dex_connector_google_docs-0.1.0/tests/test_json_values.py +15 -0
- dex_connector_google_docs-0.1.0/tests/test_query_values.py +36 -0
- dex_connector_google_docs-0.1.0/tests/test_refresh_agreement.py +278 -0
- dex_connector_google_docs-0.1.0/uv.lock +934 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
node_modules/
|
|
5
|
+
dist/
|
|
6
|
+
sdkpython/dist/dex_connectors_sdk-0.1.0-py3-none-any.whl
|
|
7
|
+
/.bin/dexcli
|
|
8
|
+
/.bin/dex-server-zd
|
|
9
|
+
/sdkpython/.dexcli-data/
|
|
10
|
+
site/.catalog.yaml
|
|
11
|
+
coverage/
|
|
12
|
+
*.out
|
|
13
|
+
*.test
|
|
14
|
+
go.work
|
|
15
|
+
go.work.sum
|
|
16
|
+
.idea/
|
|
@@ -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,25 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dex-connector-google-docs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dex-native Google Docs connector
|
|
5
|
+
Project-URL: Source, https://github.com/superdurable/dex-connectors-library/tree/main/connectors/google/docs/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
|
+
# Google Docs Python connector
|
|
15
|
+
|
|
16
|
+
Dex-native Google Docs operations, with operation-specific Step factories for
|
|
17
|
+
`getDocumentText`, `createDocument`, `replaceDocumentText`, and `appendText`.
|
|
18
|
+
The package requires Connector SDK 0.7.0. Use `new_project_connection` for the
|
|
19
|
+
generated Google OAuth or Workspace domain delegation credential refresh wiring.
|
|
20
|
+
Provider creation timestamps must be valid Go JSON timestamps; invalid values
|
|
21
|
+
fail before a document Result is stored.
|
|
22
|
+
|
|
23
|
+
The [policy read example](examples/policy_read/README.md) shows the static
|
|
24
|
+
connection name, FDG generation, Worker, and Flow starter. See the
|
|
25
|
+
[parent connector README](../README.md) for authorization and operation guidance.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Google Docs Python connector
|
|
2
|
+
|
|
3
|
+
Dex-native Google Docs operations, with operation-specific Step factories for
|
|
4
|
+
`getDocumentText`, `createDocument`, `replaceDocumentText`, and `appendText`.
|
|
5
|
+
The package requires Connector SDK 0.7.0. Use `new_project_connection` for the
|
|
6
|
+
generated Google OAuth or Workspace domain delegation credential refresh wiring.
|
|
7
|
+
Provider creation timestamps must be valid Go JSON timestamps; invalid values
|
|
8
|
+
fail before a document Result is stored.
|
|
9
|
+
|
|
10
|
+
The [policy read example](examples/policy_read/README.md) shows the static
|
|
11
|
+
connection name, FDG generation, Worker, and Flow starter. See the
|
|
12
|
+
[parent connector README](../README.md) for authorization and operation guidance.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Google Docs policy read
|
|
2
|
+
|
|
3
|
+
This Flow reads the first tab of a Google Doc as plain text. It uses the static
|
|
4
|
+
`google-docs-policy` connection and fails if an optional provider branch is selected.
|
|
5
|
+
|
|
6
|
+
From `connectors/google/docs/python`, install with `uv sync --locked` and generate
|
|
7
|
+
strict FDG 2.0 with:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
mkdir -p build
|
|
11
|
+
dexcli visualize ./examples/policy_read/flow.py --language python \
|
|
12
|
+
--python .venv/bin/python --schema-version 2.0 --json \
|
|
13
|
+
--out ./build/google-docs-policy-read
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Start `dexcli dev -flow-rendering-dir ./build`, configure the Google OAuth
|
|
17
|
+
`google-docs-policy` connection in Dex Web following the parent README, then
|
|
18
|
+
export the owner-only `DEX_CONNECTOR_CONFIG_FILE` path and
|
|
19
|
+
`DEX_FLOW_SERVICE_ADDRESS` from that project. Run the Worker and start a
|
|
20
|
+
Flow with a unique ID and a document ID from the
|
|
21
|
+
`/document/d/{documentId}/edit` portion of its Google Docs URL:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
uv run python -m examples.policy_read.worker
|
|
25
|
+
uv run python -m examples.policy_read.start policy-read-001 YOUR_DOCUMENT_ID
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The Dex integration test uses a local provider fake and needs no Google
|
|
29
|
+
authorization: `uv run pytest examples/policy_read/integration_test.py`
|
|
30
|
+
while `dexcli dev` is running.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Read one Google Doc through a Dex Flow."""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
import dex
|
|
8
|
+
from dex_connectors.sdk import QueryResult
|
|
9
|
+
|
|
10
|
+
from dex_connectors.google_docs import (
|
|
11
|
+
Connection,
|
|
12
|
+
GetDocumentTextInput,
|
|
13
|
+
GetDocumentTextOutput,
|
|
14
|
+
new_get_document_text_step,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
CONNECTION_NAME = "google-docs-policy"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class PolicyReadRequest:
|
|
22
|
+
"""Identify the Google Doc whose first tab should be read."""
|
|
23
|
+
|
|
24
|
+
document_id: str = field(default="", metadata={"dex_json_name": "documentId"})
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# dex:group group-id:google-docs group-label:"Google Docs"
|
|
28
|
+
# dex:explanation text:"Complete with the bounded text returned by Google Docs."
|
|
29
|
+
class _CompletePolicyRead(dex.Step[QueryResult[GetDocumentTextOutput]]):
|
|
30
|
+
def get_step_type(self) -> str:
|
|
31
|
+
return "CompleteGoogleDocsPolicyRead"
|
|
32
|
+
|
|
33
|
+
def execute(
|
|
34
|
+
self, context: dex.Context, input: QueryResult[GetDocumentTextOutput]
|
|
35
|
+
) -> dex.StepDecision:
|
|
36
|
+
return dex.graceful_complete(input.value)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PolicyReadFlow(dex.Flow[PolicyReadRequest]):
|
|
40
|
+
"""Read one policy document through the static Google Docs connection."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, connection: Connection) -> None:
|
|
43
|
+
if connection.reference.name != CONNECTION_NAME:
|
|
44
|
+
raise ValueError("policy read requires the static Google Docs connection")
|
|
45
|
+
self._complete = _CompletePolicyRead()
|
|
46
|
+
self._read = new_get_document_text_step(
|
|
47
|
+
step_type="ReadGoogleDocsPolicy",
|
|
48
|
+
step_input_type=PolicyReadRequest,
|
|
49
|
+
annotations={
|
|
50
|
+
"groupId": "google-docs",
|
|
51
|
+
"groupLabel": "Google Docs",
|
|
52
|
+
"explanation": "Read the first tab as plain text with pending suggestions rejected.",
|
|
53
|
+
},
|
|
54
|
+
connection=connection,
|
|
55
|
+
connection_name=CONNECTION_NAME,
|
|
56
|
+
map_to_operation_input=lambda request: GetDocumentTextInput(
|
|
57
|
+
document_id=request.document_id, format="plainText"
|
|
58
|
+
),
|
|
59
|
+
read=self._complete,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def get_flow_type(self) -> str:
|
|
63
|
+
"""Return the stable Flow identity."""
|
|
64
|
+
return "GoogleDocsPolicyRead"
|
|
65
|
+
|
|
66
|
+
def get_steps(self) -> dex.StepList[PolicyReadRequest]:
|
|
67
|
+
"""Register the read and completion Steps."""
|
|
68
|
+
return dex.StepList.start_step(self._read).other_steps(self._complete)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Exercise the checked-in Flow with a real Dex Worker and Client."""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
from datetime import timedelta
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from uuid import uuid4
|
|
10
|
+
|
|
11
|
+
import dex
|
|
12
|
+
import httpx
|
|
13
|
+
import pytest
|
|
14
|
+
from dex_connectors.sdk import ConnectionRef, SecretString, StaticCredentialProvider
|
|
15
|
+
|
|
16
|
+
from dex_connectors.google_docs import Client, Config, Credentials, GetDocumentTextOutput, new_connection
|
|
17
|
+
|
|
18
|
+
from .flow import CONNECTION_NAME, PolicyReadFlow, PolicyReadRequest
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.mark.asyncio
|
|
22
|
+
async def test_read_retries_and_unwired_not_found_fails(tmp_path: Path) -> None:
|
|
23
|
+
"""Verify the read's retry and optional branch through real Dex state."""
|
|
24
|
+
requests: list[httpx.Request] = []
|
|
25
|
+
|
|
26
|
+
def answer(request: httpx.Request) -> httpx.Response:
|
|
27
|
+
requests.append(request)
|
|
28
|
+
if request.url.path.endswith("/missing"):
|
|
29
|
+
return httpx.Response(404, json={"error": {"code": 404}})
|
|
30
|
+
if len(requests) == 1:
|
|
31
|
+
return httpx.Response(503, json={"error": {"code": 503}})
|
|
32
|
+
return httpx.Response(200, json={
|
|
33
|
+
"documentId": "policy123",
|
|
34
|
+
"title": "Policy",
|
|
35
|
+
"revisionId": "rev-1",
|
|
36
|
+
"tabs": [{
|
|
37
|
+
"tabProperties": {"tabId": "t.0"},
|
|
38
|
+
"documentTab": {"body": {"content": [
|
|
39
|
+
{"endIndex": 1, "sectionBreak": {}},
|
|
40
|
+
{"startIndex": 1, "endIndex": 8, "paragraph": {
|
|
41
|
+
"elements": [{"textRun": {"content": "Policy\n"}}]
|
|
42
|
+
}},
|
|
43
|
+
]}},
|
|
44
|
+
}],
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
reference = ConnectionRef("google", CONNECTION_NAME)
|
|
48
|
+
credentials = StaticCredentialProvider({
|
|
49
|
+
reference: Credentials(access_token=SecretString("inert-docs-token"))
|
|
50
|
+
})
|
|
51
|
+
async with httpx.AsyncClient(transport=httpx.MockTransport(answer)) as provider_http_client:
|
|
52
|
+
provider = Client(
|
|
53
|
+
Config(docs_endpoint="http://127.0.0.1:44350", drive_endpoint="http://127.0.0.1:44350"),
|
|
54
|
+
credentials,
|
|
55
|
+
provider_http_client,
|
|
56
|
+
)
|
|
57
|
+
connection = new_connection(provider, reference)
|
|
58
|
+
flow = PolicyReadFlow(connection)
|
|
59
|
+
registry = dex.Registry([flow], allow_async_handlers=True)
|
|
60
|
+
cache = dex.open_blob_cache(dex.BlobCacheConfig(str(tmp_path / "blobs"), 1 << 24))
|
|
61
|
+
address = os.environ["DEX_FLOW_SERVICE_ADDRESS"]
|
|
62
|
+
worker = dex.AsyncWorker(registry, cache, dex.WorkerOptions(server_address=address))
|
|
63
|
+
worker_task = asyncio.create_task(worker.start())
|
|
64
|
+
try:
|
|
65
|
+
await worker.wait_pull_registered(timeout=20)
|
|
66
|
+
async with dex.AsyncClient(
|
|
67
|
+
registry, cache, dex.ClientOptions(server_address=address)
|
|
68
|
+
) as client:
|
|
69
|
+
successful_id = f"google-docs-python-{uuid4().hex}"
|
|
70
|
+
await client.start_flow(flow, successful_id, PolicyReadRequest("policy123"))
|
|
71
|
+
successful = await asyncio.wait_for(
|
|
72
|
+
client.wait_for_flow(successful_id, timedelta(seconds=20)), 30
|
|
73
|
+
)
|
|
74
|
+
assert successful.status == dex.FlowStatus.COMPLETED, successful.error_message
|
|
75
|
+
output = successful.single_output(GetDocumentTextOutput)
|
|
76
|
+
assert output.document_id == "policy123"
|
|
77
|
+
assert output.text == "Policy"
|
|
78
|
+
assert len(requests) == 2
|
|
79
|
+
|
|
80
|
+
missing_id = f"google-docs-python-{uuid4().hex}"
|
|
81
|
+
await client.start_flow(flow, missing_id, PolicyReadRequest("missing"))
|
|
82
|
+
missing = await asyncio.wait_for(
|
|
83
|
+
client.wait_for_flow(missing_id, timedelta(seconds=20)), 30
|
|
84
|
+
)
|
|
85
|
+
assert missing.status == dex.FlowStatus.FAILED
|
|
86
|
+
assert missing.error_message is not None
|
|
87
|
+
assert "notFound" in missing.error_message
|
|
88
|
+
assert len(requests) == 3
|
|
89
|
+
finally:
|
|
90
|
+
await worker.stop()
|
|
91
|
+
await worker_task
|
|
92
|
+
cache.close()
|
|
93
|
+
await connection.aclose()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Start a policy read through the static Google Docs connection."""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import asyncio
|
|
7
|
+
import os
|
|
8
|
+
import tempfile
|
|
9
|
+
|
|
10
|
+
import dex
|
|
11
|
+
from dex_connectors.sdk.projectconfig import load_from_environment
|
|
12
|
+
|
|
13
|
+
from dex_connectors.google_docs import new_project_connection
|
|
14
|
+
|
|
15
|
+
from .flow import CONNECTION_NAME, PolicyReadFlow, PolicyReadRequest
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def start_policy_read(flow_id: str, document_id: str) -> str:
|
|
19
|
+
"""Start one Flow for a Google Docs document ID."""
|
|
20
|
+
project = await load_from_environment()
|
|
21
|
+
connection = new_project_connection(project, CONNECTION_NAME)
|
|
22
|
+
try:
|
|
23
|
+
flow = PolicyReadFlow(connection)
|
|
24
|
+
registry = dex.Registry([flow], allow_async_handlers=True)
|
|
25
|
+
with tempfile.TemporaryDirectory(prefix="google-docs-python-start-blobs-") as directory:
|
|
26
|
+
cache = dex.open_blob_cache(dex.BlobCacheConfig(directory, 1 << 24))
|
|
27
|
+
try:
|
|
28
|
+
async with dex.AsyncClient(
|
|
29
|
+
registry, cache,
|
|
30
|
+
dex.ClientOptions(
|
|
31
|
+
server_address=os.environ.get("DEX_FLOW_SERVICE_ADDRESS", "localhost:8801")
|
|
32
|
+
),
|
|
33
|
+
) as client:
|
|
34
|
+
return await client.start_flow(flow, flow_id, PolicyReadRequest(document_id))
|
|
35
|
+
finally:
|
|
36
|
+
cache.close()
|
|
37
|
+
finally:
|
|
38
|
+
await connection.aclose()
|
|
39
|
+
await project.close()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main() -> None:
|
|
43
|
+
"""Parse a unique Flow ID and document ID."""
|
|
44
|
+
parser = argparse.ArgumentParser(description="Read a Google Doc through Dex")
|
|
45
|
+
parser.add_argument("flow_id")
|
|
46
|
+
parser.add_argument("document_id")
|
|
47
|
+
arguments = parser.parse_args()
|
|
48
|
+
print(asyncio.run(start_policy_read(arguments.flow_id, arguments.document_id)))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
main()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Worker entrypoint for the Google Docs policy read Flow."""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
import tempfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import dex
|
|
11
|
+
from dex_connectors.sdk.projectconfig import load_from_environment
|
|
12
|
+
|
|
13
|
+
from dex_connectors.google_docs import new_project_connection
|
|
14
|
+
|
|
15
|
+
from .flow import CONNECTION_NAME, PolicyReadFlow
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def run_worker() -> None:
|
|
19
|
+
"""Load the connection and serve policy read executions."""
|
|
20
|
+
project = await load_from_environment()
|
|
21
|
+
connection = None
|
|
22
|
+
cache = None
|
|
23
|
+
try:
|
|
24
|
+
connection = new_project_connection(project, CONNECTION_NAME)
|
|
25
|
+
registry = dex.Registry([PolicyReadFlow(connection)], allow_async_handlers=True)
|
|
26
|
+
cache_directory = os.environ.get(
|
|
27
|
+
"DEX_BLOB_CACHE_DIR", str(Path(tempfile.gettempdir()) / "dex-google-docs-policy-blobs")
|
|
28
|
+
)
|
|
29
|
+
cache = dex.open_blob_cache(dex.BlobCacheConfig(cache_directory, 1 << 30))
|
|
30
|
+
worker = dex.AsyncWorker(
|
|
31
|
+
registry, cache,
|
|
32
|
+
dex.WorkerOptions(server_address=os.environ.get("DEX_FLOW_SERVICE_ADDRESS", "localhost:8801")),
|
|
33
|
+
)
|
|
34
|
+
async with worker:
|
|
35
|
+
await worker.start()
|
|
36
|
+
finally:
|
|
37
|
+
if connection is not None:
|
|
38
|
+
await connection.aclose()
|
|
39
|
+
if cache is not None:
|
|
40
|
+
cache.close()
|
|
41
|
+
await project.close()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def main() -> None:
|
|
45
|
+
"""Run the Worker until stopped."""
|
|
46
|
+
asyncio.run(run_worker())
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
main()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dex-connector-google-docs"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Dex-native Google Docs connector"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
dependencies = ["dex-connectors-sdk==0.7.0", "dex-python-sdk>=1.28.0", "httpx>=0.27"]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Source = "https://github.com/superdurable/dex-connectors-library/tree/main/connectors/google/docs/python"
|
|
17
|
+
|
|
18
|
+
[dependency-groups]
|
|
19
|
+
dev = ["pytest>=8", "pytest-asyncio>=0.24", "mypy>=1.13", "ruff>=0.7", "PyYAML>=6", "types-PyYAML>=6"]
|
|
20
|
+
|
|
21
|
+
[tool.hatch.build.targets.wheel]
|
|
22
|
+
packages = ["src/dex_connectors"]
|
|
23
|
+
|
|
24
|
+
[tool.pytest.ini_options]
|
|
25
|
+
testpaths = ["tests"]
|
|
26
|
+
pythonpath = [".", "src"]
|
|
27
|
+
asyncio_mode = "auto"
|
|
28
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
29
|
+
|
|
30
|
+
[tool.mypy]
|
|
31
|
+
python_version = "3.11"
|
|
32
|
+
strict = true
|
|
33
|
+
mypy_path = "src"
|
|
34
|
+
explicit_package_bases = true
|
|
35
|
+
|
|
36
|
+
[tool.ruff]
|
|
37
|
+
target-version = "py311"
|
|
38
|
+
line-length = 110
|
|
39
|
+
|
|
40
|
+
[tool.ruff.lint]
|
|
41
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Copyright (c) 2026 Super Durable
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Dex-native Google Docs provider operations and Step factories."""
|
|
4
|
+
|
|
5
|
+
from .client import Client, Config, Credentials
|
|
6
|
+
from .connection import Connection, new_connection, new_connection_with_operations, new_project_connection
|
|
7
|
+
from .models import (
|
|
8
|
+
AppendTextInput,
|
|
9
|
+
AppendTextOutput,
|
|
10
|
+
CreatedDocument,
|
|
11
|
+
CreateDocumentInput,
|
|
12
|
+
CreateDocumentOutput,
|
|
13
|
+
GetDocumentTextInput,
|
|
14
|
+
GetDocumentTextOutput,
|
|
15
|
+
PlaceholderReplacement,
|
|
16
|
+
PlaceholderReplacementResult,
|
|
17
|
+
ReplaceDocumentTextInput,
|
|
18
|
+
ReplaceDocumentTextOutput,
|
|
19
|
+
)
|
|
20
|
+
from .steps import (
|
|
21
|
+
new_append_text_step,
|
|
22
|
+
new_create_document_step,
|
|
23
|
+
new_get_document_text_step,
|
|
24
|
+
new_replace_document_text_step,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"Client",
|
|
29
|
+
"Config",
|
|
30
|
+
"Credentials",
|
|
31
|
+
"Connection",
|
|
32
|
+
"new_connection",
|
|
33
|
+
"new_connection_with_operations",
|
|
34
|
+
"new_project_connection",
|
|
35
|
+
"AppendTextInput",
|
|
36
|
+
"AppendTextOutput",
|
|
37
|
+
"CreatedDocument",
|
|
38
|
+
"CreateDocumentInput",
|
|
39
|
+
"CreateDocumentOutput",
|
|
40
|
+
"GetDocumentTextInput",
|
|
41
|
+
"GetDocumentTextOutput",
|
|
42
|
+
"PlaceholderReplacement",
|
|
43
|
+
"PlaceholderReplacementResult",
|
|
44
|
+
"ReplaceDocumentTextInput",
|
|
45
|
+
"ReplaceDocumentTextOutput",
|
|
46
|
+
"new_append_text_step",
|
|
47
|
+
"new_create_document_step",
|
|
48
|
+
"new_get_document_text_step",
|
|
49
|
+
"new_replace_document_text_step",
|
|
50
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
docs_endpoint: str = "https://docs.googleapis.com"
|
|
14
|
+
drive_endpoint: str = "https://www.googleapis.com"
|
|
15
|
+
max_response_bytes: int = 8388608
|
|
16
|
+
max_text_bytes: int = 1048576
|
|
17
|
+
|
|
18
|
+
setattr(Config, '__module__', 'dex_connectors.google_docs.client')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class Credentials:
|
|
23
|
+
|
|
24
|
+
auth_method_id: str = "google-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
|
+
service_account_key: SecretString | None = None
|
|
30
|
+
delegated_user: str | None = None
|
|
31
|
+
|
|
32
|
+
setattr(Credentials, '__module__', 'dex_connectors.google_docs.client')
|