dex-connector-algolia 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 (25) hide show
  1. dex_connector_algolia-0.1.0/.gitignore +6 -0
  2. dex_connector_algolia-0.1.0/PKG-INFO +53 -0
  3. dex_connector_algolia-0.1.0/README.md +42 -0
  4. dex_connector_algolia-0.1.0/examples/index_search/__init__.py +3 -0
  5. dex_connector_algolia-0.1.0/examples/index_search/flow.py +88 -0
  6. dex_connector_algolia-0.1.0/examples/index_search/worker.py +51 -0
  7. dex_connector_algolia-0.1.0/pyproject.toml +37 -0
  8. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/__init__.py +51 -0
  9. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/_generated/__init__.py +2 -0
  10. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/_generated/config.py +26 -0
  11. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/_generated/connection.py +134 -0
  12. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/_generated/definitions.py +55 -0
  13. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/_generated/mock.py +107 -0
  14. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/_generated/models.py +82 -0
  15. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/_generated/steps.py +131 -0
  16. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/algoliamock/__init__.py +54 -0
  17. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/client.py +387 -0
  18. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/connection.py +49 -0
  19. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/dex-connector.json +77 -0
  20. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/models.py +34 -0
  21. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/py.typed +0 -0
  22. dex_connector_algolia-0.1.0/src/dex_connectors/algolia/steps.py +38 -0
  23. dex_connector_algolia-0.1.0/tests/test_manifest_contract.py +15 -0
  24. dex_connector_algolia-0.1.0/tests/test_operations.py +229 -0
  25. dex_connector_algolia-0.1.0/uv.lock +932 -0
@@ -0,0 +1,6 @@
1
+ /.dex-local/dexcli
2
+ /.dex-local/dex-server-zd
3
+ /.dex-local/data/
4
+ /.dex-local/cli.log
5
+ /.dex-local/visualize.json
6
+ /.dex-local/visualize.err
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.5
2
+ Name: dex-connector-algolia
3
+ Version: 0.1.0
4
+ Summary: Algolia connector for Dex Flows
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: dex-connectors-sdk==0.1.0
8
+ Requires-Dist: dex-python-sdk>=1.28.0
9
+ Requires-Dist: httpx>=0.27
10
+ Description-Content-Type: text/markdown
11
+
12
+ # Algolia connector for Python
13
+
14
+ Install with `pip install dex-connector-algolia==0.1.0`. The package requires
15
+ `dex-connectors-sdk==0.1.0` and `dex-python-sdk>=1.28.0`, and implements the
16
+ manifest operations `search` (Query) and `saveObject` (Mutation) with the same
17
+ branches, defaults, and failure classification as the Go module.
18
+
19
+ ## Operations
20
+
21
+ | Operation | Factory | Happy branch | Optional branches |
22
+ | --- | --- | --- | --- |
23
+ | `search` | `new_search_step` | `searched` | `providerRejected`, `invalidResponse`, `defect` |
24
+ | `saveObject` | `new_save_object_step` | `saved` | `providerRejected`, `invalidResponse`, `defect` |
25
+
26
+ `search` returns Algolia's hit count and each hit's `objectID` and optional
27
+ `name`; `hitsPerPage` ranges from 0 (Algolia's default) through 1000.
28
+ `saveObject` replaces the whole record at its stable `objectID`, so a retry
29
+ writes the same absolute value; only a response echoing the `objectID` with a
30
+ positive `taskID` selects `saved`. Indexing completes asynchronously.
31
+
32
+ Both operations retry on 408, 409, 429, 5xx, and transport failures; 401 and
33
+ 403 select `providerRejected` with authentication and authorization kinds.
34
+ With the default base URL, each call targets `https://<application ID>.algolia.net/1`.
35
+
36
+ ## Authorization
37
+
38
+ Create a Dex Web connection named `algolia` as described in the
39
+ [connector README](../README.md#authorization). The API key travels only in
40
+ the `x-algolia-api-key` header and is resolved for every attempt.
41
+
42
+ ## Example
43
+
44
+ [`examples/index_search`](examples/index_search/flow.py) searches one index
45
+ for up to ten hits. Run the Worker with
46
+ `uv run python -m examples.index_search.worker` after `dexcli dev` has loaded
47
+ a project containing the `algolia` connection.
48
+
49
+ ## Tests
50
+
51
+ `algoliamock.Mock` scripts both operations and defaults to the manifest
52
+ answers. Run `uv run pytest tests` for provider parity and manifest contract
53
+ checks.
@@ -0,0 +1,42 @@
1
+ # Algolia connector for Python
2
+
3
+ Install with `pip install dex-connector-algolia==0.1.0`. The package requires
4
+ `dex-connectors-sdk==0.1.0` and `dex-python-sdk>=1.28.0`, and implements the
5
+ manifest operations `search` (Query) and `saveObject` (Mutation) with the same
6
+ branches, defaults, and failure classification as the Go module.
7
+
8
+ ## Operations
9
+
10
+ | Operation | Factory | Happy branch | Optional branches |
11
+ | --- | --- | --- | --- |
12
+ | `search` | `new_search_step` | `searched` | `providerRejected`, `invalidResponse`, `defect` |
13
+ | `saveObject` | `new_save_object_step` | `saved` | `providerRejected`, `invalidResponse`, `defect` |
14
+
15
+ `search` returns Algolia's hit count and each hit's `objectID` and optional
16
+ `name`; `hitsPerPage` ranges from 0 (Algolia's default) through 1000.
17
+ `saveObject` replaces the whole record at its stable `objectID`, so a retry
18
+ writes the same absolute value; only a response echoing the `objectID` with a
19
+ positive `taskID` selects `saved`. Indexing completes asynchronously.
20
+
21
+ Both operations retry on 408, 409, 429, 5xx, and transport failures; 401 and
22
+ 403 select `providerRejected` with authentication and authorization kinds.
23
+ With the default base URL, each call targets `https://<application ID>.algolia.net/1`.
24
+
25
+ ## Authorization
26
+
27
+ Create a Dex Web connection named `algolia` as described in the
28
+ [connector README](../README.md#authorization). The API key travels only in
29
+ the `x-algolia-api-key` header and is resolved for every attempt.
30
+
31
+ ## Example
32
+
33
+ [`examples/index_search`](examples/index_search/flow.py) searches one index
34
+ for up to ten hits. Run the Worker with
35
+ `uv run python -m examples.index_search.worker` after `dexcli dev` has loaded
36
+ a project containing the `algolia` connection.
37
+
38
+ ## Tests
39
+
40
+ `algoliamock.Mock` scripts both operations and defaults to the manifest
41
+ answers. Run `uv run pytest tests` for provider parity and manifest contract
42
+ checks.
@@ -0,0 +1,3 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ """Algolia index search example Flow."""
@@ -0,0 +1,88 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ """Runnable Algolia Flow that searches one index."""
4
+
5
+ from dataclasses import dataclass, field, replace
6
+
7
+ import dex
8
+ from dex_connectors.sdk import QueryResult
9
+
10
+ from dex_connectors.algolia import Connection, SearchInput, SearchPage, new_search_step
11
+
12
+ FLOW_TYPE = "AlgoliaIndexSearch"
13
+ CONNECTION_NAME = "algolia"
14
+ _SEARCH_STEP_TYPE = "SearchAlgoliaIndex"
15
+ _HITS_PER_PAGE = 10
16
+ _OUTCOME = dex.Attribute("algolia-index-search-outcome", SearchPage)
17
+
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class IndexSearchRequest:
21
+ """An existing index in the connected application and a search phrase; blank matches all records."""
22
+
23
+ index_name: str = field(metadata={"dex_json_name": "indexName"})
24
+ query: str = ""
25
+
26
+
27
+ def map_to_search_input(request: IndexSearchRequest) -> SearchInput:
28
+ """Search the validated index for one bounded page."""
29
+ return SearchInput(index_name=request.index_name, query=request.query, hits_per_page=_HITS_PER_PAGE)
30
+
31
+
32
+ # dex:group group-id:index-search group-label:"Index search"
33
+ # dex:explanation text:"Validate the index name before searching Algolia."
34
+ class _Start(dex.Step[IndexSearchRequest]):
35
+ def get_step_type(self) -> str:
36
+ return "StartAlgoliaIndexSearch"
37
+
38
+ def execute(self, context: dex.Context, input: IndexSearchRequest) -> dex.StepDecision:
39
+ request = replace(input, index_name=input.index_name.strip())
40
+ if not request.index_name:
41
+ return dex.force_fail("indexName is required")
42
+ return dex.go_to(dex.StepRef(_SEARCH_STEP_TYPE), request)
43
+
44
+
45
+ # dex:group group-id:index-search group-label:"Index search"
46
+ # dex:explanation text:"Complete with the matching record identities."
47
+ class _Searched(dex.Step[QueryResult[SearchPage]]):
48
+ def get_step_type(self) -> str:
49
+ return "AlgoliaHitsFound"
50
+
51
+ def execute(self, context: dex.Context, input: QueryResult[SearchPage]) -> dex.StepDecision:
52
+ _OUTCOME.set(context, input.value)
53
+ return dex.graceful_complete(input.value)
54
+
55
+
56
+ class IndexSearchFlow(dex.Flow[IndexSearchRequest]):
57
+ """Search one Algolia index through the static algolia connection."""
58
+
59
+ def __init__(self, connection: Connection) -> None:
60
+ if connection.reference.name != CONNECTION_NAME:
61
+ raise ValueError("index search requires the static algolia connection")
62
+ self._start = _Start()
63
+ self._searched = _Searched()
64
+ self._search = new_search_step(
65
+ step_type=_SEARCH_STEP_TYPE,
66
+ step_input_type=IndexSearchRequest,
67
+ annotations={
68
+ "groupId": "index-search",
69
+ "groupLabel": "Index search",
70
+ "explanation": "Search the connected Algolia index for matching records.",
71
+ },
72
+ connection=connection,
73
+ connection_name=CONNECTION_NAME,
74
+ map_to_operation_input=map_to_search_input,
75
+ searched=self._searched,
76
+ )
77
+
78
+ def get_flow_type(self) -> str:
79
+ """Return the stable Flow identity used in Dex Web."""
80
+ return FLOW_TYPE
81
+
82
+ def get_steps(self) -> dex.StepList[IndexSearchRequest]:
83
+ """Register the start, search, and completion Steps."""
84
+ return dex.StepList.start_step(self._start).other_steps(self._search, self._searched)
85
+
86
+ def get_persistence_schema(self) -> dex.PersistenceSchema:
87
+ """Persist the hit page."""
88
+ return dex.PersistenceSchema.of(_OUTCOME)
@@ -0,0 +1,51 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ """Worker entrypoint for the Algolia index search 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.algolia import new_project_connection
14
+
15
+ from .flow import CONNECTION_NAME, IndexSearchFlow
16
+
17
+
18
+ async def run_worker() -> None:
19
+ """Load the named Dex Web connection and search indexes."""
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([IndexSearchFlow(connection)], allow_async_handlers=True)
26
+ cache_directory = os.environ.get(
27
+ "DEX_BLOB_CACHE_DIR", str(Path(tempfile.gettempdir()) / "dex-algolia-index-search-blobs")
28
+ )
29
+ cache = dex.open_blob_cache(dex.BlobCacheConfig(cache_directory, 1 << 30))
30
+ worker = dex.AsyncWorker(
31
+ registry,
32
+ cache,
33
+ dex.WorkerOptions(server_address=os.environ.get("DEX_FLOW_SERVICE_ADDRESS", "localhost:8801")),
34
+ )
35
+ async with worker:
36
+ await worker.start()
37
+ finally:
38
+ if connection is not None:
39
+ await connection.aclose()
40
+ if cache is not None:
41
+ cache.close()
42
+ await project.close()
43
+
44
+
45
+ def main() -> None:
46
+ """Run until the Worker stops or the process receives an interrupt."""
47
+ asyncio.run(run_worker())
48
+
49
+
50
+ if __name__ == "__main__":
51
+ main()
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "dex-connector-algolia"
7
+ version = "0.1.0"
8
+ description = "Algolia connector for Dex Flows"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ dependencies = ["dex-connectors-sdk==0.1.0", "dex-python-sdk>=1.28.0", "httpx>=0.27"]
13
+
14
+ [dependency-groups]
15
+ dev = ["pytest>=8", "pytest-asyncio>=0.24", "mypy>=1.13", "ruff>=0.7", "pyyaml>=6", "types-PyYAML>=6"]
16
+
17
+ [tool.hatch.build.targets.wheel]
18
+ packages = ["src/dex_connectors"]
19
+
20
+ [tool.pytest.ini_options]
21
+ testpaths = ["tests"]
22
+ pythonpath = [".", "src"]
23
+ asyncio_mode = "auto"
24
+ asyncio_default_fixture_loop_scope = "function"
25
+
26
+ [tool.mypy]
27
+ python_version = "3.11"
28
+ strict = true
29
+ mypy_path = "src"
30
+ explicit_package_bases = true
31
+
32
+ [tool.ruff]
33
+ target-version = "py311"
34
+ line-length = 110
35
+
36
+ [tool.ruff.lint]
37
+ select = ["E", "F", "I", "UP", "B"]
@@ -0,0 +1,51 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ """Algolia index search and record replacement operations for Dex connector Steps."""
4
+
5
+ from .client import (
6
+ CONNECTOR_ID,
7
+ DEFECT,
8
+ INVALID_RESPONSE,
9
+ PROVIDER_REJECTED,
10
+ PUBLIC_HOST_TEMPLATE,
11
+ SAVE_OBJECT_DEFINITION,
12
+ SAVED,
13
+ SEARCH_DEFINITION,
14
+ SEARCHED,
15
+ Client,
16
+ Config,
17
+ Credentials,
18
+ SaveObjectOperation,
19
+ SearchOperation,
20
+ )
21
+ from .connection import Connection, new_connection, new_connection_with_operations, new_project_connection
22
+ from .models import SavedObject, SaveObjectInput, SearchHit, SearchInput, SearchPage
23
+ from .steps import new_save_object_step, new_search_step
24
+
25
+ __all__ = [
26
+ "Client",
27
+ "Config",
28
+ "Connection",
29
+ "Credentials",
30
+ "SaveObjectInput",
31
+ "SaveObjectOperation",
32
+ "SavedObject",
33
+ "SearchHit",
34
+ "SearchInput",
35
+ "SearchOperation",
36
+ "SearchPage",
37
+ "new_connection",
38
+ "new_connection_with_operations",
39
+ "new_project_connection",
40
+ "new_save_object_step",
41
+ "new_search_step",
42
+ "CONNECTOR_ID",
43
+ "DEFECT",
44
+ "INVALID_RESPONSE",
45
+ "PROVIDER_REJECTED",
46
+ "PUBLIC_HOST_TEMPLATE",
47
+ "SAVE_OBJECT_DEFINITION",
48
+ "SAVED",
49
+ "SEARCH_DEFINITION",
50
+ "SEARCHED",
51
+ ]
@@ -0,0 +1,2 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
@@ -0,0 +1,26 @@
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
+ base_url: str = "https://algolia.net/1"
14
+ timeout_seconds: float = 15
15
+ max_response_bytes: int = 2097152
16
+
17
+ setattr(Config, '__module__', 'dex_connectors.algolia.client')
18
+
19
+
20
+ @dataclass(frozen=True, slots=True)
21
+ class Credentials:
22
+
23
+ application_id: str
24
+ api_key: SecretString
25
+
26
+ setattr(Credentials, '__module__', 'dex_connectors.algolia.client')
@@ -0,0 +1,134 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ # ruff: noqa
4
+
5
+ from typing import Any
6
+
7
+ import httpx
8
+ from dex_connectors.sdk import ConnectionRef, Mutation, Query, Call, SecretString
9
+ from dex_connectors.sdk.projectconfig import ConnectionKey, LoadedProject
10
+
11
+ from ..client import Client, Config, Credentials
12
+ from .definitions import CONNECTOR_ID, SAVE_OBJECT_DEFINITION, SEARCH_DEFINITION
13
+ from ..models import SaveObjectInput, SavedObject, SearchInput, SearchPage
14
+
15
+ class Connection:
16
+
17
+ __slots__ = ('_save_object', '_search', '_client', 'reference')
18
+
19
+ def __init__(self,
20
+ reference: ConnectionRef,
21
+ save_object: Mutation[SaveObjectInput, SavedObject],
22
+ search: Query[SearchInput, SearchPage],
23
+ client: Client | None = None,
24
+ ) -> None:
25
+ reference.validate()
26
+ if reference.provider != CONNECTOR_ID or save_object is None or search is None:
27
+ raise ValueError("Algolia connection provider and operations are required")
28
+ if save_object.definition().operation != SAVE_OBJECT_DEFINITION.operation:
29
+ raise ValueError("Algolia connection operation identity differs")
30
+ if search.definition().operation != SEARCH_DEFINITION.operation:
31
+ raise ValueError("Algolia connection operation identity differs")
32
+ self.reference = reference
33
+ self._save_object = save_object
34
+ self._search = search
35
+ self._client = client
36
+
37
+ def save_object(self) -> Mutation[SaveObjectInput, SavedObject]:
38
+ return self._save_object
39
+
40
+ def search(self) -> Query[SearchInput, SearchPage]:
41
+ return self._search
42
+
43
+ async def aclose(self) -> None:
44
+ if self._client is not None:
45
+ await self._client.aclose()
46
+
47
+ def __repr__(self) -> str:
48
+ return "algolia.Connection([REDACTED])"
49
+
50
+ def __reduce__(self) -> tuple[object, ...]:
51
+ raise TypeError("Algolia connections cannot be serialized")
52
+
53
+ setattr(Connection, '__module__', 'dex_connectors.algolia.connection')
54
+
55
+
56
+ def new_connection(client: Client, reference: ConnectionRef) -> Connection:
57
+ if client is None:
58
+ raise ValueError("Algolia client is required")
59
+ return Connection(reference, client.save_object(), client.search(), client)
60
+
61
+ setattr(new_connection, '__module__', 'dex_connectors.algolia.connection')
62
+
63
+
64
+ def new_connection_with_operations(
65
+ save_object: Mutation[SaveObjectInput, SavedObject],
66
+ search: Query[SearchInput, SearchPage],
67
+ reference: ConnectionRef,
68
+ ) -> Connection:
69
+ return Connection(reference, save_object, search)
70
+
71
+ setattr(new_connection_with_operations, '__module__', 'dex_connectors.algolia.connection')
72
+
73
+
74
+ class _ProjectCredentialSource:
75
+
76
+ def __init__(self, project: LoadedProject, key: ConnectionKey) -> None:
77
+ self.project = project
78
+ self.key = key
79
+
80
+ async def resolve(self, call: Call) -> Credentials:
81
+ material = await self.project.resolve_credential(self.key, str(call.id))
82
+ application_id = material.get("application_id")
83
+ if not isinstance(application_id, str) or not application_id:
84
+ raise ValueError("Algolia credential application_id is required")
85
+ api_key = material.get("api_key")
86
+ if not isinstance(api_key, str) or not api_key:
87
+ raise ValueError("Algolia credential api_key is required")
88
+ return Credentials(application_id, SecretString(api_key))
89
+
90
+
91
+ def new_project_connection(
92
+ project: LoadedProject, connection_name: str, http_client: httpx.AsyncClient | None = None
93
+ ) -> Connection:
94
+ if project is None:
95
+ raise ValueError("Algolia connection requires loaded project configuration")
96
+ reference = ConnectionRef(CONNECTOR_ID, connection_name)
97
+ reference.validate()
98
+ key = ConnectionKey(CONNECTOR_ID, connection_name)
99
+ matching = [
100
+ entry
101
+ for entry in project.configuration.connections
102
+ if entry.connector_id == CONNECTOR_ID and entry.connection_name == connection_name
103
+ ]
104
+ if len(matching) != 1:
105
+ raise ValueError("Algolia project connection is missing or duplicated")
106
+ if matching[0].provider != CONNECTOR_ID:
107
+ raise ValueError("Algolia project connection provider differs")
108
+ config = _decode_config(matching[0].configuration)
109
+ client = Client(config, _ProjectCredentialSource(project, key), http_client)
110
+ return new_connection(client, reference)
111
+
112
+ setattr(new_project_connection, '__module__', 'dex_connectors.algolia.connection')
113
+
114
+
115
+ def _decode_config(value: dict[str, Any]) -> Config:
116
+ if set(value) - {"baseUrl", "timeout", "maxResponseBytes"}:
117
+ raise ValueError("Algolia configuration has unknown fields")
118
+ config_arguments: dict[str, Any] = {}
119
+ if "baseUrl" in value:
120
+ base_url = value["baseUrl"]
121
+ if not (isinstance(base_url, str)):
122
+ raise ValueError("Algolia configuration baseUrl has an invalid type")
123
+ config_arguments['base_url'] = base_url
124
+ if "timeout" in value:
125
+ timeout = value["timeout"]
126
+ if not (type(timeout) is int):
127
+ raise ValueError("Algolia configuration timeout has an invalid type")
128
+ config_arguments['timeout_seconds'] = timeout / 1_000_000_000
129
+ if "maxResponseBytes" in value:
130
+ max_response_bytes = value["maxResponseBytes"]
131
+ if not (type(max_response_bytes) is int):
132
+ raise ValueError("Algolia configuration maxResponseBytes has an invalid type")
133
+ config_arguments['max_response_bytes'] = max_response_bytes
134
+ return Config(**config_arguments)
@@ -0,0 +1,55 @@
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 = "algolia"
18
+ PROVIDER = "algolia"
19
+ SAVE_OBJECT_OPERATION_ID = "saveObject"
20
+ SEARCH_OPERATION_ID = "search"
21
+ DEFECT = "defect"
22
+ INVALID_RESPONSE = "invalidResponse"
23
+ PROVIDER_REJECTED = "providerRejected"
24
+ SAVED = "saved"
25
+ SEARCHED = "searched"
26
+
27
+ SAVE_OBJECT_DEFINITION = MutationDefinition(
28
+ OperationRef(CONNECTOR_ID, SAVE_OBJECT_OPERATION_ID),
29
+ (
30
+ BranchDefinition(SAVED, "Algolia accepted the replacement task.", False),
31
+ BranchDefinition(PROVIDER_REJECTED, "Algolia rejected the record or key.", True),
32
+ BranchDefinition(INVALID_RESPONSE, "Algolia returned malformed or oversized data.", True),
33
+ BranchDefinition(DEFECT, "Local input or credentials are invalid.", True),
34
+ ),
35
+ StepDefaults(
36
+ timedelta(seconds=30),
37
+ 2,
38
+ execute_retry=default_connector_step_execute_retry_policy(),
39
+ ),
40
+ )
41
+
42
+ SEARCH_DEFINITION = QueryDefinition(
43
+ OperationRef(CONNECTOR_ID, SEARCH_OPERATION_ID),
44
+ (
45
+ BranchDefinition(SEARCHED, "Algolia returned a hit page, possibly empty.", False),
46
+ BranchDefinition(PROVIDER_REJECTED, "Algolia rejected the search or key.", True),
47
+ BranchDefinition(INVALID_RESPONSE, "Algolia returned malformed or oversized data.", True),
48
+ BranchDefinition(DEFECT, "Local input or credentials are invalid.", True),
49
+ ),
50
+ StepDefaults(
51
+ timedelta(seconds=30),
52
+ 2,
53
+ execute_retry=default_connector_step_execute_retry_policy(),
54
+ ),
55
+ )
@@ -0,0 +1,107 @@
1
+ # Copyright (c) 2026 Super Durable
2
+ # SPDX-License-Identifier: MIT
3
+ # ruff: noqa
4
+
5
+ from datetime import datetime
6
+
7
+ from dex_connectors.sdk import ConnectionRef, Failure, FailureKind
8
+ from dex_connectors.sdk.connectormock import Case, Mutation, Query
9
+
10
+ from ..connection import Connection, new_connection_with_operations
11
+ from ..models import SaveObjectInput, SavedObject, SearchHit, SearchInput, SearchPage
12
+ from .definitions import CONNECTOR_ID, DEFECT, INVALID_RESPONSE, PROVIDER_REJECTED, SAVED, SAVE_OBJECT_DEFINITION, SEARCHED, SEARCH_DEFINITION
13
+
14
+ def save_object_saved(value: SavedObject, failure: Failure | None = None) -> Case[SavedObject]:
15
+ return Case.branch_result(SAVED, value, failure)
16
+
17
+ setattr(save_object_saved, '__module__', "dex_connectors.algolia.algoliamock")
18
+
19
+ def save_object_provider_rejected(value: SavedObject, failure: Failure | None = None) -> Case[SavedObject]:
20
+ return Case.branch_result(PROVIDER_REJECTED, value, failure)
21
+
22
+ setattr(save_object_provider_rejected, '__module__', "dex_connectors.algolia.algoliamock")
23
+
24
+ def save_object_invalid_response(value: SavedObject, failure: Failure | None = None) -> Case[SavedObject]:
25
+ return Case.branch_result(INVALID_RESPONSE, value, failure)
26
+
27
+ setattr(save_object_invalid_response, '__module__', "dex_connectors.algolia.algoliamock")
28
+
29
+ def save_object_defect(value: SavedObject, failure: Failure | None = None) -> Case[SavedObject]:
30
+ return Case.branch_result(DEFECT, value, failure)
31
+
32
+ setattr(save_object_defect, '__module__', "dex_connectors.algolia.algoliamock")
33
+
34
+ def save_object_retry(failure: Failure, after_seconds: float = 0) -> Case[SavedObject]:
35
+ return Case.retry(failure, after_seconds)
36
+
37
+ setattr(save_object_retry, '__module__', "dex_connectors.algolia.algoliamock")
38
+
39
+ def save_object_saved_default_object() -> Case[SavedObject]:
40
+ return Case.branch_result(SAVED, SavedObject(object_id='product-1', task_id=1514562690001))
41
+
42
+ setattr(save_object_saved_default_object, '__module__', "dex_connectors.algolia.algoliamock")
43
+
44
+ def search_searched(value: SearchPage, failure: Failure | None = None) -> Case[SearchPage]:
45
+ return Case.branch_result(SEARCHED, value, failure)
46
+
47
+ setattr(search_searched, '__module__', "dex_connectors.algolia.algoliamock")
48
+
49
+ def search_provider_rejected(value: SearchPage, failure: Failure | None = None) -> Case[SearchPage]:
50
+ return Case.branch_result(PROVIDER_REJECTED, value, failure)
51
+
52
+ setattr(search_provider_rejected, '__module__', "dex_connectors.algolia.algoliamock")
53
+
54
+ def search_invalid_response(value: SearchPage, failure: Failure | None = None) -> Case[SearchPage]:
55
+ return Case.branch_result(INVALID_RESPONSE, value, failure)
56
+
57
+ setattr(search_invalid_response, '__module__', "dex_connectors.algolia.algoliamock")
58
+
59
+ def search_defect(value: SearchPage, failure: Failure | None = None) -> Case[SearchPage]:
60
+ return Case.branch_result(DEFECT, value, failure)
61
+
62
+ setattr(search_defect, '__module__', "dex_connectors.algolia.algoliamock")
63
+
64
+ def search_retry(failure: Failure, after_seconds: float = 0) -> Case[SearchPage]:
65
+ return Case.retry(failure, after_seconds)
66
+
67
+ setattr(search_retry, '__module__', "dex_connectors.algolia.algoliamock")
68
+
69
+ def search_searched_default_index() -> Case[SearchPage]:
70
+ return Case.branch_result(SEARCHED, SearchPage(nb_hits=1, hits=(SearchHit(object_id='product-1', name='Meridian notebook'),)))
71
+
72
+ setattr(search_searched_default_index, '__module__', "dex_connectors.algolia.algoliamock")
73
+
74
+
75
+ class Mock:
76
+
77
+ def __init__(self, connection_name: str = "algolia") -> None:
78
+ self.save_object_script: Mutation[SaveObjectInput, SavedObject] = Mutation(SAVE_OBJECT_DEFINITION)
79
+ self.save_object_script.default(save_object_saved_default_object())
80
+ self.search_script: Query[SearchInput, SearchPage] = Query(SEARCH_DEFINITION)
81
+ self.search_script.default(search_searched_default_index())
82
+ self._connection = new_connection_with_operations(
83
+ save_object=self.save_object_script,
84
+ search=self.search_script,
85
+ reference=ConnectionRef(CONNECTOR_ID, connection_name),
86
+ )
87
+
88
+ def connection(self) -> Connection:
89
+ return self._connection
90
+
91
+ setattr(Mock, '__module__', "dex_connectors.algolia.algoliamock")
92
+
93
+ __all__ = [
94
+ "Mock",
95
+ "save_object_saved",
96
+ "save_object_provider_rejected",
97
+ "save_object_invalid_response",
98
+ "save_object_defect",
99
+ "save_object_retry",
100
+ "save_object_saved_default_object",
101
+ "search_searched",
102
+ "search_provider_rejected",
103
+ "search_invalid_response",
104
+ "search_defect",
105
+ "search_retry",
106
+ "search_searched_default_index",
107
+ ]