Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/linkup/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from ._client import LinkupClient
from ._errors import (
LinkupAuthenticationError,
LinkupBudgetLimitExceededError,
LinkupFailedFetchError,
LinkupFetchResponseTooLargeError,
LinkupFetchUnsupportedContentTypeError,
LinkupFetchUrlIsFileError,
LinkupInsufficientCreditError,
LinkupInvalidRequestError,
LinkupNoResultError,
LinkupPaymentRequiredError,
LinkupTaskNotFoundError,
LinkupTasksQueueLimitExceededError,
LinkupTimeoutError,
LinkupTooManyRequestsError,
LinkupUnknownError,
Expand Down Expand Up @@ -38,11 +42,13 @@

# Aliases to allow usage like `import linkup` and `client = linkup.Client(...)`
AuthenticationError = LinkupAuthenticationError
BudgetLimitExceededError = LinkupBudgetLimitExceededError
Client = LinkupClient
FailedFetchError = LinkupFailedFetchError
FetchImageExtraction = LinkupFetchImageExtraction
FetchResponse = LinkupFetchResponse
FetchResponseTooLargeError = LinkupFetchResponseTooLargeError
FetchUnsupportedContentTypeError = LinkupFetchUnsupportedContentTypeError
FetchTask = LinkupFetchTask
FetchTaskInput = LinkupFetchTaskInput
FetchUrlIsFileError = LinkupFetchUrlIsFileError
Expand All @@ -62,34 +68,40 @@
Source = LinkupSource
SourcedAnswer = LinkupSourcedAnswer
Task = LinkupTask
TaskNotFoundError = LinkupTaskNotFoundError
TaskInput = LinkupTaskInput
TaskMetadata = LinkupTaskMetadata
TaskQuota = LinkupTaskQuota
TasksPage = LinkupTasksPage
TasksQueueLimitExceededError = LinkupTasksQueueLimitExceededError
TimeoutError = LinkupTimeoutError # noqa: A001
TooManyRequestsError = LinkupTooManyRequestsError
UnknownError = LinkupUnknownError

__all__ = [
"AuthenticationError",
"BudgetLimitExceededError",
"Client",
"FailedFetchError",
"FetchImageExtraction",
"FetchResponse",
"FetchResponseTooLargeError",
"FetchTask",
"FetchTaskInput",
"FetchUnsupportedContentTypeError",
"FetchUrlIsFileError",
"InsufficientCreditError",
"InvalidRequestError",
"LinkupAuthenticationError",
"LinkupBudgetLimitExceededError",
"LinkupClient",
"LinkupFailedFetchError",
"LinkupFetchImageExtraction",
"LinkupFetchResponse",
"LinkupFetchResponseTooLargeError",
"LinkupFetchTask",
"LinkupFetchTaskInput",
"LinkupFetchUnsupportedContentTypeError",
"LinkupFetchUrlIsFileError",
"LinkupInsufficientCreditError",
"LinkupInvalidRequestError",
Expand All @@ -109,8 +121,10 @@
"LinkupTask",
"LinkupTaskInput",
"LinkupTaskMetadata",
"LinkupTaskNotFoundError",
"LinkupTaskQuota",
"LinkupTasksPage",
"LinkupTasksQueueLimitExceededError",
"LinkupTimeoutError",
"LinkupTooManyRequestsError",
"LinkupUnknownError",
Expand All @@ -130,8 +144,10 @@
"Task",
"TaskInput",
"TaskMetadata",
"TaskNotFoundError",
"TaskQuota",
"TasksPage",
"TasksQueueLimitExceededError",
"TimeoutError",
"TooManyRequestsError",
"UnknownError",
Expand Down
83 changes: 67 additions & 16 deletions src/linkup/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@

from ._errors import (
LinkupAuthenticationError,
LinkupBudgetLimitExceededError,
LinkupFailedFetchError,
LinkupFetchResponseTooLargeError,
LinkupFetchUnsupportedContentTypeError,
LinkupFetchUrlIsFileError,
LinkupInsufficientCreditError,
LinkupInvalidRequestError,
LinkupNoResultError,
LinkupPaymentRequiredError,
LinkupTaskNotFoundError,
LinkupTasksQueueLimitExceededError,
LinkupTimeoutError,
LinkupTooManyRequestsError,
LinkupUnknownError,
Expand Down Expand Up @@ -154,6 +158,7 @@ def search(
JSON schema when output_type is "structured".
LinkupAuthenticationError: If the Linkup API key is invalid.
LinkupInsufficientCreditError: If you have run out of credit.
LinkupBudgetLimitExceededError: If the API key has reached its configured budget limit.
LinkupNoResultError: If the search query did not yield any result.
LinkupTimeoutError: If the request times out.
"""
Expand Down Expand Up @@ -253,6 +258,7 @@ async def async_search(
JSON schema when output_type is "structured".
LinkupAuthenticationError: If the Linkup API key is invalid.
LinkupInsufficientCreditError: If you have run out of credit.
LinkupBudgetLimitExceededError: If the API key has reached its configured budget limit.
LinkupNoResultError: If the search query did not yield any result.
LinkupTimeoutError: If the request times out.
"""
Expand Down Expand Up @@ -519,7 +525,7 @@ def get_research(self, research_id: str, timeout: float | None = None) -> Linkup
The requested research task.

Raises:
LinkupInvalidRequestError: If the research identifier is invalid or unknown.
LinkupTaskNotFoundError: If the research identifier does not match an existing task.
LinkupAuthenticationError: If the Linkup API key is invalid.
LinkupTimeoutError: If the request times out.
"""
Expand All @@ -545,7 +551,7 @@ async def async_get_research(
The requested research task.

Raises:
LinkupInvalidRequestError: If the research identifier is invalid or unknown.
LinkupTaskNotFoundError: If the research identifier does not match an existing task.
LinkupAuthenticationError: If the Linkup API key is invalid.
LinkupTimeoutError: If the request times out.
"""
Expand Down Expand Up @@ -577,6 +583,7 @@ def create_tasks(
LinkupInvalidRequestError: If the task payload is invalid.
LinkupAuthenticationError: If the Linkup API key is invalid.
LinkupInsufficientCreditError: If you have run out of credit.
LinkupTasksQueueLimitExceededError: If too many tasks are already pending or processing.
LinkupTimeoutError: If the request times out.
"""
response = self._request(
Expand Down Expand Up @@ -609,6 +616,7 @@ async def async_create_tasks(
LinkupInvalidRequestError: If the task payload is invalid.
LinkupAuthenticationError: If the Linkup API key is invalid.
LinkupInsufficientCreditError: If you have run out of credit.
LinkupTasksQueueLimitExceededError: If too many tasks are already pending or processing.
LinkupTimeoutError: If the request times out.
"""
response = await self._async_request(
Expand All @@ -627,8 +635,12 @@ def list_tasks(
page_size: int | None = None,
sort_by: Literal["createdAt", "updatedAt"] | None = None,
sort_direction: Literal["asc", "desc"] | None = None,
status: Literal["pending", "processing", "completed", "failed"] | None = None,
task_type: Literal["search", "fetch", "research"] | None = None,
status: Literal["pending", "processing", "completed", "failed"]
| list[Literal["pending", "processing", "completed", "failed"]]
| None = None,
task_type: Literal["search", "fetch", "research"]
| list[Literal["search", "fetch", "research"]]
| None = None,
timeout: float | None = None,
) -> LinkupTasksPage:
"""List tasks for the authenticated organization.
Expand All @@ -640,8 +652,8 @@ def list_tasks(
API default is used.
sort_direction: The sort direction, either "asc" or "desc". If None, the Linkup API
default is used.
status: A task status to filter by. If None, no status filter is sent.
task_type: A task type to filter by. If None, no task type filter is sent.
status: One or more task statuses to filter by. If None, no status filter is sent.
task_type: One or more task types to filter by. If None, no task type filter is sent.
timeout: The timeout for the HTTP request, in seconds. If None, the request will have
no timeout.

Expand Down Expand Up @@ -676,8 +688,12 @@ async def async_list_tasks(
page_size: int | None = None,
sort_by: Literal["createdAt", "updatedAt"] | None = None,
sort_direction: Literal["asc", "desc"] | None = None,
status: Literal["pending", "processing", "completed", "failed"] | None = None,
task_type: Literal["search", "fetch", "research"] | None = None,
status: Literal["pending", "processing", "completed", "failed"]
| list[Literal["pending", "processing", "completed", "failed"]]
| None = None,
task_type: Literal["search", "fetch", "research"]
| list[Literal["search", "fetch", "research"]]
| None = None,
timeout: float | None = None,
) -> LinkupTasksPage:
"""Asynchronously list tasks for the authenticated organization.
Expand All @@ -689,8 +705,8 @@ async def async_list_tasks(
API default is used.
sort_direction: The sort direction, either "asc" or "desc". If None, the Linkup API
default is used.
status: A task status to filter by. If None, no status filter is sent.
task_type: A task type to filter by. If None, no task type filter is sent.
status: One or more task statuses to filter by. If None, no status filter is sent.
task_type: One or more task types to filter by. If None, no task type filter is sent.
timeout: The timeout for the HTTP request, in seconds. If None, the request will have
no timeout.

Expand Down Expand Up @@ -731,7 +747,7 @@ def get_task(self, task_id: str, timeout: float | None = None) -> LinkupTask:
The requested task, parsed according to its task type.

Raises:
LinkupInvalidRequestError: If the task identifier is invalid or unknown.
LinkupTaskNotFoundError: If the task identifier does not match an existing task.
LinkupAuthenticationError: If the Linkup API key is invalid.
LinkupTimeoutError: If the request times out.
"""
Expand All @@ -755,7 +771,7 @@ async def async_get_task(self, task_id: str, timeout: float | None = None) -> Li
The requested task, parsed according to its task type.

Raises:
LinkupInvalidRequestError: If the task identifier is invalid or unknown.
LinkupTaskNotFoundError: If the task identifier does not match an existing task.
LinkupAuthenticationError: If the Linkup API key is invalid.
LinkupTimeoutError: If the request times out.
"""
Expand Down Expand Up @@ -797,7 +813,8 @@ def fetch(
LinkupInvalidRequestError: If the provided URL is not valid.
LinkupFailedFetchError: If the provided URL is not found or can't be fetched.
LinkupFetchResponseTooLargeError: If the fetch response is too large.
LinkupFetchUrlIsFileError: If the provided URL points to a file and not a web page.
LinkupFetchUnsupportedContentTypeError: If the URL resolves to an unsupported content
type.
LinkupTimeoutError: If the request times out.
"""
params: dict[str, str | bool] = self._get_fetch_params(
Expand Down Expand Up @@ -846,7 +863,8 @@ async def async_fetch(
LinkupInvalidRequestError: If the provided URL is not valid.
LinkupFailedFetchError: If the provided URL is not found or can't be fetched.
LinkupFetchResponseTooLargeError: If the fetch response is too large.
LinkupFetchUrlIsFileError: If the provided URL points to a file and not a web page.
LinkupFetchUnsupportedContentTypeError: If the URL resolves to an unsupported content
type.
LinkupTimeoutError: If the request times out.
"""
params: dict[str, str | bool] = self._get_fetch_params(
Expand Down Expand Up @@ -1085,6 +1103,12 @@ def _raise_linkup_error(self, response: httpx.Response) -> None:
"The provided URL's response is too large to be processed.\n"
f"Original error message: {error_msg}."
)
if code == "FETCH_UNSUPPORTED_CONTENT_TYPE":
raise LinkupFetchUnsupportedContentTypeError(
"The Linkup API returned an unsupported content type error (400). "
"The provided URL does not point to a supported web page content type.\n"
f"Original error message: {error_msg}."
)
if code == "FETCH_URL_IS_FILE":
raise LinkupFetchUrlIsFileError(
"The Linkup API returned a fetch URL is file error (400). "
Expand All @@ -1110,13 +1134,36 @@ def _raise_linkup_error(self, response: httpx.Response) -> None:
"key is valid.\n"
f"Original error message: {error_msg}."
)
if response.status_code == 404:
if code == "TASK_NOT_FOUND":
raise LinkupTaskNotFoundError(
"The Linkup API returned a task not found error (404). "
"The requested task identifier does not exist.\n"
f"Original error message: {error_msg}."
)
raise LinkupUnknownError(
f"The Linkup API returned an unknown error (404).\n"
f"Original error message: ({error_msg})."
)
if response.status_code == 429:
if code == "INSUFFICIENT_FUNDS_CREDITS":
raise LinkupInsufficientCreditError(
"The Linkup API returned an insufficient credit error (429). Make sure "
"you haven't exhausted your credits.\n"
f"Original error message: {error_msg}."
)
if code == "EXCEED_BUDGET_LIMIT":
raise LinkupBudgetLimitExceededError(
"The Linkup API returned a budget limit exceeded error (429). "
"The API key has reached its configured budget limit.\n"
f"Original error message: {error_msg}."
)
if code == "TASKS_QUEUE_LIMIT_EXCEEDED":
raise LinkupTasksQueueLimitExceededError(
"The Linkup API returned a tasks queue limit exceeded error (429). "
"Too many tasks are already pending or processing.\n"
f"Original error message: {error_msg}."
)
if code == "TOO_MANY_REQUESTS":
raise LinkupTooManyRequestsError(
"The Linkup API returned a too many requests error (429). Make sure "
Expand Down Expand Up @@ -1256,8 +1303,12 @@ def _get_task_list_params(
page_size: int | None,
sort_by: Literal["createdAt", "updatedAt"] | None,
sort_direction: Literal["asc", "desc"] | None,
status: Literal["pending", "processing", "completed", "failed"] | None,
task_type: Literal["search", "fetch", "research"] | None,
status: Literal["pending", "processing", "completed", "failed"]
| list[Literal["pending", "processing", "completed", "failed"]]
| None,
task_type: Literal["search", "fetch", "research"]
| list[Literal["search", "fetch", "research"]]
| None,
) -> dict[str, Any]:
params = self._get_paginated_params(
page=page,
Expand Down
45 changes: 41 additions & 4 deletions src/linkup/_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,48 @@ class LinkupFetchResponseTooLargeError(Exception):
pass


class LinkupFetchUrlIsFileError(Exception):
"""Fetch URL is file error, raised when the Linkup API search returns a 400 status code.
class LinkupFetchUnsupportedContentTypeError(Exception):
"""Unsupported fetch content type error, raised when the Linkup API returns a 400 status code.

It is returned when the Linkup API can't fetch the URL because it points to a file and not
a web page.
It is returned when the URL resolves to a content type that Linkup cannot convert into a web
page response.
"""

pass


class LinkupFetchUrlIsFileError(LinkupFetchUnsupportedContentTypeError):
"""Backward-compatible alias for unsupported fetch content type errors.

Older Linkup API deployments could report this case as a file URL. Current deployments return
the more general unsupported content type error instead.
"""

pass


class LinkupBudgetLimitExceededError(Exception):
"""Budget limit exceeded error, raised when the Linkup API returns a 429 status code.

It is returned when the API key has reached its configured budget limit.
"""

pass


class LinkupTasksQueueLimitExceededError(Exception):
"""Tasks queue limit exceeded error, raised when the Linkup API returns a 429 status code.

It is returned when too many tasks are already pending or processing for the organization.
"""

pass


class LinkupTaskNotFoundError(Exception):
"""Task not found error, raised when the Linkup API returns a 404 status code.

It is returned when a task or research task identifier does not exist.
"""

pass
Expand Down
Loading
Loading