airbyte.exceptions
All exceptions used in the PyAirbyte.
This design is modeled after structlog's exceptions, in that we bias towards auto-generated property prints rather than sentence-like string concatenation.
E.g. Instead of this:
Subprocess failed with exit code '1'
We do this:
Subprocess failed. (exit_code=1)
The benefit of this approach is that we can easily support structured logging, and we can easily add new properties to exceptions without having to update all the places where they are raised. We can also support any arbitrary number of properties in exceptions, without spending time on building sentence-like string constructions with optional inputs.
In addition, the following principles are applied for exception class design:
- All exceptions inherit from a common base class.
- All exceptions have a message attribute.
- The first line of the docstring is used as the default message.
- The default message can be overridden by explicitly setting the message attribute.
- Exceptions may optionally have a guidance attribute.
- Exceptions may optionally have a help_url attribute.
- Rendering is automatically handled by the base class.
- Any helpful context not defined by the exception class can be passed in the
contextdict arg. - Within reason, avoid sending PII to the exception constructor.
- Exceptions are dataclasses, so they can be instantiated with keyword arguments.
- Use the 'from' syntax to chain exceptions when it is helpful to do so.
E.g.
raise AirbyteConnectorNotFoundError(...) from FileNotFoundError(connector_path) - Any exception that adds a new property should also be decorated as
@dataclass.
1# Copyright (c) 2023 Airbyte, Inc., all rights reserved. 2 3"""All exceptions used in the PyAirbyte. 4 5This design is modeled after structlog's exceptions, in that we bias towards auto-generated 6property prints rather than sentence-like string concatenation. 7 8E.g. Instead of this: 9 10> `Subprocess failed with exit code '1'` 11 12We do this: 13 14> `Subprocess failed. (exit_code=1)` 15 16The benefit of this approach is that we can easily support structured logging, and we can 17easily add new properties to exceptions without having to update all the places where they 18are raised. We can also support any arbitrary number of properties in exceptions, without spending 19time on building sentence-like string constructions with optional inputs. 20 21 22In addition, the following principles are applied for exception class design: 23 24- All exceptions inherit from a common base class. 25- All exceptions have a message attribute. 26- The first line of the docstring is used as the default message. 27- The default message can be overridden by explicitly setting the message attribute. 28- Exceptions may optionally have a guidance attribute. 29- Exceptions may optionally have a help_url attribute. 30- Rendering is automatically handled by the base class. 31- Any helpful context not defined by the exception class can be passed in the `context` dict arg. 32- Within reason, avoid sending PII to the exception constructor. 33- Exceptions are dataclasses, so they can be instantiated with keyword arguments. 34- Use the 'from' syntax to chain exceptions when it is helpful to do so. 35 E.g. `raise AirbyteConnectorNotFoundError(...) from FileNotFoundError(connector_path)` 36- Any exception that adds a new property should also be decorated as `@dataclass`. 37""" 38 39from __future__ import annotations 40 41import logging 42from dataclasses import dataclass 43from pathlib import Path 44from textwrap import indent 45from typing import TYPE_CHECKING, Any 46 47from airbyte.constants import AIRBYTE_PRINT_FULL_ERROR_LOGS 48 49 50if TYPE_CHECKING: 51 from airbyte._util.api_duck_types import AirbyteApiResponseDuckType 52 from airbyte.cloud.workspaces import CloudWorkspace 53 54 55NEW_ISSUE_URL = "https://github.com/airbytehq/airbyte/issues/new/choose" 56DOCS_URL_BASE = "https://airbytehq.github.io/PyAirbyte" 57DOCS_URL = f"{DOCS_URL_BASE}/airbyte.html" 58 59VERTICAL_SEPARATOR = "\n" + "-" * 60 60 61 62# Base error class 63 64 65@dataclass 66class PyAirbyteError(Exception): 67 """Base class for exceptions in Airbyte.""" 68 69 guidance: str | None = None 70 help_url: str | None = None 71 log_text: str | list[str] | None = None 72 log_file: Path | None = None 73 print_full_log: bool = AIRBYTE_PRINT_FULL_ERROR_LOGS 74 context: dict[str, Any] | None = None 75 message: str | None = None 76 original_exception: Exception | None = None 77 78 def get_message(self) -> str: 79 """Return the best description for the exception. 80 81 We resolve the following in order: 82 1. The message sent to the exception constructor (if provided). 83 2. The first line of the class's docstring. 84 """ 85 if self.message: 86 return self.message 87 88 return self.__doc__.split("\n")[0] if self.__doc__ else "" 89 90 def __str__(self) -> str: 91 """Return a string representation of the exception.""" 92 special_properties = [ 93 "message", 94 "guidance", 95 "help_url", 96 "log_text", 97 "context", 98 "log_file", 99 "print_full_log", 100 "original_exception", 101 ] 102 display_properties = { 103 k: v 104 for k, v in self.__dict__.items() 105 if k not in special_properties and not k.startswith("_") and v is not None 106 } 107 display_properties.update(self.context or {}) 108 context_str = "\n ".join( 109 f"{str(k).replace('_', ' ').title()}: {v!r}" for k, v in display_properties.items() 110 ) 111 exception_str = ( 112 f"{self.get_message()} ({self.__class__.__name__})" 113 + VERTICAL_SEPARATOR 114 + f"\n{self.__class__.__name__}: {self.get_message()}" 115 ) 116 117 if self.guidance: 118 exception_str += f"\n {self.guidance}" 119 120 if self.help_url: 121 exception_str += f"\n More info: {self.help_url}" 122 123 if context_str: 124 exception_str += "\n " + context_str 125 126 if self.log_text: 127 if isinstance(self.log_text, list): 128 self.log_text = "\n".join(self.log_text) 129 130 exception_str += f"\n Log output: \n {indent(self.log_text, ' ')}" 131 132 if self.original_exception: 133 exception_str += VERTICAL_SEPARATOR + f"\nCaused by: {self.original_exception!s}" 134 135 if self.log_file: 136 if self.print_full_log: 137 if not self.log_file.is_file(): 138 exception_str += f"\n No log file found at: {self.log_file.absolute()!s}" 139 140 else: 141 try: 142 full_log_file_text = self.log_file.read_text() 143 except Exception as ex: 144 full_log_file_text = ( 145 f"[ERROR] Log file could not be read from: {self.log_file.absolute()!s}" 146 f"\nRead error: {ex!s}" 147 ) 148 149 exception_str += ( 150 f"\n Full log file text from {self.log_file.absolute()!s}:" 151 + VERTICAL_SEPARATOR 152 + full_log_file_text 153 + VERTICAL_SEPARATOR 154 ) 155 else: 156 exception_str += f"\n Log file: {self.log_file.absolute()!s}" 157 return exception_str 158 159 def __repr__(self) -> str: 160 """Return a string representation of the exception.""" 161 class_name = self.__class__.__name__ 162 properties_str = ", ".join( 163 f"{k}={v!r}" for k, v in self.__dict__.items() if not k.startswith("_") 164 ) 165 return f"{class_name}({properties_str})" 166 167 def safe_logging_dict(self) -> dict[str, Any]: 168 """Return a dictionary of the exception's properties which is safe for logging. 169 170 We avoid any properties which could potentially contain PII. 171 """ 172 result = { 173 # The class name is safe to log: 174 "class": self.__class__.__name__, 175 # We discourage interpolated strings in 'message' so that this should never contain PII: 176 "message": self.get_message(), 177 } 178 safe_attrs = ["connector_name", "stream_name", "violation", "exit_code"] 179 for attr in safe_attrs: 180 if hasattr(self, attr): 181 result[attr] = getattr(self, attr) 182 183 return result 184 185 186# PyAirbyte Internal Errors (these are probably bugs) 187 188 189@dataclass 190class PyAirbyteInternalError(PyAirbyteError): 191 """An internal error occurred in PyAirbyte.""" 192 193 guidance = "Please consider reporting this error to the Airbyte team." 194 help_url = NEW_ISSUE_URL 195 196 197# PyAirbyte Input Errors (replaces ValueError for user input) 198 199 200@dataclass 201class PyAirbyteInputError(PyAirbyteError, ValueError): 202 """The input provided to PyAirbyte did not match expected validation rules. 203 204 This inherits from ValueError so that it can be used as a drop-in replacement for 205 ValueError in the PyAirbyte API. 206 """ 207 208 guidance = "Please check the provided value and try again." 209 help_url = DOCS_URL 210 input_value: str | None = None 211 212 213@dataclass 214class PyAirbyteNoStreamsSelectedError(PyAirbyteInputError): 215 """No streams were selected for the source.""" 216 217 guidance = ( 218 "Please call `select_streams()` to select at least one stream from the list provided. " 219 "You can also call `select_all_streams()` to select all available streams for this source." 220 ) 221 connector_name: str | None = None 222 available_streams: list[str] | None = None 223 224 225# MCP Server Errors 226 227 228@dataclass 229class AirbyteMCPError(PyAirbyteError): 230 """An error occurred in the Airbyte MCP server.""" 231 232 233@dataclass 234class AirbyteTrustedExecutionRequiredError(AirbyteMCPError): 235 """A trusted-execution-only capability was invoked while trusted execution is disabled. 236 237 Trusted execution grants the MCP server its trusted-machine capabilities: local 238 filesystem access, local connector installation/execution, and server-side secret 239 resolution. It defaults to *off* on every transport and is permanently unavailable 240 over the HTTP transport, so a backend helper that exposes one of those capabilities 241 hard-fails when the gate is disabled -- independently of whether the corresponding 242 tool was hidden from the tool listing. 243 """ 244 245 guidance = ( 246 "Set `AIRBYTE_MCP_TRUSTED_EXECUTION=1` on the MCP server process and restart it. " 247 "Trusted execution is only available on the stdio transport; it can never be " 248 "enabled for an HTTP/hosted deployment." 249 ) 250 feature: str | None = None 251 252 253# Normalization Errors 254 255 256@dataclass 257class PyAirbyteNameNormalizationError(PyAirbyteError, ValueError): 258 """Error occurred while normalizing a table or column name.""" 259 260 guidance = ( 261 "Please consider renaming the source object if possible, or " 262 "raise an issue in GitHub if not." 263 ) 264 help_url = NEW_ISSUE_URL 265 266 raw_name: str | None = None 267 normalization_result: str | None = None 268 269 270# PyAirbyte Cache Errors 271 272 273class PyAirbyteCacheError(PyAirbyteError): 274 """Error occurred while accessing the cache.""" 275 276 277@dataclass 278class PyAirbyteCacheTableValidationError(PyAirbyteCacheError): 279 """Cache table validation failed.""" 280 281 violation: str | None = None 282 283 284@dataclass 285class AirbyteConnectorConfigurationMissingError(PyAirbyteCacheError): 286 """Connector is missing configuration.""" 287 288 connector_name: str | None = None 289 290 291# Subprocess Errors 292 293 294@dataclass 295class AirbyteSubprocessError(PyAirbyteError): 296 """Error when running subprocess.""" 297 298 run_args: list[str] | None = None 299 300 301@dataclass 302class AirbyteSubprocessFailedError(AirbyteSubprocessError): 303 """Subprocess failed.""" 304 305 exit_code: int | None = None 306 307 308# Connector Registry Errors 309 310 311class AirbyteConnectorRegistryError(PyAirbyteError): 312 """Error when accessing the connector registry.""" 313 314 315@dataclass 316class AirbyteConnectorNotRegisteredError(AirbyteConnectorRegistryError): 317 """Connector not found in registry.""" 318 319 connector_name: str | None = None 320 guidance = ( 321 "Please double check the connector name. " 322 "Alternatively, you can provide an explicit connector install method to `get_source()`: " 323 "`pip_url`, `local_executable`, `docker_image`, or `source_manifest`." 324 ) 325 help_url = DOCS_URL_BASE + "/airbyte/sources/util.html#get_source" 326 327 328@dataclass 329class AirbyteConnectorNotPyPiPublishedError(AirbyteConnectorRegistryError): 330 """Connector found, but not published to PyPI.""" 331 332 connector_name: str | None = None 333 guidance = "This likely means that the connector is not ready for use with PyAirbyte." 334 335 336# Connector Errors 337 338 339@dataclass 340class AirbyteConnectorError(PyAirbyteError): 341 """Error when running the connector.""" 342 343 connector_name: str | None = None 344 345 def __post_init__(self) -> None: 346 """Set the log file path for the connector.""" 347 self.log_file = self._get_log_file() 348 if not self.guidance and self.log_file: 349 self.guidance = "Please review the log file for more information." 350 351 def _get_log_file(self) -> Path | None: 352 """Return the log file path for the connector.""" 353 if self.connector_name: 354 logger = logging.getLogger(f"airbyte.{self.connector_name}") 355 356 log_paths: list[Path] = [ 357 Path(handler.baseFilename).absolute() 358 for handler in logger.handlers 359 if isinstance(handler, logging.FileHandler) 360 ] 361 362 if log_paths: 363 return log_paths[0] 364 365 return None 366 367 368class AirbyteConnectorExecutableNotFoundError(AirbyteConnectorError): 369 """Connector executable not found.""" 370 371 372class AirbyteConnectorInstallationError(AirbyteConnectorError): 373 """Error when installing the connector.""" 374 375 376class AirbyteConnectorReadError(AirbyteConnectorError): 377 """Error when reading from the connector.""" 378 379 380class AirbyteConnectorWriteError(AirbyteConnectorError): 381 """Error when writing to the connector.""" 382 383 384class AirbyteConnectorSpecFailedError(AirbyteConnectorError): 385 """Error when getting spec from the connector.""" 386 387 388class AirbyteConnectorDiscoverFailedError(AirbyteConnectorError): 389 """Error when running discovery on the connector.""" 390 391 392class AirbyteNoDataFromConnectorError(AirbyteConnectorError): 393 """No data was provided from the connector.""" 394 395 396class AirbyteConnectorMissingCatalogError(AirbyteConnectorError): 397 """Connector did not return a catalog.""" 398 399 400class AirbyteConnectorMissingSpecError(AirbyteConnectorError): 401 """Connector did not return a spec.""" 402 403 404class AirbyteConnectorValidationFailedError(AirbyteConnectorError): 405 """Connector config validation failed.""" 406 407 guidance = ( 408 "Please double-check your config and review the validation errors for more information." 409 ) 410 411 412class AirbyteConnectorCheckFailedError(AirbyteConnectorError): 413 """Connector check failed.""" 414 415 guidance = ( 416 "Please double-check your config or review the connector's logs for more information." 417 ) 418 419 420@dataclass 421class AirbyteConnectorFailedError(AirbyteConnectorError): 422 """Connector failed.""" 423 424 exit_code: int | None = None 425 426 427@dataclass 428class AirbyteStreamNotFoundError(AirbyteConnectorError): 429 """Connector stream not found.""" 430 431 stream_name: str | None = None 432 available_streams: list[str] | None = None 433 434 435@dataclass 436class AirbyteStateNotFoundError(AirbyteConnectorError, KeyError): 437 """State entry not found.""" 438 439 stream_name: str | None = None 440 available_streams: list[str] | None = None 441 442 443@dataclass 444class PyAirbyteSecretNotFoundError(PyAirbyteError): 445 """Secret not found.""" 446 447 guidance = "Please ensure that the secret is set." 448 help_url = ( 449 "https://docs.airbyte.com/using-airbyte/airbyte-lib/getting-started#secrets-management" 450 ) 451 452 secret_name: str | None = None 453 sources: list[str] | None = None 454 455 456# Airbyte API Errors 457 458 459@dataclass 460class AirbyteError(PyAirbyteError): 461 """An error occurred while communicating with the hosted Airbyte instance.""" 462 463 response: AirbyteApiResponseDuckType | None = None 464 """The API response from the failed request.""" 465 466 workspace: CloudWorkspace | None = None 467 """The workspace where the error occurred.""" 468 469 @property 470 def workspace_url(self) -> str | None: 471 """The URL to the workspace where the error occurred.""" 472 if self.workspace: 473 return self.workspace.workspace_url 474 475 return None 476 477 478@dataclass 479class AirbyteConnectionError(AirbyteError): 480 """An connection error occurred while communicating with the hosted Airbyte instance.""" 481 482 connection_id: str | None = None 483 """The connection ID where the error occurred.""" 484 485 job_id: int | None = None 486 """The job ID where the error occurred (if applicable).""" 487 488 job_status: str | None = None 489 """The latest status of the job where the error occurred (if applicable).""" 490 491 @property 492 def connection_url(self) -> str | None: 493 """The web URL to the connection where the error occurred.""" 494 if self.workspace_url and self.connection_id: 495 return f"{self.workspace_url}/connections/{self.connection_id}" 496 497 return None 498 499 @property 500 def job_history_url(self) -> str | None: 501 """The URL to the job history where the error occurred.""" 502 if self.connection_url: 503 return f"{self.connection_url}/timeline" 504 505 return None 506 507 @property 508 def job_url(self) -> str | None: 509 """The URL to the job where the error occurred.""" 510 if self.job_history_url and self.job_id: 511 return f"{self.job_history_url}#{self.job_id}::0" 512 513 return None 514 515 516@dataclass 517class AirbyteConnectionSyncError(AirbyteConnectionError): 518 """An error occurred while executing the remote Airbyte job.""" 519 520 521@dataclass 522class AirbyteConnectionSyncActiveError(AirbyteConnectionError): 523 """State update rejected because a sync is currently running (HTTP 423).""" 524 525 526@dataclass 527class AirbyteWorkspaceMismatchError(AirbyteError): 528 """Resource does not belong to the expected workspace. 529 530 This error is raised when a resource (connection, source, or destination) is fetched 531 from the API and the workspace ID in the response does not match the expected workspace. 532 """ 533 534 resource_type: str | None = None 535 """The type of resource (e.g., 'connection', 'source', 'destination').""" 536 537 resource_id: str | None = None 538 """The ID of the resource that was fetched.""" 539 540 expected_workspace_id: str | None = None 541 """The workspace ID that was expected.""" 542 543 actual_workspace_id: str | None = None 544 """The workspace ID returned by the API.""" 545 546 547@dataclass 548class AirbyteWorkspaceNotEmptyError(AirbyteError): 549 """Workspace cannot be deleted because it contains connections.""" 550 551 workspace_id: str | None = None 552 """The workspace ID that was expected to be empty.""" 553 554 connection_ids: list[str] | None = None 555 """The connection IDs found in the workspace.""" 556 557 558@dataclass 559class AirbyteConnectionSyncTimeoutError(AirbyteConnectionSyncError): 560 """An timeout occurred while waiting for the remote Airbyte job to complete.""" 561 562 timeout: int | None = None 563 """The timeout in seconds that was reached.""" 564 565 566# Airbyte Resource Errors (General) 567 568 569@dataclass 570class AirbyteMissingResourceError(AirbyteError): 571 """Remote Airbyte resources does not exist.""" 572 573 resource_type: str | None = None 574 resource_name_or_id: str | None = None 575 576 577@dataclass 578class AirbyteDuplicateResourcesError(AirbyteError): 579 """Process failed because resource name was not unique.""" 580 581 resource_type: str | None = None 582 resource_name: str | None = None 583 584 585# Custom Warnings 586@dataclass 587class AirbyteMultipleResourcesError(AirbyteError): 588 """Could not locate the resource because multiple matching resources were found.""" 589 590 resource_type: str | None = None 591 resource_name_or_id: str | None = None 592 593 594# Custom Warnings 595 596 597class AirbyteExperimentalFeatureWarning(FutureWarning): 598 """Warning whenever using experimental features in PyAirbyte.""" 599 600 601# PyAirbyte Warnings 602 603 604class PyAirbyteWarning(Warning): 605 """General warnings from PyAirbyte.""" 606 607 608class PyAirbyteDataLossWarning(PyAirbyteWarning): 609 """Warning for potential data loss. 610 611 Users can ignore this warning by running: 612 > warnings.filterwarnings("ignore", category="airbyte.exceptions.PyAirbyteDataLossWarning") 613 """
66@dataclass 67class PyAirbyteError(Exception): 68 """Base class for exceptions in Airbyte.""" 69 70 guidance: str | None = None 71 help_url: str | None = None 72 log_text: str | list[str] | None = None 73 log_file: Path | None = None 74 print_full_log: bool = AIRBYTE_PRINT_FULL_ERROR_LOGS 75 context: dict[str, Any] | None = None 76 message: str | None = None 77 original_exception: Exception | None = None 78 79 def get_message(self) -> str: 80 """Return the best description for the exception. 81 82 We resolve the following in order: 83 1. The message sent to the exception constructor (if provided). 84 2. The first line of the class's docstring. 85 """ 86 if self.message: 87 return self.message 88 89 return self.__doc__.split("\n")[0] if self.__doc__ else "" 90 91 def __str__(self) -> str: 92 """Return a string representation of the exception.""" 93 special_properties = [ 94 "message", 95 "guidance", 96 "help_url", 97 "log_text", 98 "context", 99 "log_file", 100 "print_full_log", 101 "original_exception", 102 ] 103 display_properties = { 104 k: v 105 for k, v in self.__dict__.items() 106 if k not in special_properties and not k.startswith("_") and v is not None 107 } 108 display_properties.update(self.context or {}) 109 context_str = "\n ".join( 110 f"{str(k).replace('_', ' ').title()}: {v!r}" for k, v in display_properties.items() 111 ) 112 exception_str = ( 113 f"{self.get_message()} ({self.__class__.__name__})" 114 + VERTICAL_SEPARATOR 115 + f"\n{self.__class__.__name__}: {self.get_message()}" 116 ) 117 118 if self.guidance: 119 exception_str += f"\n {self.guidance}" 120 121 if self.help_url: 122 exception_str += f"\n More info: {self.help_url}" 123 124 if context_str: 125 exception_str += "\n " + context_str 126 127 if self.log_text: 128 if isinstance(self.log_text, list): 129 self.log_text = "\n".join(self.log_text) 130 131 exception_str += f"\n Log output: \n {indent(self.log_text, ' ')}" 132 133 if self.original_exception: 134 exception_str += VERTICAL_SEPARATOR + f"\nCaused by: {self.original_exception!s}" 135 136 if self.log_file: 137 if self.print_full_log: 138 if not self.log_file.is_file(): 139 exception_str += f"\n No log file found at: {self.log_file.absolute()!s}" 140 141 else: 142 try: 143 full_log_file_text = self.log_file.read_text() 144 except Exception as ex: 145 full_log_file_text = ( 146 f"[ERROR] Log file could not be read from: {self.log_file.absolute()!s}" 147 f"\nRead error: {ex!s}" 148 ) 149 150 exception_str += ( 151 f"\n Full log file text from {self.log_file.absolute()!s}:" 152 + VERTICAL_SEPARATOR 153 + full_log_file_text 154 + VERTICAL_SEPARATOR 155 ) 156 else: 157 exception_str += f"\n Log file: {self.log_file.absolute()!s}" 158 return exception_str 159 160 def __repr__(self) -> str: 161 """Return a string representation of the exception.""" 162 class_name = self.__class__.__name__ 163 properties_str = ", ".join( 164 f"{k}={v!r}" for k, v in self.__dict__.items() if not k.startswith("_") 165 ) 166 return f"{class_name}({properties_str})" 167 168 def safe_logging_dict(self) -> dict[str, Any]: 169 """Return a dictionary of the exception's properties which is safe for logging. 170 171 We avoid any properties which could potentially contain PII. 172 """ 173 result = { 174 # The class name is safe to log: 175 "class": self.__class__.__name__, 176 # We discourage interpolated strings in 'message' so that this should never contain PII: 177 "message": self.get_message(), 178 } 179 safe_attrs = ["connector_name", "stream_name", "violation", "exit_code"] 180 for attr in safe_attrs: 181 if hasattr(self, attr): 182 result[attr] = getattr(self, attr) 183 184 return result
Base class for exceptions in Airbyte.
79 def get_message(self) -> str: 80 """Return the best description for the exception. 81 82 We resolve the following in order: 83 1. The message sent to the exception constructor (if provided). 84 2. The first line of the class's docstring. 85 """ 86 if self.message: 87 return self.message 88 89 return self.__doc__.split("\n")[0] if self.__doc__ else ""
Return the best description for the exception.
We resolve the following in order:
- The message sent to the exception constructor (if provided).
- The first line of the class's docstring.
168 def safe_logging_dict(self) -> dict[str, Any]: 169 """Return a dictionary of the exception's properties which is safe for logging. 170 171 We avoid any properties which could potentially contain PII. 172 """ 173 result = { 174 # The class name is safe to log: 175 "class": self.__class__.__name__, 176 # We discourage interpolated strings in 'message' so that this should never contain PII: 177 "message": self.get_message(), 178 } 179 safe_attrs = ["connector_name", "stream_name", "violation", "exit_code"] 180 for attr in safe_attrs: 181 if hasattr(self, attr): 182 result[attr] = getattr(self, attr) 183 184 return result
Return a dictionary of the exception's properties which is safe for logging.
We avoid any properties which could potentially contain PII.
190@dataclass 191class PyAirbyteInternalError(PyAirbyteError): 192 """An internal error occurred in PyAirbyte.""" 193 194 guidance = "Please consider reporting this error to the Airbyte team." 195 help_url = NEW_ISSUE_URL
An internal error occurred in PyAirbyte.
Inherited Members
201@dataclass 202class PyAirbyteInputError(PyAirbyteError, ValueError): 203 """The input provided to PyAirbyte did not match expected validation rules. 204 205 This inherits from ValueError so that it can be used as a drop-in replacement for 206 ValueError in the PyAirbyte API. 207 """ 208 209 guidance = "Please check the provided value and try again." 210 help_url = DOCS_URL 211 input_value: str | None = None
The input provided to PyAirbyte did not match expected validation rules.
This inherits from ValueError so that it can be used as a drop-in replacement for ValueError in the PyAirbyte API.
Inherited Members
214@dataclass 215class PyAirbyteNoStreamsSelectedError(PyAirbyteInputError): 216 """No streams were selected for the source.""" 217 218 guidance = ( 219 "Please call `select_streams()` to select at least one stream from the list provided. " 220 "You can also call `select_all_streams()` to select all available streams for this source." 221 ) 222 connector_name: str | None = None 223 available_streams: list[str] | None = None
No streams were selected for the source.
229@dataclass 230class AirbyteMCPError(PyAirbyteError): 231 """An error occurred in the Airbyte MCP server."""
An error occurred in the Airbyte MCP server.
234@dataclass 235class AirbyteTrustedExecutionRequiredError(AirbyteMCPError): 236 """A trusted-execution-only capability was invoked while trusted execution is disabled. 237 238 Trusted execution grants the MCP server its trusted-machine capabilities: local 239 filesystem access, local connector installation/execution, and server-side secret 240 resolution. It defaults to *off* on every transport and is permanently unavailable 241 over the HTTP transport, so a backend helper that exposes one of those capabilities 242 hard-fails when the gate is disabled -- independently of whether the corresponding 243 tool was hidden from the tool listing. 244 """ 245 246 guidance = ( 247 "Set `AIRBYTE_MCP_TRUSTED_EXECUTION=1` on the MCP server process and restart it. " 248 "Trusted execution is only available on the stdio transport; it can never be " 249 "enabled for an HTTP/hosted deployment." 250 ) 251 feature: str | None = None
A trusted-execution-only capability was invoked while trusted execution is disabled.
Trusted execution grants the MCP server its trusted-machine capabilities: local filesystem access, local connector installation/execution, and server-side secret resolution. It defaults to off on every transport and is permanently unavailable over the HTTP transport, so a backend helper that exposes one of those capabilities hard-fails when the gate is disabled -- independently of whether the corresponding tool was hidden from the tool listing.
257@dataclass 258class PyAirbyteNameNormalizationError(PyAirbyteError, ValueError): 259 """Error occurred while normalizing a table or column name.""" 260 261 guidance = ( 262 "Please consider renaming the source object if possible, or " 263 "raise an issue in GitHub if not." 264 ) 265 help_url = NEW_ISSUE_URL 266 267 raw_name: str | None = None 268 normalization_result: str | None = None
Error occurred while normalizing a table or column name.
Inherited Members
Error occurred while accessing the cache.
278@dataclass 279class PyAirbyteCacheTableValidationError(PyAirbyteCacheError): 280 """Cache table validation failed.""" 281 282 violation: str | None = None
Cache table validation failed.
285@dataclass 286class AirbyteConnectorConfigurationMissingError(PyAirbyteCacheError): 287 """Connector is missing configuration.""" 288 289 connector_name: str | None = None
Connector is missing configuration.
295@dataclass 296class AirbyteSubprocessError(PyAirbyteError): 297 """Error when running subprocess.""" 298 299 run_args: list[str] | None = None
Error when running subprocess.
302@dataclass 303class AirbyteSubprocessFailedError(AirbyteSubprocessError): 304 """Subprocess failed.""" 305 306 exit_code: int | None = None
Subprocess failed.
312class AirbyteConnectorRegistryError(PyAirbyteError): 313 """Error when accessing the connector registry."""
Error when accessing the connector registry.
316@dataclass 317class AirbyteConnectorNotRegisteredError(AirbyteConnectorRegistryError): 318 """Connector not found in registry.""" 319 320 connector_name: str | None = None 321 guidance = ( 322 "Please double check the connector name. " 323 "Alternatively, you can provide an explicit connector install method to `get_source()`: " 324 "`pip_url`, `local_executable`, `docker_image`, or `source_manifest`." 325 ) 326 help_url = DOCS_URL_BASE + "/airbyte/sources/util.html#get_source"
Connector not found in registry.
Inherited Members
329@dataclass 330class AirbyteConnectorNotPyPiPublishedError(AirbyteConnectorRegistryError): 331 """Connector found, but not published to PyPI.""" 332 333 connector_name: str | None = None 334 guidance = "This likely means that the connector is not ready for use with PyAirbyte."
Connector found, but not published to PyPI.
340@dataclass 341class AirbyteConnectorError(PyAirbyteError): 342 """Error when running the connector.""" 343 344 connector_name: str | None = None 345 346 def __post_init__(self) -> None: 347 """Set the log file path for the connector.""" 348 self.log_file = self._get_log_file() 349 if not self.guidance and self.log_file: 350 self.guidance = "Please review the log file for more information." 351 352 def _get_log_file(self) -> Path | None: 353 """Return the log file path for the connector.""" 354 if self.connector_name: 355 logger = logging.getLogger(f"airbyte.{self.connector_name}") 356 357 log_paths: list[Path] = [ 358 Path(handler.baseFilename).absolute() 359 for handler in logger.handlers 360 if isinstance(handler, logging.FileHandler) 361 ] 362 363 if log_paths: 364 return log_paths[0] 365 366 return None
Error when running the connector.
369class AirbyteConnectorExecutableNotFoundError(AirbyteConnectorError): 370 """Connector executable not found."""
Connector executable not found.
373class AirbyteConnectorInstallationError(AirbyteConnectorError): 374 """Error when installing the connector."""
Error when installing the connector.
377class AirbyteConnectorReadError(AirbyteConnectorError): 378 """Error when reading from the connector."""
Error when reading from the connector.
381class AirbyteConnectorWriteError(AirbyteConnectorError): 382 """Error when writing to the connector."""
Error when writing to the connector.
385class AirbyteConnectorSpecFailedError(AirbyteConnectorError): 386 """Error when getting spec from the connector."""
Error when getting spec from the connector.
389class AirbyteConnectorDiscoverFailedError(AirbyteConnectorError): 390 """Error when running discovery on the connector."""
Error when running discovery on the connector.
393class AirbyteNoDataFromConnectorError(AirbyteConnectorError): 394 """No data was provided from the connector."""
No data was provided from the connector.
397class AirbyteConnectorMissingCatalogError(AirbyteConnectorError): 398 """Connector did not return a catalog."""
Connector did not return a catalog.
401class AirbyteConnectorMissingSpecError(AirbyteConnectorError): 402 """Connector did not return a spec."""
Connector did not return a spec.
405class AirbyteConnectorValidationFailedError(AirbyteConnectorError): 406 """Connector config validation failed.""" 407 408 guidance = ( 409 "Please double-check your config and review the validation errors for more information." 410 )
Connector config validation failed.
413class AirbyteConnectorCheckFailedError(AirbyteConnectorError): 414 """Connector check failed.""" 415 416 guidance = ( 417 "Please double-check your config or review the connector's logs for more information." 418 )
Connector check failed.
421@dataclass 422class AirbyteConnectorFailedError(AirbyteConnectorError): 423 """Connector failed.""" 424 425 exit_code: int | None = None
Connector failed.
428@dataclass 429class AirbyteStreamNotFoundError(AirbyteConnectorError): 430 """Connector stream not found.""" 431 432 stream_name: str | None = None 433 available_streams: list[str] | None = None
Connector stream not found.
436@dataclass 437class AirbyteStateNotFoundError(AirbyteConnectorError, KeyError): 438 """State entry not found.""" 439 440 stream_name: str | None = None 441 available_streams: list[str] | None = None
State entry not found.
444@dataclass 445class PyAirbyteSecretNotFoundError(PyAirbyteError): 446 """Secret not found.""" 447 448 guidance = "Please ensure that the secret is set." 449 help_url = ( 450 "https://docs.airbyte.com/using-airbyte/airbyte-lib/getting-started#secrets-management" 451 ) 452 453 secret_name: str | None = None 454 sources: list[str] | None = None
Secret not found.
Inherited Members
460@dataclass 461class AirbyteError(PyAirbyteError): 462 """An error occurred while communicating with the hosted Airbyte instance.""" 463 464 response: AirbyteApiResponseDuckType | None = None 465 """The API response from the failed request.""" 466 467 workspace: CloudWorkspace | None = None 468 """The workspace where the error occurred.""" 469 470 @property 471 def workspace_url(self) -> str | None: 472 """The URL to the workspace where the error occurred.""" 473 if self.workspace: 474 return self.workspace.workspace_url 475 476 return None
An error occurred while communicating with the hosted Airbyte instance.
The API response from the failed request.
479@dataclass 480class AirbyteConnectionError(AirbyteError): 481 """An connection error occurred while communicating with the hosted Airbyte instance.""" 482 483 connection_id: str | None = None 484 """The connection ID where the error occurred.""" 485 486 job_id: int | None = None 487 """The job ID where the error occurred (if applicable).""" 488 489 job_status: str | None = None 490 """The latest status of the job where the error occurred (if applicable).""" 491 492 @property 493 def connection_url(self) -> str | None: 494 """The web URL to the connection where the error occurred.""" 495 if self.workspace_url and self.connection_id: 496 return f"{self.workspace_url}/connections/{self.connection_id}" 497 498 return None 499 500 @property 501 def job_history_url(self) -> str | None: 502 """The URL to the job history where the error occurred.""" 503 if self.connection_url: 504 return f"{self.connection_url}/timeline" 505 506 return None 507 508 @property 509 def job_url(self) -> str | None: 510 """The URL to the job where the error occurred.""" 511 if self.job_history_url and self.job_id: 512 return f"{self.job_history_url}#{self.job_id}::0" 513 514 return None
An connection error occurred while communicating with the hosted Airbyte instance.
The latest status of the job where the error occurred (if applicable).
492 @property 493 def connection_url(self) -> str | None: 494 """The web URL to the connection where the error occurred.""" 495 if self.workspace_url and self.connection_id: 496 return f"{self.workspace_url}/connections/{self.connection_id}" 497 498 return None
The web URL to the connection where the error occurred.
500 @property 501 def job_history_url(self) -> str | None: 502 """The URL to the job history where the error occurred.""" 503 if self.connection_url: 504 return f"{self.connection_url}/timeline" 505 506 return None
The URL to the job history where the error occurred.
517@dataclass 518class AirbyteConnectionSyncError(AirbyteConnectionError): 519 """An error occurred while executing the remote Airbyte job."""
An error occurred while executing the remote Airbyte job.
522@dataclass 523class AirbyteConnectionSyncActiveError(AirbyteConnectionError): 524 """State update rejected because a sync is currently running (HTTP 423)."""
State update rejected because a sync is currently running (HTTP 423).
527@dataclass 528class AirbyteWorkspaceMismatchError(AirbyteError): 529 """Resource does not belong to the expected workspace. 530 531 This error is raised when a resource (connection, source, or destination) is fetched 532 from the API and the workspace ID in the response does not match the expected workspace. 533 """ 534 535 resource_type: str | None = None 536 """The type of resource (e.g., 'connection', 'source', 'destination').""" 537 538 resource_id: str | None = None 539 """The ID of the resource that was fetched.""" 540 541 expected_workspace_id: str | None = None 542 """The workspace ID that was expected.""" 543 544 actual_workspace_id: str | None = None 545 """The workspace ID returned by the API."""
Resource does not belong to the expected workspace.
This error is raised when a resource (connection, source, or destination) is fetched from the API and the workspace ID in the response does not match the expected workspace.
548@dataclass 549class AirbyteWorkspaceNotEmptyError(AirbyteError): 550 """Workspace cannot be deleted because it contains connections.""" 551 552 workspace_id: str | None = None 553 """The workspace ID that was expected to be empty.""" 554 555 connection_ids: list[str] | None = None 556 """The connection IDs found in the workspace."""
Workspace cannot be deleted because it contains connections.
559@dataclass 560class AirbyteConnectionSyncTimeoutError(AirbyteConnectionSyncError): 561 """An timeout occurred while waiting for the remote Airbyte job to complete.""" 562 563 timeout: int | None = None 564 """The timeout in seconds that was reached."""
An timeout occurred while waiting for the remote Airbyte job to complete.
570@dataclass 571class AirbyteMissingResourceError(AirbyteError): 572 """Remote Airbyte resources does not exist.""" 573 574 resource_type: str | None = None 575 resource_name_or_id: str | None = None
Remote Airbyte resources does not exist.
578@dataclass 579class AirbyteDuplicateResourcesError(AirbyteError): 580 """Process failed because resource name was not unique.""" 581 582 resource_type: str | None = None 583 resource_name: str | None = None
Process failed because resource name was not unique.
587@dataclass 588class AirbyteMultipleResourcesError(AirbyteError): 589 """Could not locate the resource because multiple matching resources were found.""" 590 591 resource_type: str | None = None 592 resource_name_or_id: str | None = None
Could not locate the resource because multiple matching resources were found.
598class AirbyteExperimentalFeatureWarning(FutureWarning): 599 """Warning whenever using experimental features in PyAirbyte."""
Warning whenever using experimental features in PyAirbyte.
General warnings from PyAirbyte.
609class PyAirbyteDataLossWarning(PyAirbyteWarning): 610 """Warning for potential data loss. 611 612 Users can ignore this warning by running: 613 > warnings.filterwarnings("ignore", category="airbyte.exceptions.PyAirbyteDataLossWarning") 614 """
Warning for potential data loss.
Users can ignore this warning by running:
warnings.filterwarnings("ignore", category="airbyte.exceptions.PyAirbyteDataLossWarning")