From 3859f83f9aefa8b03775d9e6405005c1f6a148a5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 16 Jun 2026 08:08:09 +0000 Subject: [PATCH 1/2] fix: tolerate unsupported task variants Co-authored-by: william --- src/linkup/__init__.py | 4 ++ src/linkup/_client.py | 5 ++- src/linkup/_types.py | 28 ++++++++++++- tests/unit/client_test.py | 88 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 2 deletions(-) diff --git a/src/linkup/__init__.py b/src/linkup/__init__.py index b47680f..70b6afb 100644 --- a/src/linkup/__init__.py +++ b/src/linkup/__init__.py @@ -38,6 +38,7 @@ LinkupTaskMetadata, LinkupTaskQuota, LinkupTasksPage, + LinkupUnsupportedTask, ) from ._version import __version__ @@ -78,6 +79,7 @@ TimeoutError = LinkupTimeoutError # noqa: A001 TooManyRequestsError = LinkupTooManyRequestsError UnknownError = LinkupUnknownError +UnsupportedTask = LinkupUnsupportedTask __all__ = [ "AuthenticationError", @@ -129,6 +131,7 @@ "LinkupTasksQueueLimitExceededError", "LinkupTimeoutError", "LinkupTooManyRequestsError", + "LinkupUnsupportedTask", "LinkupUnknownError", "NoResultError", "PaymentRequiredError", @@ -152,6 +155,7 @@ "TasksQueueLimitExceededError", "TimeoutError", "TooManyRequestsError", + "UnsupportedTask", "UnknownError", "__version__", ] diff --git a/src/linkup/_client.py b/src/linkup/_client.py index f42e55c..b21444e 100644 --- a/src/linkup/_client.py +++ b/src/linkup/_client.py @@ -42,6 +42,7 @@ LinkupTask, LinkupTaskInput, LinkupTasksPage, + LinkupUnsupportedTask, ) from ._version import __version__ @@ -1692,7 +1693,9 @@ def _parse_task(self, task_data: dict[str, Any]) -> LinkupTask: if task_type == "research": return self._parse_research_task(task_data) - raise ValueError(f"Unexpected task type value: '{task_type}'") + return LinkupUnsupportedTask.model_validate( + {**task_data, "rawType": task_type, "type": "unsupported"} + ) def _parse_research_tasks_page(self, response_data: dict[str, Any]) -> LinkupResearchTasksPage: return LinkupResearchTasksPage.model_validate( diff --git a/src/linkup/_types.py b/src/linkup/_types.py index 1736d47..38a7ba9 100644 --- a/src/linkup/_types.py +++ b/src/linkup/_types.py @@ -343,7 +343,33 @@ class LinkupResearchTask(_LinkupBaseModel): updated_at: str = pydantic.Field(validation_alias="updatedAt") -LinkupTask = LinkupSearchTask | LinkupFetchTask | LinkupResearchTask +class LinkupUnsupportedTask(_LinkupBaseModel): + """A task returned by the API that this SDK does not model explicitly. + + Attributes: + created_at: The task creation timestamp. + error: The task error message, if the task failed. + id: The task identifier. + input: The raw task input payload returned by the API. + output: The raw task output payload, if available. + raw_type: The original task type returned by the API. + status: The current task status. + type: The normalized task type, always "unsupported". + updated_at: The last task update timestamp. + """ + + created_at: str = pydantic.Field(validation_alias="createdAt") + error: str | None = None + id: str + input: JSONObject + output: Any | None = None + raw_type: str = pydantic.Field(validation_alias="rawType") + status: Literal["pending", "processing", "completed", "failed"] + type: Literal["unsupported"] + updated_at: str = pydantic.Field(validation_alias="updatedAt") + + +LinkupTask = LinkupSearchTask | LinkupFetchTask | LinkupResearchTask | LinkupUnsupportedTask class LinkupResearchTasksPage(_LinkupBaseModel): diff --git a/tests/unit/client_test.py b/tests/unit/client_test.py index 50ec541..7b1d73a 100644 --- a/tests/unit/client_test.py +++ b/tests/unit/client_test.py @@ -1492,6 +1492,62 @@ def test_list_tasks(mocker: MockerFixture, client: linkup.Client) -> None: assert tasks_page.quota.in_flight == 1 +def test_list_tasks_preserves_unsupported_task_variants( + mocker: MockerFixture, client: linkup.Client +) -> None: + mocker.patch( + "httpx.Client.request", + return_value=Response( + status_code=200, + content=b""" + { + "data": [ + { + "createdAt": "2026-05-18T00:00:00.000Z", + "error": null, + "id": "extract-task-1", + "input": { + "q": "companies founded in paris", + "schema": { + "type": "object" + } + }, + "output": { + "rows": [ + { + "company": "Linkup" + } + ], + "rowsReturned": 1 + }, + "status": "completed", + "type": "extract", + "updatedAt": "2026-05-18T00:00:00.000Z" + } + ], + "metadata": { + "page": 1, + "pageSize": 10, + "total": 1, + "totalPages": 1 + }, + "quota": { + "inFlight": 1, + "limit": 100 + } + } + """, + ), + ) + + tasks_page = client.list_tasks() + + assert isinstance(tasks_page.data[0], linkup.UnsupportedTask) + assert tasks_page.data[0].type == "unsupported" + assert tasks_page.data[0].raw_type == "extract" + assert tasks_page.data[0].output == {"rows": [{"company": "Linkup"}], "rowsReturned": 1} + + def test_get_task_structured_search_output_keeps_search_results_shape_raw( mocker: MockerFixture, client: linkup.Client ) -> None: @@ -1567,6 +1623,38 @@ def test_get_task_structured_search_output_raw( assert task.output == {"summary": "done"} +def test_get_task_preserves_unsupported_task_variant( + mocker: MockerFixture, client: linkup.Client +) -> None: + mocker.patch( + "httpx.Client.request", + return_value=Response( + status_code=200, + content=b""" + { + "createdAt": "2026-05-18T00:00:00.000Z", + "error": null, + "id": "extract-task-2", + "input": { + "q": "companies founded in paris" + }, + "output": null, + "status": "processing", + "type": "extract", + "updatedAt": "2026-05-18T00:00:00.000Z" + } + """, + ), + ) + + task = client.get_task("extract-task-2") + + assert isinstance(task, linkup.UnsupportedTask) + assert task.type == "unsupported" + assert task.raw_type == "extract" + assert task.input == {"q": "companies founded in paris"} + + def test_list_tasks_with_multiple_filters(mocker: MockerFixture, client: linkup.Client) -> None: request_mock = mocker.patch( "httpx.Client.request", From 15230f50093549468aec1cbd95badef8a4567e58 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 16 Jun 2026 08:10:06 +0000 Subject: [PATCH 2/2] chore: apply lint fixes Co-authored-by: william --- src/linkup/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linkup/__init__.py b/src/linkup/__init__.py index 70b6afb..c572c31 100644 --- a/src/linkup/__init__.py +++ b/src/linkup/__init__.py @@ -131,8 +131,8 @@ "LinkupTasksQueueLimitExceededError", "LinkupTimeoutError", "LinkupTooManyRequestsError", - "LinkupUnsupportedTask", "LinkupUnknownError", + "LinkupUnsupportedTask", "NoResultError", "PaymentRequiredError", "ResearchTask", @@ -155,7 +155,7 @@ "TasksQueueLimitExceededError", "TimeoutError", "TooManyRequestsError", - "UnsupportedTask", "UnknownError", + "UnsupportedTask", "__version__", ]