chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,456 @@
|
||||
"""
|
||||
Composio exceptions.
|
||||
"""
|
||||
|
||||
import difflib
|
||||
import typing as t
|
||||
|
||||
ENV_COMPOSIO_API_KEY = "COMPOSIO_API_KEY"
|
||||
|
||||
|
||||
class ComposioError(Exception):
|
||||
"""Base composio SDK error."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
*args: t.Any,
|
||||
delegate: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize Composio SDK error.
|
||||
|
||||
:param message: Error message
|
||||
:param delegate: Whether to delegate the error message to the log
|
||||
collection server or not
|
||||
"""
|
||||
super().__init__(message, *args)
|
||||
self.message = message
|
||||
self.delegate = delegate
|
||||
|
||||
|
||||
class NotFoundError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class HTTPError(ComposioError):
|
||||
"""
|
||||
Exception class for HTTP API errors.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
status_code: int,
|
||||
*args: t.Any,
|
||||
delegate: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize HTTPError class.
|
||||
|
||||
:param message: Content from the API response
|
||||
:param status_code: HTTP response status code
|
||||
:param delegate: Whether to delegate the error message to the log
|
||||
collection server or not
|
||||
"""
|
||||
super().__init__(message, *args, delegate=delegate)
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
class ComposioClientError(ComposioError):
|
||||
"""
|
||||
Exception class for Composio client errors.
|
||||
"""
|
||||
|
||||
|
||||
class SDKError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class ProcessorError(SDKError):
|
||||
pass
|
||||
|
||||
|
||||
class EnumError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class ValidationError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class JSONSchemaRefResolutionError(ValidationError):
|
||||
"""Raised when an internal JSON Schema ``$ref`` cannot be inlined.
|
||||
|
||||
Covers malformed pointers, missing ``$defs``/``definitions`` targets
|
||||
(strict mode only), and ``$ref`` chains or node nesting that exceed the
|
||||
safety caps in :func:`composio.utils.json_schema.dereference_json_schema`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
*args: t.Any,
|
||||
meta: t.Optional[t.Dict[str, t.Any]] = None,
|
||||
delegate: bool = False,
|
||||
) -> None:
|
||||
super().__init__(message, *args, delegate=delegate)
|
||||
self.meta = meta or {}
|
||||
|
||||
|
||||
class ToolkitError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class EntityIDError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class PluginError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidParams(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class FileError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class ComposioSDKTimeoutError(ComposioError, TimeoutError):
|
||||
pass
|
||||
|
||||
|
||||
class SDKFileNotFoundError(ComposioError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
|
||||
class LockFileError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class VersionError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidLockFile(LockFileError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidVersionString(EnumError):
|
||||
pass
|
||||
|
||||
|
||||
class VersionSelectionError(LockFileError, VersionError):
|
||||
def __init__(
|
||||
self,
|
||||
action: str,
|
||||
requested: str,
|
||||
locked: str,
|
||||
delegate: bool = False,
|
||||
) -> None:
|
||||
self.action = action
|
||||
self.requested = requested
|
||||
self.locked = locked
|
||||
super().__init__(
|
||||
message=(
|
||||
f"Error selecting version for action: {action!r}, "
|
||||
f"requested: {requested!r}, locked: {locked!r}"
|
||||
),
|
||||
delegate=delegate,
|
||||
)
|
||||
|
||||
|
||||
class InvalidEnum(EnumError):
|
||||
pass
|
||||
|
||||
|
||||
class EnumStringNotFound(EnumError):
|
||||
"""Raise when user provides invalid enum string."""
|
||||
|
||||
def __init__(self, value: str, enum: str, possible_values: t.List[str]) -> None:
|
||||
error_message = f"Invalid value `{value}` for enum class `{enum}`"
|
||||
matches = difflib.get_close_matches(value, possible_values, n=1)
|
||||
if matches:
|
||||
(match,) = matches
|
||||
error_message += f". Did you mean {match!r}?"
|
||||
|
||||
super().__init__(message=error_message)
|
||||
|
||||
|
||||
class EnumMetadataNotFound(EnumError):
|
||||
pass
|
||||
|
||||
|
||||
class ErrorUploadingFile(FileError):
|
||||
pass
|
||||
|
||||
|
||||
class SensitiveFilePathBlockedError(FileError):
|
||||
"""Raised when a local file path is refused before upload (sensitive directory or credential-like name)."""
|
||||
|
||||
|
||||
class FileUploadPathNotAllowedError(FileError):
|
||||
"""
|
||||
Raised when automatic file upload during tool execution is attempted from a
|
||||
path outside the configured ``file_upload_dirs`` allowlist.
|
||||
|
||||
Only fires for auto-upload (enabled via
|
||||
``dangerously_allow_auto_upload_download_files=True``). Manual upload APIs
|
||||
are not subject to this check.
|
||||
"""
|
||||
|
||||
|
||||
class FileUploadAbortedError(FileError):
|
||||
"""Raised when a ``before_file_upload`` hook returns ``False``."""
|
||||
|
||||
|
||||
class ErrorDownloadingFile(FileError):
|
||||
pass
|
||||
|
||||
|
||||
class RemoteFileDownloadError(FileError):
|
||||
"""Raised when fetching a remote file from a tool router session mount fails.
|
||||
|
||||
Includes HTTP status, URL, and file path context for debugging.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = "Failed to download remote file",
|
||||
*,
|
||||
status_code: t.Optional[int] = None,
|
||||
status_text: t.Optional[str] = None,
|
||||
download_url: t.Optional[str] = None,
|
||||
mount_relative_path: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
**kwargs: t.Any,
|
||||
) -> None:
|
||||
super().__init__(message, **kwargs)
|
||||
self.status_code = status_code
|
||||
self.status_text = status_text
|
||||
self.download_url = download_url
|
||||
self.mount_relative_path = mount_relative_path
|
||||
self.filename = filename
|
||||
|
||||
|
||||
class ResponseTooLargeError(FileError):
|
||||
"""Raised when a response exceeds the maximum allowed size."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TriggerError(ToolkitError):
|
||||
pass
|
||||
|
||||
|
||||
class WebhookSignatureVerificationError(TriggerError):
|
||||
"""Raised when webhook signature verification fails."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class WebhookPayloadError(TriggerError):
|
||||
"""Raised when webhook payload is invalid."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TriggerTypeNotFound(TriggerError, NotFoundError):
|
||||
"""Raised when a trigger type cannot be found for the given slug.
|
||||
|
||||
Mirrors the TypeScript SDK's ``ComposioTriggerTypeNotFoundError``.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ActionError(ToolkitError):
|
||||
pass
|
||||
|
||||
|
||||
class TriggerSubscriptionError(TriggerError, ComposioClientError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidTriggerFilters(TriggerSubscriptionError):
|
||||
pass
|
||||
|
||||
|
||||
class ApiKeyError(ComposioClientError):
|
||||
pass
|
||||
|
||||
|
||||
class ApiKeyNotProvidedError(ApiKeyError, NotFoundError):
|
||||
"""Raise when API key is required but not provided."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
message=(
|
||||
"API Key not provided, either provide API key "
|
||||
f"or export it as `{ENV_COMPOSIO_API_KEY}` "
|
||||
"or run `composio login`"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ResourceError(ComposioClientError):
|
||||
pass
|
||||
|
||||
|
||||
class NoItemsFound(ResourceError):
|
||||
"""
|
||||
Exception class for empty collection values.
|
||||
"""
|
||||
|
||||
|
||||
class ErrorFetchingResource(ResourceError):
|
||||
pass
|
||||
|
||||
|
||||
class SchemaError(ToolkitError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidSchemaError(SchemaError, TypeError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidEntityIdError(EntityIDError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class IntegrationError(ResourceError):
|
||||
pass
|
||||
|
||||
|
||||
class ConnectedAccountError(ResourceError):
|
||||
pass
|
||||
|
||||
|
||||
class ConnectedAccountNotFoundError(NotFoundError, ConnectedAccountError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidConnectedAccount(ValidationError, ConnectedAccountError):
|
||||
pass
|
||||
|
||||
|
||||
class ComposioMultipleConnectedAccountsError(ConnectedAccountError):
|
||||
"""Raised when multiple connected accounts are found for a user and auth config."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ComposioLegacyConnectedAccountsEndpointRetiredError(ConnectedAccountError):
|
||||
"""Raised by ``composio.connected_accounts.initiate()`` when the legacy
|
||||
``POST /api/v3/connected_accounts`` endpoint rejects a Composio-managed
|
||||
OAuth (OAuth1, OAuth2, DCR_OAUTH) auth-config request.
|
||||
|
||||
Cutover dates: 2026-05-08 (new orgs), 2026-07-03 (all remaining orgs).
|
||||
Migrate to ``composio.connected_accounts.link()`` — same return shape,
|
||||
works for every redirectable scheme regardless of whether the auth
|
||||
config is Composio-managed or custom.
|
||||
|
||||
See: https://docs.composio.dev/docs/changelog/2026/04/24
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ComposioAclOnlyForSharedError(ConnectedAccountError):
|
||||
"""Raised when ACL fields (``allow_all_users``, ``allowed_user_ids``,
|
||||
``not_allowed_user_ids``) are sent on a ``PRIVATE`` connection. ACL
|
||||
is only meaningful for ``SHARED`` connections.
|
||||
|
||||
Fix: use ``account_type='SHARED'``, or omit the ACL fields when
|
||||
creating/updating a PRIVATE connection.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ComposioSharedAccessDeniedError(ConnectedAccountError):
|
||||
"""Raised when a tool execution attempts to use a SHARED connected
|
||||
account but the requesting ``user_id`` is not allowed by the
|
||||
connection's ACL.
|
||||
|
||||
Surfaces when a SHARED connection is reached directly (e.g. via
|
||||
``composio.tools.execute(slug, connected_account_id=...)``) without
|
||||
going through a tool-router session.
|
||||
|
||||
Fix: ask the connection's creator to grant access via
|
||||
``composio.experimental.update_acl()`` — set ``allow_all_users``,
|
||||
add the ``user_id`` to ``allowed_user_ids``, or remove it from
|
||||
``not_allowed_user_ids``.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ComposioSharedConnectionNotAccessibleError(ConnectedAccountError):
|
||||
"""Raised by ``tool_router.session.create()`` / ``session.patch()``
|
||||
when the session's ``user_id`` cannot use a pinned SHARED connection.
|
||||
Raised at session-create time so the session never enters a state
|
||||
that fails mid-execution.
|
||||
|
||||
Fix: grant the session user access via
|
||||
``composio.experimental.update_acl()`` on the pinned connection,
|
||||
or pin a different connection the user can use.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ErrorProcessingToolExecutionRequest(PluginError):
|
||||
pass
|
||||
|
||||
|
||||
class DescopeAuthError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class DescopeConfigError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidExecuteFunctionError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class ToolNotFoundError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidModifier(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class ExecuteToolFnNotSetError(ComposioError):
|
||||
pass
|
||||
|
||||
|
||||
class ToolVersionRequiredError(ComposioError):
|
||||
"""Raised when toolkit version is not specified for manual tool execution."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
message=(
|
||||
"Toolkit version not specified. For manual execution of the tool "
|
||||
"please pass a specific toolkit version.\n\n"
|
||||
"Possible fixes:\n"
|
||||
"1. Pass the toolkit version as a parameter to the execute function "
|
||||
'("latest" is not supported in manual execution)\n'
|
||||
"2. Set the toolkit versions in the Composio config "
|
||||
"(toolkit_versions={'<toolkit-slug>': '<toolkit-version>'})\n"
|
||||
"3. Set the toolkit version in the environment variable "
|
||||
"(COMPOSIO_TOOLKIT_VERSION_<TOOLKIT_SLUG>)\n"
|
||||
"4. Set dangerously_skip_version_check to True "
|
||||
"(this might cause unexpected behavior when new versions of the tools are released)"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class UsageError(ComposioError):
|
||||
pass
|
||||
Reference in New Issue
Block a user