dex-connector-front 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.
Files changed (33) hide show
  1. dex_connector_front-0.1.0/.gitignore +16 -0
  2. dex_connector_front-0.1.0/LICENSE +21 -0
  3. dex_connector_front-0.1.0/PKG-INFO +78 -0
  4. dex_connector_front-0.1.0/README.md +65 -0
  5. dex_connector_front-0.1.0/examples/__init__.py +3 -0
  6. dex_connector_front-0.1.0/examples/contact_lookup/README.md +29 -0
  7. dex_connector_front-0.1.0/examples/contact_lookup/__init__.py +3 -0
  8. dex_connector_front-0.1.0/examples/contact_lookup/flow.py +78 -0
  9. dex_connector_front-0.1.0/examples/contact_lookup/integration_test.py +89 -0
  10. dex_connector_front-0.1.0/examples/contact_lookup/main.py +27 -0
  11. dex_connector_front-0.1.0/examples/contact_lookup/sample-input.json +1 -0
  12. dex_connector_front-0.1.0/examples/contact_lookup/worker.py +26 -0
  13. dex_connector_front-0.1.0/pyproject.toml +41 -0
  14. dex_connector_front-0.1.0/src/dex_connectors/front/__init__.py +62 -0
  15. dex_connector_front-0.1.0/src/dex_connectors/front/_generated/__init__.py +2 -0
  16. dex_connector_front-0.1.0/src/dex_connectors/front/_generated/config.py +23 -0
  17. dex_connector_front-0.1.0/src/dex_connectors/front/_generated/connection.py +151 -0
  18. dex_connector_front-0.1.0/src/dex_connectors/front/_generated/definitions.py +113 -0
  19. dex_connector_front-0.1.0/src/dex_connectors/front/_generated/mock.py +338 -0
  20. dex_connector_front-0.1.0/src/dex_connectors/front/_generated/models.py +424 -0
  21. dex_connector_front-0.1.0/src/dex_connectors/front/_generated/steps.py +296 -0
  22. dex_connector_front-0.1.0/src/dex_connectors/front/_generated/timestamps.py +60 -0
  23. dex_connector_front-0.1.0/src/dex_connectors/front/client.py +467 -0
  24. dex_connector_front-0.1.0/src/dex_connectors/front/connection.py +5 -0
  25. dex_connector_front-0.1.0/src/dex_connectors/front/dex-connector.json +201 -0
  26. dex_connector_front-0.1.0/src/dex_connectors/front/frontmock/__init__.py +6 -0
  27. dex_connector_front-0.1.0/src/dex_connectors/front/models.py +5 -0
  28. dex_connector_front-0.1.0/src/dex_connectors/front/operations.py +958 -0
  29. dex_connector_front-0.1.0/src/dex_connectors/front/py.typed +0 -0
  30. dex_connector_front-0.1.0/src/dex_connectors/front/steps.py +5 -0
  31. dex_connector_front-0.1.0/tests/test_client.py +106 -0
  32. dex_connector_front-0.1.0/tests/test_conformance.py +38 -0
  33. dex_connector_front-0.1.0/uv.lock +932 -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,78 @@
1
+ Metadata-Version: 2.5
2
+ Name: dex-connector-front
3
+ Version: 0.1.0
4
+ Summary: Dex-native Front conversation and contact connector
5
+ Project-URL: Source, https://github.com/superdurable/dex-connectors-library/tree/main/connectors/front/python
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: dex-connectors-sdk==0.1.0
10
+ Requires-Dist: dex-python-sdk>=1.28.0
11
+ Requires-Dist: httpx>=0.27
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Front connector for Python
15
+
16
+ `dex-connector-front==0.1.0` implements all five Front operations as Dex Python
17
+ Queries and Mutations: `searchConversations`, `getConversation`,
18
+ `replyToConversation`, `updateConversation`, and `findContactByEmail`. It has
19
+ no Python Trigger. Import from `dex_connectors.front`; the generated models,
20
+ Step factories, connection binding, mock outcomes, and `dex-connector.json`
21
+ come from [`connector.yaml`](../connector.yaml).
22
+
23
+ The connector requires Python 3.11 or newer, Dex Python SDK 1.28.0 or newer,
24
+ and the pinned `dex-connectors-sdk` release in `pyproject.toml`.
25
+
26
+ ## Connection
27
+
28
+ The runnable example uses the static name `front-support`. Create an API token
29
+ at <https://app.frontapp.com/> under **Settings > Developers > API Tokens**.
30
+ A company admin chooses **Create API token**, grants contact read access for
31
+ this example, then copies the token into the secret `api_token` field in Dex Web
32
+ **Connections**. The token has three dot-separated JWT segments and is sent
33
+ only as an Authorization bearer token. The `maxResponseBytes` setting limits
34
+ one complete response in bytes; leave it blank to retain the displayed safety
35
+ limit, and raise it only when a valid large response selects `invalidResponse`.
36
+ The [Front connector guide](../README.md#authorization) details permissions
37
+ for every operation and the inbox, teammate, and tag pickers.
38
+
39
+ The runnable [`main.py`](examples/contact_lookup/main.py) loads project
40
+ configuration and resolves private credentials at call time through
41
+ `new_project_connection(project, "front-support")`. Secrets stay out of Flow
42
+ input, Results, Attributes, receipts, logs, and generated manifests.
43
+
44
+ ## Operation outcomes
45
+
46
+ The generated operation-specific factories accept one required happy-path
47
+ branch. Optional branches fail the Flow when left unwired. Searches select
48
+ `searched`; conversation reads and contact lookups select `found`; replies
49
+ select `replied`; updates select `updated`. Reads and updates retry safe
50
+ transient failures. A reply or internal comment uses the SDK single-dispatch
51
+ fence because Front has no provider idempotency key. Ambiguous delivery
52
+ selects `uncertain`; reconcile it in Front before making a new request. An
53
+ update reads back its requested values and can repeat absolute writes.
54
+
55
+ `frontmock.Mock` and its generated named scenarios can exercise branch routing
56
+ without credentials. The 50 Go-recorded provider scenarios in
57
+ [`conformance/connectors/front`](../../../conformance/connectors/front/scenarios.json)
58
+ exercise exact HTTP requests, response decoding, branches, retry decisions,
59
+ and the production SDK output codec. They supplement the real Dex Worker and
60
+ Client [example integration test](examples/contact_lookup/integration_test.py).
61
+
62
+ ## Run the example
63
+
64
+ See the [contact lookup Flow](examples/contact_lookup/README.md). From this
65
+ package directory, install and run its Worker after configuring Dex Web:
66
+
67
+ ```sh
68
+ uv sync --locked
69
+ DEX_FLOW_SERVICE_ADDRESS=127.0.0.1:8801 \
70
+ DEX_CONNECTOR_CONFIG_FILE=/path/to/connections.json \
71
+ uv run python -m examples.contact_lookup.main
72
+ ```
73
+
74
+ For local verification with `dexcli dev`, use `uv run pytest` for the package
75
+ suite and set `DEX_FLOW_SERVICE_ADDRESS` to the active local Dex gRPC address
76
+ when running `uv run pytest examples/contact_lookup/integration_test.py`.
77
+ The local integration test uses a free-port Front stand-in; Front account
78
+ setup, API permissions, and live provider behavior remain unverified.
@@ -0,0 +1,65 @@
1
+ # Front connector for Python
2
+
3
+ `dex-connector-front==0.1.0` implements all five Front operations as Dex Python
4
+ Queries and Mutations: `searchConversations`, `getConversation`,
5
+ `replyToConversation`, `updateConversation`, and `findContactByEmail`. It has
6
+ no Python Trigger. Import from `dex_connectors.front`; the generated models,
7
+ Step factories, connection binding, mock outcomes, and `dex-connector.json`
8
+ come from [`connector.yaml`](../connector.yaml).
9
+
10
+ The connector requires Python 3.11 or newer, Dex Python SDK 1.28.0 or newer,
11
+ and the pinned `dex-connectors-sdk` release in `pyproject.toml`.
12
+
13
+ ## Connection
14
+
15
+ The runnable example uses the static name `front-support`. Create an API token
16
+ at <https://app.frontapp.com/> under **Settings > Developers > API Tokens**.
17
+ A company admin chooses **Create API token**, grants contact read access for
18
+ this example, then copies the token into the secret `api_token` field in Dex Web
19
+ **Connections**. The token has three dot-separated JWT segments and is sent
20
+ only as an Authorization bearer token. The `maxResponseBytes` setting limits
21
+ one complete response in bytes; leave it blank to retain the displayed safety
22
+ limit, and raise it only when a valid large response selects `invalidResponse`.
23
+ The [Front connector guide](../README.md#authorization) details permissions
24
+ for every operation and the inbox, teammate, and tag pickers.
25
+
26
+ The runnable [`main.py`](examples/contact_lookup/main.py) loads project
27
+ configuration and resolves private credentials at call time through
28
+ `new_project_connection(project, "front-support")`. Secrets stay out of Flow
29
+ input, Results, Attributes, receipts, logs, and generated manifests.
30
+
31
+ ## Operation outcomes
32
+
33
+ The generated operation-specific factories accept one required happy-path
34
+ branch. Optional branches fail the Flow when left unwired. Searches select
35
+ `searched`; conversation reads and contact lookups select `found`; replies
36
+ select `replied`; updates select `updated`. Reads and updates retry safe
37
+ transient failures. A reply or internal comment uses the SDK single-dispatch
38
+ fence because Front has no provider idempotency key. Ambiguous delivery
39
+ selects `uncertain`; reconcile it in Front before making a new request. An
40
+ update reads back its requested values and can repeat absolute writes.
41
+
42
+ `frontmock.Mock` and its generated named scenarios can exercise branch routing
43
+ without credentials. The 50 Go-recorded provider scenarios in
44
+ [`conformance/connectors/front`](../../../conformance/connectors/front/scenarios.json)
45
+ exercise exact HTTP requests, response decoding, branches, retry decisions,
46
+ and the production SDK output codec. They supplement the real Dex Worker and
47
+ Client [example integration test](examples/contact_lookup/integration_test.py).
48
+
49
+ ## Run the example
50
+
51
+ See the [contact lookup Flow](examples/contact_lookup/README.md). From this
52
+ package directory, install and run its Worker after configuring Dex Web:
53
+
54
+ ```sh
55
+ uv sync --locked
56
+ DEX_FLOW_SERVICE_ADDRESS=127.0.0.1:8801 \
57
+ DEX_CONNECTOR_CONFIG_FILE=/path/to/connections.json \
58
+ uv run python -m examples.contact_lookup.main
59
+ ```
60
+
61
+ For local verification with `dexcli dev`, use `uv run pytest` for the package
62
+ suite and set `DEX_FLOW_SERVICE_ADDRESS` to the active local Dex gRPC address
63
+ when running `uv run pytest examples/contact_lookup/integration_test.py`.
64
+ The local integration test uses a free-port Front stand-in; Front account
65
+ setup, API permissions, and live provider behavior remain unverified.
@@ -0,0 +1,3 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ """Runnable Front connector applications."""
@@ -0,0 +1,29 @@
1
+ # Front contact lookup Flow
2
+
3
+ This Flow uses the static `front-support` connection to find the Front contact
4
+ that owns one email handle. Run its Worker with `python -m
5
+ examples.contact_lookup.main` after configuring the connection through Dex Web.
6
+ The connection holds an API token; the Flow input is only an email address.
7
+
8
+ Start `dexcli dev` and configure `front / front-support` in Dex Web **Connections**.
9
+ Create the Front API token using the authorization guide in
10
+ [`connector.yaml`](../../../connector.yaml). In Front, an admin opens
11
+ <https://app.frontapp.com/> > **Settings > Developers > API Tokens**,
12
+ creates a token with contact read access, and copies its secret into Dex Web.
13
+ Leave the response byte limit blank for the displayed default. Set
14
+ `DEX_CONNECTOR_CONFIG_FILE` to the private `connections.json` exported by that
15
+ project, then run:
16
+
17
+ ```sh
18
+ uv sync --locked
19
+ DEX_FLOW_SERVICE_ADDRESS=127.0.0.1:8801 \
20
+ DEX_CONNECTOR_CONFIG_FILE=/path/to/connections.json \
21
+ uv run python -m examples.contact_lookup.main
22
+ ```
23
+
24
+ The Worker registration uses the real application source in [`main.py`](main.py).
25
+ Start `FrontContactLookupPython` with `sample-input.json` using a Dex Client.
26
+ Only the `found` branch is wired; a missing contact, provider rejection,
27
+ invalid response, or local defect fails the Flow. `integration_test.py` runs
28
+ this Flow through a real Dex Worker and Client against a local Front stand-in.
29
+ Live Front authorization and provider behavior remain unverified.
@@ -0,0 +1,3 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ """Front contact lookup Flow."""
@@ -0,0 +1,78 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ """Find a Front contact through one durable Dex Query Step."""
4
+
5
+ from dataclasses import dataclass
6
+
7
+ import dex
8
+ from dex_connectors.sdk import QueryResult
9
+
10
+ from dex_connectors.front import (
11
+ Connection,
12
+ FindContactByEmailInput,
13
+ FindContactByEmailOutput,
14
+ new_find_contact_by_email_step,
15
+ )
16
+
17
+ CONNECTION_NAME = "front-support"
18
+ FLOW_TYPE = "FrontContactLookupPython"
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class ContactLookupRequest:
23
+ """Customer email entered when starting the Flow."""
24
+
25
+ email: str
26
+
27
+
28
+ def map_to_contact_lookup_input(request: ContactLookupRequest) -> FindContactByEmailInput:
29
+ """Map the safe Flow input to Front's email alias lookup."""
30
+ return FindContactByEmailInput(email=request.email)
31
+
32
+
33
+ # dex:group group-id:front-contact group-label:"Front contact"
34
+ # dex:explanation text:"Return the Front contact found for the requested email."
35
+ class CompleteContactLookup(dex.Step[QueryResult[FindContactByEmailOutput]]):
36
+ """Complete the Flow with the contact lookup output."""
37
+
38
+ def get_step_type(self) -> str:
39
+ """Return the stable completion Step identity."""
40
+ return "CompleteFrontContactLookup"
41
+
42
+ def execute(self, context: dex.Context, input: QueryResult[FindContactByEmailOutput]) -> dex.StepDecision:
43
+ """Keep the result free of connection credentials."""
44
+ return dex.graceful_complete(input.value)
45
+
46
+
47
+ class ContactLookupFlow(dex.Flow[ContactLookupRequest]):
48
+ """Find a contact through the static Front connection."""
49
+
50
+ def __init__(self, connection: Connection) -> None:
51
+ if connection.reference.name != CONNECTION_NAME:
52
+ raise ValueError("Front contact lookup requires its static connection name")
53
+ self.complete = CompleteContactLookup()
54
+ self.lookup = new_find_contact_by_email_step(
55
+ step_type="FindFrontContactByEmail",
56
+ step_input_type=ContactLookupRequest,
57
+ annotations={
58
+ "groupId": "front-contact",
59
+ "groupLabel": "Front contact",
60
+ "explanation": "Find the Front contact with this email handle.",
61
+ },
62
+ connection=connection,
63
+ connection_name="front-support",
64
+ map_to_operation_input=map_to_contact_lookup_input,
65
+ found=self.complete,
66
+ )
67
+
68
+ def get_flow_type(self) -> str:
69
+ """Return the stable application Flow type."""
70
+ return FLOW_TYPE
71
+
72
+ def get_steps(self) -> dex.StepList[ContactLookupRequest]:
73
+ """Register the provider Query and completion Step."""
74
+ return dex.StepList.start_step(self.lookup).other_steps(self.complete)
75
+
76
+ def get_persistence_schema(self) -> dex.PersistenceSchema:
77
+ """Persist no Attributes; completion carries the safe contact."""
78
+ return dex.PersistenceSchema.of()
@@ -0,0 +1,89 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ """Exercise the Front example through a real Dex Worker and Client."""
4
+
5
+ import asyncio
6
+ import os
7
+ from datetime import timedelta
8
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
9
+ from pathlib import Path
10
+ from threading import Thread
11
+ from uuid import uuid4
12
+
13
+ import dex
14
+ import pytest
15
+ from dex_connectors.sdk import ConnectionRef, SecretString, StaticCredentialProvider
16
+
17
+ from dex_connectors.front import Client, Config, Credentials, FindContactByEmailOutput, new_connection
18
+
19
+ from .flow import CONNECTION_NAME, ContactLookupFlow, ContactLookupRequest
20
+
21
+
22
+ @pytest.mark.asyncio
23
+ async def test_contact_lookup_retries_and_fails_unwired_not_found(tmp_path: Path) -> None:
24
+ """Verify durable retry and optional branch routing against a local provider."""
25
+ server_address = os.environ["DEX_FLOW_SERVICE_ADDRESS"]
26
+ requests: list[str] = []
27
+
28
+ class FrontHandler(BaseHTTPRequestHandler):
29
+ def do_GET(self) -> None:
30
+ requests.append(self.path)
31
+ if "missing" in self.path:
32
+ self.send_response(404)
33
+ self.end_headers()
34
+ return
35
+ if len(requests) == 1:
36
+ self.send_response(429)
37
+ self.end_headers()
38
+ return
39
+ body = (
40
+ b'{"id":"crd_100","name":"Jane","handles":'
41
+ b'[{"handle":"jane@acme.example.com","source":"email"}]}'
42
+ )
43
+ self.send_response(200)
44
+ self.send_header("Content-Type", "application/json")
45
+ self.send_header("Content-Length", str(len(body)))
46
+ self.end_headers()
47
+ self.wfile.write(body)
48
+
49
+ def log_message(self, format: str, *args: object) -> None:
50
+ pass # The stand-in logs no request details or credentials.
51
+
52
+ provider = ThreadingHTTPServer(("127.0.0.1", 0), FrontHandler)
53
+ provider_thread = Thread(target=provider.serve_forever, daemon=True)
54
+ provider_thread.start()
55
+ reference = ConnectionRef("front", CONNECTION_NAME)
56
+ credentials = StaticCredentialProvider({reference: Credentials(SecretString("front-inert-test-token"))})
57
+ client = Client(Config(), credentials, api_base_url=f"http://127.0.0.1:{provider.server_port}")
58
+ connection = new_connection(client, reference)
59
+ flow = ContactLookupFlow(connection)
60
+ registry = dex.Registry([flow], allow_async_handlers=True)
61
+ cache = dex.open_blob_cache(dex.BlobCacheConfig(str(tmp_path / "blobs"), 1 << 24))
62
+ worker = dex.AsyncWorker(registry, cache, dex.WorkerOptions(server_address=server_address))
63
+ worker_task = asyncio.create_task(worker.start())
64
+ try:
65
+ await worker.wait_pull_registered(timeout=30)
66
+ async with dex.AsyncClient(
67
+ registry, cache, dex.ClientOptions(server_address=server_address)
68
+ ) as dex_client:
69
+ success_id = f"front-python-found-{uuid4().hex}"
70
+ await dex_client.start_flow(flow, success_id, ContactLookupRequest("jane@acme.example.com"))
71
+ success = await asyncio.wait_for(dex_client.wait_for_flow(success_id, timedelta(seconds=30)), 40)
72
+ assert success.status == dex.FlowStatus.COMPLETED, success.error_message
73
+ output = success.single_output(FindContactByEmailOutput)
74
+ assert output.contacts[0].id == "crd_100"
75
+ assert requests.count("/contacts/alt:email:jane@acme.example.com") == 2
76
+
77
+ missing_id = f"front-python-missing-{uuid4().hex}"
78
+ await dex_client.start_flow(flow, missing_id, ContactLookupRequest("missing@acme.example.com"))
79
+ missing = await asyncio.wait_for(dex_client.wait_for_flow(missing_id, timedelta(seconds=30)), 40)
80
+ assert missing.status == dex.FlowStatus.FAILED
81
+ assert missing.error_message is not None and "notFound" in missing.error_message
82
+ finally:
83
+ await worker.stop()
84
+ await worker_task
85
+ cache.close()
86
+ await connection.aclose()
87
+ provider.shutdown()
88
+ provider.server_close()
89
+ provider_thread.join(timeout=5)
@@ -0,0 +1,27 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ """Load the Front connection and run the contact lookup Worker."""
4
+
5
+ import asyncio
6
+
7
+ from dex_connectors.sdk.projectconfig import load_from_environment
8
+
9
+ from dex_connectors.front import new_project_connection
10
+
11
+ from .flow import CONNECTION_NAME, ContactLookupFlow
12
+ from .worker import run_worker
13
+
14
+
15
+ async def main() -> None:
16
+ """Bind current project credentials to the static Front connection."""
17
+ project = await load_from_environment()
18
+ connection = new_project_connection(project, CONNECTION_NAME)
19
+ try:
20
+ await run_worker(ContactLookupFlow(connection))
21
+ finally:
22
+ await connection.aclose()
23
+ await project.close()
24
+
25
+
26
+ if __name__ == "__main__":
27
+ asyncio.run(main())
@@ -0,0 +1 @@
1
+ {"email":"jane@acme.example.com"}
@@ -0,0 +1,26 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ """Serve the Front contact lookup Flow with a Dex Worker."""
4
+
5
+ import os
6
+ import tempfile
7
+ from pathlib import Path
8
+
9
+ import dex
10
+
11
+ from .flow import ContactLookupFlow
12
+
13
+
14
+ async def run_worker(flow: ContactLookupFlow) -> None:
15
+ """Run the Flow until the Worker is stopped."""
16
+ registry = dex.Registry([flow], allow_async_handlers=True)
17
+ directory = Path(
18
+ os.environ.get("DEX_BLOB_CACHE_DIR", str(Path(tempfile.gettempdir()) / "dex-front-python-blobs"))
19
+ )
20
+ cache = dex.open_blob_cache(dex.BlobCacheConfig(str(directory), 1 << 30))
21
+ try:
22
+ address = os.environ.get("DEX_FLOW_SERVICE_ADDRESS", "localhost:8801")
23
+ async with dex.AsyncWorker(registry, cache, dex.WorkerOptions(server_address=address)) as worker:
24
+ await worker.start()
25
+ finally:
26
+ cache.close()
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "dex-connector-front"
7
+ version = "0.1.0"
8
+ description = "Dex-native Front conversation and contact connector"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ dependencies = ["dex-connectors-sdk==0.1.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/front/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,62 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ """Front conversation and contact operations for Dex Python Flows."""
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
+ Comment,
9
+ Contact,
10
+ ContactHandle,
11
+ Conversation,
12
+ ConversationDetails,
13
+ ConversationReply,
14
+ FindContactByEmailInput,
15
+ FindContactByEmailOutput,
16
+ GetConversationInput,
17
+ Message,
18
+ ReplyToConversationInput,
19
+ SearchConversationsInput,
20
+ SearchConversationsOutput,
21
+ UpdateConversationInput,
22
+ UpdateConversationOutput,
23
+ )
24
+ from .operations import build_reply_html
25
+ from .steps import (
26
+ new_find_contact_by_email_step,
27
+ new_get_conversation_step,
28
+ new_reply_to_conversation_step,
29
+ new_search_conversations_step,
30
+ new_update_conversation_step,
31
+ )
32
+
33
+ __all__ = [
34
+ "Client",
35
+ "Config",
36
+ "Credentials",
37
+ "Connection",
38
+ "new_connection",
39
+ "new_connection_with_operations",
40
+ "new_project_connection",
41
+ "Comment",
42
+ "Contact",
43
+ "ContactHandle",
44
+ "Conversation",
45
+ "ConversationDetails",
46
+ "ConversationReply",
47
+ "FindContactByEmailInput",
48
+ "FindContactByEmailOutput",
49
+ "GetConversationInput",
50
+ "Message",
51
+ "ReplyToConversationInput",
52
+ "SearchConversationsInput",
53
+ "SearchConversationsOutput",
54
+ "UpdateConversationInput",
55
+ "UpdateConversationOutput",
56
+ "build_reply_html",
57
+ "new_find_contact_by_email_step",
58
+ "new_get_conversation_step",
59
+ "new_reply_to_conversation_step",
60
+ "new_search_conversations_step",
61
+ "new_update_conversation_step",
62
+ ]
@@ -0,0 +1,2 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ # ruff: noqa
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from dex_connectors.sdk import SecretString
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class Config:
12
+
13
+ max_response_bytes: int = 4194304
14
+
15
+ setattr(Config, '__module__', 'dex_connectors.front.client')
16
+
17
+
18
+ @dataclass(frozen=True, slots=True)
19
+ class Credentials:
20
+
21
+ api_token: SecretString
22
+
23
+ setattr(Credentials, '__module__', 'dex_connectors.front.client')