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 context dict 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, Protocol
 46
 47from airbyte.constants import (
 48    AIRBYTE_PRINT_FULL_ERROR_LOGS,
 49    CLOUD_BEARER_TOKEN_ENV_VAR,
 50    CLOUD_CLIENT_ID_ENV_VAR,
 51    CLOUD_CLIENT_SECRET_ENV_VAR,
 52    CLOUD_WORKSPACE_ID_ENV_VAR,
 53    MCP_BEARER_TOKEN_HEADER,
 54    MCP_WORKSPACE_ID_HEADER,
 55    is_hosted_mcp_mode,
 56)
 57
 58
 59if TYPE_CHECKING:
 60    from airbyte._util.api_duck_types import AirbyteApiResponseDuckType
 61
 62
 63NEW_ISSUE_URL = "https://github.com/airbytehq/airbyte/issues/new/choose"
 64DOCS_URL_BASE = "https://airbytehq.github.io/PyAirbyte"
 65DOCS_URL = f"{DOCS_URL_BASE}/airbyte.html"
 66
 67VERTICAL_SEPARATOR = "\n" + "-" * 60
 68
 69
 70# Base error class
 71
 72
 73@dataclass
 74class PyAirbyteError(Exception):
 75    """Base class for exceptions in Airbyte."""
 76
 77    guidance: str | None = None
 78    help_url: str | None = None
 79    log_text: str | list[str] | None = None
 80    log_file: Path | None = None
 81    print_full_log: bool = AIRBYTE_PRINT_FULL_ERROR_LOGS
 82    context: dict[str, Any] | None = None
 83    message: str | None = None
 84    original_exception: Exception | None = None
 85
 86    def get_message(self) -> str:
 87        """Return the best description for the exception.
 88
 89        We resolve the following in order:
 90        1. The message sent to the exception constructor (if provided).
 91        2. The first line of the class's docstring.
 92        """
 93        if self.message:
 94            return self.message
 95
 96        return self.__doc__.split("\n")[0] if self.__doc__ else ""
 97
 98    def __str__(self) -> str:
 99        """Return a string representation of the exception."""
100        special_properties = [
101            "message",
102            "guidance",
103            "help_url",
104            "log_text",
105            "context",
106            "log_file",
107            "print_full_log",
108            "original_exception",
109        ]
110        display_properties = {
111            k: v
112            for k, v in self.__dict__.items()
113            if k not in special_properties and not k.startswith("_") and v is not None
114        }
115        display_properties.update(self.context or {})
116        context_str = "\n    ".join(
117            f"{str(k).replace('_', ' ').title()}: {v!r}" for k, v in display_properties.items()
118        )
119        exception_str = (
120            f"{self.get_message()} ({self.__class__.__name__})"
121            + VERTICAL_SEPARATOR
122            + f"\n{self.__class__.__name__}: {self.get_message()}"
123        )
124
125        if self.guidance:
126            exception_str += f"\n    {self.guidance}"
127
128        if self.help_url:
129            exception_str += f"\n    More info: {self.help_url}"
130
131        if context_str:
132            exception_str += "\n    " + context_str
133
134        if self.log_text:
135            if isinstance(self.log_text, list):
136                self.log_text = "\n".join(self.log_text)
137
138            exception_str += f"\n    Log output: \n    {indent(self.log_text, '    ')}"
139
140        if self.original_exception:
141            exception_str += VERTICAL_SEPARATOR + f"\nCaused by: {self.original_exception!s}"
142
143        if self.log_file:
144            if self.print_full_log:
145                if not self.log_file.is_file():
146                    exception_str += f"\n    No log file found at: {self.log_file.absolute()!s}"
147
148                else:
149                    try:
150                        full_log_file_text = self.log_file.read_text()
151                    except Exception as ex:
152                        full_log_file_text = (
153                            f"[ERROR] Log file could not be read from: {self.log_file.absolute()!s}"
154                            f"\nRead error: {ex!s}"
155                        )
156
157                    exception_str += (
158                        f"\n    Full log file text from {self.log_file.absolute()!s}:"
159                        + VERTICAL_SEPARATOR
160                        + full_log_file_text
161                        + VERTICAL_SEPARATOR
162                    )
163            else:
164                exception_str += f"\n    Log file: {self.log_file.absolute()!s}"
165        return exception_str
166
167    def __repr__(self) -> str:
168        """Return a string representation of the exception."""
169        class_name = self.__class__.__name__
170        properties_str = ", ".join(
171            f"{k}={v!r}" for k, v in self.__dict__.items() if not k.startswith("_")
172        )
173        return f"{class_name}({properties_str})"
174
175    def safe_logging_dict(self) -> dict[str, Any]:
176        """Return a dictionary of the exception's properties which is safe for logging.
177
178        We avoid any properties which could potentially contain PII.
179        """
180        result = {
181            # The class name is safe to log:
182            "class": self.__class__.__name__,
183            # We discourage interpolated strings in 'message' so that this should never contain PII:
184            "message": self.get_message(),
185        }
186        safe_attrs = ["connector_name", "stream_name", "violation", "exit_code"]
187        for attr in safe_attrs:
188            if hasattr(self, attr):
189                result[attr] = getattr(self, attr)
190
191        return result
192
193
194# PyAirbyte Internal Errors (these are probably bugs)
195
196
197@dataclass
198class PyAirbyteInternalError(PyAirbyteError):
199    """An internal error occurred in PyAirbyte."""
200
201    guidance = "Please consider reporting this error to the Airbyte team."
202    help_url = NEW_ISSUE_URL
203
204
205# PyAirbyte Input Errors (replaces ValueError for user input)
206
207
208@dataclass
209class PyAirbyteInputError(PyAirbyteError, ValueError):
210    """The input provided to PyAirbyte did not match expected validation rules.
211
212    This inherits from ValueError so that it can be used as a drop-in replacement for
213    ValueError in the PyAirbyte API.
214    """
215
216    guidance = "Please check the provided value and try again."
217    help_url = DOCS_URL
218    input_value: str | None = None
219
220
221@dataclass
222class PyAirbyteNoStreamsSelectedError(PyAirbyteInputError):
223    """No streams were selected for the source."""
224
225    guidance = (
226        "Please call `select_streams()` to select at least one stream from the list provided. "
227        "You can also call `select_all_streams()` to select all available streams for this source."
228    )
229    connector_name: str | None = None
230    available_streams: list[str] | None = None
231
232
233@dataclass
234class AirbyteNoCloudCredentialsError(PyAirbyteInputError):
235    """No Airbyte credentials found."""
236
237    guidance: str | None = None
238    _allow_bearer: bool = True
239    _env_vars: bool = True
240
241    def __post_init__(self) -> None:
242        """Set guidance for the current execution mode."""
243        if self.guidance is not None:
244            return
245        if is_hosted_mcp_mode():
246            if self._allow_bearer:
247                self.guidance = (
248                    f"Provide a bearer token via the `{MCP_BEARER_TOKEN_HEADER}` header, "
249                    "or client credentials via the transport `Client-Id` and "
250                    "`Client-Secret` headers."
251                )
252            else:
253                self.guidance = (
254                    "Provide client credentials via the transport `Client-Id` and "
255                    "`Client-Secret` headers."
256                )
257        elif self._allow_bearer and self._env_vars:
258            self.guidance = (
259                f"Provide `bearer_token`, or both `client_id` and `client_secret`, as "
260                f"arguments or via the `{CLOUD_BEARER_TOKEN_ENV_VAR}`, "
261                f"`{CLOUD_CLIENT_ID_ENV_VAR}`, and `{CLOUD_CLIENT_SECRET_ENV_VAR}` "
262                "environment variables."
263            )
264        elif self._allow_bearer:
265            self.guidance = "Provide `bearer_token`, or both `client_id` and `client_secret`."
266        elif self._env_vars:
267            self.guidance = (
268                f"Provide both `client_id` and `client_secret`, as arguments or via the "
269                f"`{CLOUD_CLIENT_ID_ENV_VAR}` and `{CLOUD_CLIENT_SECRET_ENV_VAR}` "
270                "environment variables."
271            )
272        else:
273            self.guidance = "Provide both `client_id` and `client_secret`."
274
275
276@dataclass
277class AirbyteMissingWorkspaceContextError(PyAirbyteInputError):
278    """Workspace ID is required but not provided."""
279
280    guidance: str | None = None
281
282    def __post_init__(self) -> None:
283        """Set guidance for the current execution mode."""
284        if self.guidance is not None:
285            return
286        if is_hosted_mcp_mode():
287            self.guidance = (
288                "Call `list_cloud_organizations` and `list_cloud_workspaces` first. "
289                "If discovery returns exactly one workspace, provide its ID via the "
290                f"`{MCP_WORKSPACE_ID_HEADER}` header or the `workspace_id` parameter; "
291                "otherwise ask the user to choose."
292            )
293        else:
294            self.guidance = (
295                "Call `list_cloud_organizations` and `list_cloud_workspaces` first. "
296                "If discovery returns exactly one workspace, set its ID in "
297                f"`{CLOUD_WORKSPACE_ID_ENV_VAR}` or pass the `workspace_id` parameter; "
298                "otherwise ask the user to choose."
299            )
300
301
302# MCP Server Errors
303
304
305@dataclass
306class AirbyteMCPError(PyAirbyteError):
307    """An error occurred in the Airbyte MCP server."""
308
309
310@dataclass
311class AirbyteTrustedExecutionRequiredError(AirbyteMCPError):
312    """A trusted-execution-only capability was invoked while trusted execution is disabled.
313
314    Trusted execution grants the MCP server its trusted-machine capabilities: local
315    filesystem access, local connector installation/execution, and server-side secret
316    resolution. It defaults to *off* on every transport and is permanently unavailable
317    over the HTTP transport, so a backend helper that exposes one of those capabilities
318    hard-fails when the gate is disabled -- independently of whether the corresponding
319    tool was hidden from the tool listing.
320    """
321
322    guidance = (
323        "Set `AIRBYTE_MCP_TRUSTED_EXECUTION=1` on the MCP server process and restart it. "
324        "Trusted execution is only available on the stdio transport; it can never be "
325        "enabled for an HTTP/hosted deployment."
326    )
327    feature: str | None = None
328
329
330# Normalization Errors
331
332
333@dataclass
334class PyAirbyteNameNormalizationError(PyAirbyteError, ValueError):
335    """Error occurred while normalizing a table or column name."""
336
337    guidance = (
338        "Please consider renaming the source object if possible, or "
339        "raise an issue in GitHub if not."
340    )
341    help_url = NEW_ISSUE_URL
342
343    raw_name: str | None = None
344    normalization_result: str | None = None
345
346
347# PyAirbyte Cache Errors
348
349
350class PyAirbyteCacheError(PyAirbyteError):
351    """Error occurred while accessing the cache."""
352
353
354@dataclass
355class PyAirbyteCacheTableValidationError(PyAirbyteCacheError):
356    """Cache table validation failed."""
357
358    violation: str | None = None
359
360
361@dataclass
362class AirbyteConnectorConfigurationMissingError(PyAirbyteCacheError):
363    """Connector is missing configuration."""
364
365    connector_name: str | None = None
366
367
368# Subprocess Errors
369
370
371@dataclass
372class AirbyteSubprocessError(PyAirbyteError):
373    """Error when running subprocess."""
374
375    run_args: list[str] | None = None
376
377
378@dataclass
379class AirbyteSubprocessFailedError(AirbyteSubprocessError):
380    """Subprocess failed."""
381
382    exit_code: int | None = None
383
384
385# Connector Registry Errors
386
387
388class AirbyteConnectorRegistryError(PyAirbyteError):
389    """Error when accessing the connector registry."""
390
391
392@dataclass
393class AirbyteConnectorNotRegisteredError(AirbyteConnectorRegistryError):
394    """Connector not found in registry."""
395
396    connector_name: str | None = None
397    guidance = (
398        "Please double check the connector name. "
399        "Alternatively, you can provide an explicit connector install method to `get_source()`: "
400        "`pip_url`, `local_executable`, `docker_image`, or `source_manifest`."
401    )
402    help_url = DOCS_URL_BASE + "/airbyte/sources/util.html#get_source"
403
404
405@dataclass
406class AirbyteConnectorNotPyPiPublishedError(AirbyteConnectorRegistryError):
407    """Connector found, but not published to PyPI."""
408
409    connector_name: str | None = None
410    guidance = "This likely means that the connector is not ready for use with PyAirbyte."
411
412
413# Connector Errors
414
415
416@dataclass
417class AirbyteConnectorError(PyAirbyteError):
418    """Error when running the connector."""
419
420    connector_name: str | None = None
421
422    def __post_init__(self) -> None:
423        """Set the log file path for the connector."""
424        self.log_file = self._get_log_file()
425        if not self.guidance and self.log_file:
426            self.guidance = "Please review the log file for more information."
427
428    def _get_log_file(self) -> Path | None:
429        """Return the log file path for the connector."""
430        if self.connector_name:
431            logger = logging.getLogger(f"airbyte.{self.connector_name}")
432
433            log_paths: list[Path] = [
434                Path(handler.baseFilename).absolute()
435                for handler in logger.handlers
436                if isinstance(handler, logging.FileHandler)
437            ]
438
439            if log_paths:
440                return log_paths[0]
441
442        return None
443
444
445class AirbyteConnectorExecutableNotFoundError(AirbyteConnectorError):
446    """Connector executable not found."""
447
448
449class AirbyteConnectorInstallationError(AirbyteConnectorError):
450    """Error when installing the connector."""
451
452
453class AirbyteConnectorReadError(AirbyteConnectorError):
454    """Error when reading from the connector."""
455
456
457class AirbyteConnectorWriteError(AirbyteConnectorError):
458    """Error when writing to the connector."""
459
460
461class AirbyteConnectorSpecFailedError(AirbyteConnectorError):
462    """Error when getting spec from the connector."""
463
464
465class AirbyteConnectorDiscoverFailedError(AirbyteConnectorError):
466    """Error when running discovery on the connector."""
467
468
469class AirbyteNoDataFromConnectorError(AirbyteConnectorError):
470    """No data was provided from the connector."""
471
472
473class AirbyteConnectorMissingCatalogError(AirbyteConnectorError):
474    """Connector did not return a catalog."""
475
476
477class AirbyteConnectorMissingSpecError(AirbyteConnectorError):
478    """Connector did not return a spec."""
479
480
481class AirbyteConnectorValidationFailedError(AirbyteConnectorError):
482    """Connector config validation failed."""
483
484    guidance = (
485        "Please double-check your config and review the validation errors for more information."
486    )
487
488
489class AirbyteConnectorCheckFailedError(AirbyteConnectorError):
490    """Connector check failed."""
491
492    guidance = (
493        "Please double-check your config or review the connector's logs for more information."
494    )
495
496
497@dataclass
498class AirbyteConnectorFailedError(AirbyteConnectorError):
499    """Connector failed."""
500
501    exit_code: int | None = None
502
503
504@dataclass
505class AirbyteStreamNotFoundError(AirbyteConnectorError):
506    """Connector stream not found."""
507
508    stream_name: str | None = None
509    available_streams: list[str] | None = None
510
511
512@dataclass
513class AirbyteStateNotFoundError(AirbyteConnectorError, KeyError):
514    """State entry not found."""
515
516    stream_name: str | None = None
517    available_streams: list[str] | None = None
518
519
520@dataclass
521class PyAirbyteSecretNotFoundError(PyAirbyteError):
522    """Secret not found."""
523
524    guidance = "Please ensure that the secret is set."
525    help_url = (
526        "https://docs.airbyte.com/using-airbyte/airbyte-lib/getting-started#secrets-management"
527    )
528
529    secret_name: str | None = None
530    sources: list[str] | None = None
531
532
533# Airbyte API Errors
534
535
536class _WorkspaceWithUrl(Protocol):
537    """Structural type for a workspace that exposes a `workspace_url`.
538
539    Declared locally so `exceptions` does not need to import `airbyte.cloud`, which
540    would create an import cycle. Any object with a `workspace_url` attribute (e.g.
541    `CloudWorkspace`) satisfies this via structural (duck) typing.
542    """
543
544    @property
545    def workspace_url(self) -> str | None:
546        """The web URL of the workspace."""
547
548
549@dataclass
550class AirbyteError(PyAirbyteError):
551    """An error occurred while communicating with the hosted Airbyte instance."""
552
553    response: AirbyteApiResponseDuckType | None = None
554    """The API response from the failed request."""
555
556    workspace: _WorkspaceWithUrl | None = None
557    """The workspace where the error occurred."""
558
559    @property
560    def workspace_url(self) -> str | None:
561        """The URL to the workspace where the error occurred."""
562        if self.workspace:
563            return self.workspace.workspace_url
564
565        return None
566
567
568@dataclass
569class AirbyteConnectionError(AirbyteError):
570    """An connection error occurred while communicating with the hosted Airbyte instance."""
571
572    connection_id: str | None = None
573    """The connection ID where the error occurred."""
574
575    job_id: int | None = None
576    """The job ID where the error occurred (if applicable)."""
577
578    job_status: str | None = None
579    """The latest status of the job where the error occurred (if applicable)."""
580
581    @property
582    def connection_url(self) -> str | None:
583        """The web URL to the connection where the error occurred."""
584        if self.workspace_url and self.connection_id:
585            return f"{self.workspace_url}/connections/{self.connection_id}"
586
587        return None
588
589    @property
590    def job_history_url(self) -> str | None:
591        """The URL to the job history where the error occurred."""
592        if self.connection_url:
593            return f"{self.connection_url}/timeline"
594
595        return None
596
597    @property
598    def job_url(self) -> str | None:
599        """The URL to the job where the error occurred."""
600        if self.job_history_url and self.job_id:
601            return f"{self.job_history_url}#{self.job_id}::0"
602
603        return None
604
605
606@dataclass
607class AirbyteConnectionSyncError(AirbyteConnectionError):
608    """An error occurred while executing the remote Airbyte job."""
609
610
611@dataclass
612class AirbyteConnectionSyncActiveError(AirbyteConnectionError):
613    """State update rejected because a sync is currently running (HTTP 423)."""
614
615
616@dataclass
617class AirbyteWorkspaceMismatchError(AirbyteError):
618    """Resource does not belong to the expected workspace.
619
620    This error is raised when a resource (connection, source, or destination) is fetched
621    from the API and the workspace ID in the response does not match the expected workspace.
622    """
623
624    resource_type: str | None = None
625    """The type of resource (e.g., 'connection', 'source', 'destination')."""
626
627    resource_id: str | None = None
628    """The ID of the resource that was fetched."""
629
630    expected_workspace_id: str | None = None
631    """The workspace ID that was expected."""
632
633    actual_workspace_id: str | None = None
634    """The workspace ID returned by the API."""
635
636
637@dataclass
638class AirbyteWorkspaceNotEmptyError(AirbyteError):
639    """Workspace cannot be deleted because it contains connections."""
640
641    workspace_id: str | None = None
642    """The workspace ID that was expected to be empty."""
643
644    connection_ids: list[str] | None = None
645    """The connection IDs found in the workspace."""
646
647
648@dataclass
649class AirbyteConnectionSyncTimeoutError(AirbyteConnectionSyncError):
650    """An timeout occurred while waiting for the remote Airbyte job to complete."""
651
652    timeout: int | None = None
653    """The timeout in seconds that was reached."""
654
655
656# Airbyte Resource Errors (General)
657
658
659@dataclass
660class AirbyteMissingResourceError(AirbyteError):
661    """Remote Airbyte resources does not exist."""
662
663    resource_type: str | None = None
664    resource_name_or_id: str | None = None
665
666
667@dataclass
668class AirbyteDuplicateResourcesError(AirbyteError):
669    """Process failed because resource name was not unique."""
670
671    resource_type: str | None = None
672    resource_name: str | None = None
673
674
675# Custom Warnings
676@dataclass
677class AirbyteMultipleResourcesError(AirbyteError):
678    """Could not locate the resource because multiple matching resources were found."""
679
680    resource_type: str | None = None
681    resource_name_or_id: str | None = None
682
683
684# Custom Warnings
685
686
687class AirbyteExperimentalFeatureWarning(FutureWarning):
688    """Warning whenever using experimental features in PyAirbyte."""
689
690
691# PyAirbyte Warnings
692
693
694class PyAirbyteWarning(Warning):
695    """General warnings from PyAirbyte."""
696
697
698class PyAirbyteDataLossWarning(PyAirbyteWarning):
699    """Warning for potential data loss.
700
701    Users can ignore this warning by running:
702    > warnings.filterwarnings("ignore", category="airbyte.exceptions.PyAirbyteDataLossWarning")
703    """
NEW_ISSUE_URL = 'https://github.com/airbytehq/airbyte/issues/new/choose'
DOCS_URL_BASE = 'https://airbytehq.github.io/PyAirbyte'
DOCS_URL = 'https://airbytehq.github.io/PyAirbyte/airbyte.html'
VERTICAL_SEPARATOR = '\n------------------------------------------------------------'
@dataclass
class PyAirbyteError(builtins.Exception):
 74@dataclass
 75class PyAirbyteError(Exception):
 76    """Base class for exceptions in Airbyte."""
 77
 78    guidance: str | None = None
 79    help_url: str | None = None
 80    log_text: str | list[str] | None = None
 81    log_file: Path | None = None
 82    print_full_log: bool = AIRBYTE_PRINT_FULL_ERROR_LOGS
 83    context: dict[str, Any] | None = None
 84    message: str | None = None
 85    original_exception: Exception | None = None
 86
 87    def get_message(self) -> str:
 88        """Return the best description for the exception.
 89
 90        We resolve the following in order:
 91        1. The message sent to the exception constructor (if provided).
 92        2. The first line of the class's docstring.
 93        """
 94        if self.message:
 95            return self.message
 96
 97        return self.__doc__.split("\n")[0] if self.__doc__ else ""
 98
 99    def __str__(self) -> str:
100        """Return a string representation of the exception."""
101        special_properties = [
102            "message",
103            "guidance",
104            "help_url",
105            "log_text",
106            "context",
107            "log_file",
108            "print_full_log",
109            "original_exception",
110        ]
111        display_properties = {
112            k: v
113            for k, v in self.__dict__.items()
114            if k not in special_properties and not k.startswith("_") and v is not None
115        }
116        display_properties.update(self.context or {})
117        context_str = "\n    ".join(
118            f"{str(k).replace('_', ' ').title()}: {v!r}" for k, v in display_properties.items()
119        )
120        exception_str = (
121            f"{self.get_message()} ({self.__class__.__name__})"
122            + VERTICAL_SEPARATOR
123            + f"\n{self.__class__.__name__}: {self.get_message()}"
124        )
125
126        if self.guidance:
127            exception_str += f"\n    {self.guidance}"
128
129        if self.help_url:
130            exception_str += f"\n    More info: {self.help_url}"
131
132        if context_str:
133            exception_str += "\n    " + context_str
134
135        if self.log_text:
136            if isinstance(self.log_text, list):
137                self.log_text = "\n".join(self.log_text)
138
139            exception_str += f"\n    Log output: \n    {indent(self.log_text, '    ')}"
140
141        if self.original_exception:
142            exception_str += VERTICAL_SEPARATOR + f"\nCaused by: {self.original_exception!s}"
143
144        if self.log_file:
145            if self.print_full_log:
146                if not self.log_file.is_file():
147                    exception_str += f"\n    No log file found at: {self.log_file.absolute()!s}"
148
149                else:
150                    try:
151                        full_log_file_text = self.log_file.read_text()
152                    except Exception as ex:
153                        full_log_file_text = (
154                            f"[ERROR] Log file could not be read from: {self.log_file.absolute()!s}"
155                            f"\nRead error: {ex!s}"
156                        )
157
158                    exception_str += (
159                        f"\n    Full log file text from {self.log_file.absolute()!s}:"
160                        + VERTICAL_SEPARATOR
161                        + full_log_file_text
162                        + VERTICAL_SEPARATOR
163                    )
164            else:
165                exception_str += f"\n    Log file: {self.log_file.absolute()!s}"
166        return exception_str
167
168    def __repr__(self) -> str:
169        """Return a string representation of the exception."""
170        class_name = self.__class__.__name__
171        properties_str = ", ".join(
172            f"{k}={v!r}" for k, v in self.__dict__.items() if not k.startswith("_")
173        )
174        return f"{class_name}({properties_str})"
175
176    def safe_logging_dict(self) -> dict[str, Any]:
177        """Return a dictionary of the exception's properties which is safe for logging.
178
179        We avoid any properties which could potentially contain PII.
180        """
181        result = {
182            # The class name is safe to log:
183            "class": self.__class__.__name__,
184            # We discourage interpolated strings in 'message' so that this should never contain PII:
185            "message": self.get_message(),
186        }
187        safe_attrs = ["connector_name", "stream_name", "violation", "exit_code"]
188        for attr in safe_attrs:
189            if hasattr(self, attr):
190                result[attr] = getattr(self, attr)
191
192        return result

Base class for exceptions in Airbyte.

PyAirbyteError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None)
guidance: str | None = None
help_url: str | None = None
log_text: str | list[str] | None = None
log_file: pathlib.Path | None = None
print_full_log: bool = True
context: dict[str, typing.Any] | None = None
message: str | None = None
original_exception: Exception | None = None
def get_message(self) -> str:
87    def get_message(self) -> str:
88        """Return the best description for the exception.
89
90        We resolve the following in order:
91        1. The message sent to the exception constructor (if provided).
92        2. The first line of the class's docstring.
93        """
94        if self.message:
95            return self.message
96
97        return self.__doc__.split("\n")[0] if self.__doc__ else ""

Return the best description for the exception.

We resolve the following in order:

  1. The message sent to the exception constructor (if provided).
  2. The first line of the class's docstring.
def safe_logging_dict(self) -> dict[str, typing.Any]:
176    def safe_logging_dict(self) -> dict[str, Any]:
177        """Return a dictionary of the exception's properties which is safe for logging.
178
179        We avoid any properties which could potentially contain PII.
180        """
181        result = {
182            # The class name is safe to log:
183            "class": self.__class__.__name__,
184            # We discourage interpolated strings in 'message' so that this should never contain PII:
185            "message": self.get_message(),
186        }
187        safe_attrs = ["connector_name", "stream_name", "violation", "exit_code"]
188        for attr in safe_attrs:
189            if hasattr(self, attr):
190                result[attr] = getattr(self, attr)
191
192        return result

Return a dictionary of the exception's properties which is safe for logging.

We avoid any properties which could potentially contain PII.

@dataclass
class PyAirbyteInternalError(PyAirbyteError):
198@dataclass
199class PyAirbyteInternalError(PyAirbyteError):
200    """An internal error occurred in PyAirbyte."""
201
202    guidance = "Please consider reporting this error to the Airbyte team."
203    help_url = NEW_ISSUE_URL

An internal error occurred in PyAirbyte.

PyAirbyteInternalError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None)
guidance = 'Please consider reporting this error to the Airbyte team.'
help_url = 'https://github.com/airbytehq/airbyte/issues/new/choose'
@dataclass
class PyAirbyteInputError(PyAirbyteError, builtins.ValueError):
209@dataclass
210class PyAirbyteInputError(PyAirbyteError, ValueError):
211    """The input provided to PyAirbyte did not match expected validation rules.
212
213    This inherits from ValueError so that it can be used as a drop-in replacement for
214    ValueError in the PyAirbyte API.
215    """
216
217    guidance = "Please check the provided value and try again."
218    help_url = DOCS_URL
219    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.

PyAirbyteInputError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, input_value: str | None = None)
guidance = 'Please check the provided value and try again.'
help_url = 'https://airbytehq.github.io/PyAirbyte/airbyte.html'
input_value: str | None = None
@dataclass
class PyAirbyteNoStreamsSelectedError(PyAirbyteInputError):
222@dataclass
223class PyAirbyteNoStreamsSelectedError(PyAirbyteInputError):
224    """No streams were selected for the source."""
225
226    guidance = (
227        "Please call `select_streams()` to select at least one stream from the list provided. "
228        "You can also call `select_all_streams()` to select all available streams for this source."
229    )
230    connector_name: str | None = None
231    available_streams: list[str] | None = None

No streams were selected for the source.

PyAirbyteNoStreamsSelectedError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, input_value: str | None = None, connector_name: str | None = None, available_streams: list[str] | None = None)
guidance = 'Please call `select_streams()` to select at least one stream from the list provided. You can also call `select_all_streams()` to select all available streams for this source.'
connector_name: str | None = None
available_streams: list[str] | None = None
@dataclass
class AirbyteNoCloudCredentialsError(PyAirbyteInputError):
234@dataclass
235class AirbyteNoCloudCredentialsError(PyAirbyteInputError):
236    """No Airbyte credentials found."""
237
238    guidance: str | None = None
239    _allow_bearer: bool = True
240    _env_vars: bool = True
241
242    def __post_init__(self) -> None:
243        """Set guidance for the current execution mode."""
244        if self.guidance is not None:
245            return
246        if is_hosted_mcp_mode():
247            if self._allow_bearer:
248                self.guidance = (
249                    f"Provide a bearer token via the `{MCP_BEARER_TOKEN_HEADER}` header, "
250                    "or client credentials via the transport `Client-Id` and "
251                    "`Client-Secret` headers."
252                )
253            else:
254                self.guidance = (
255                    "Provide client credentials via the transport `Client-Id` and "
256                    "`Client-Secret` headers."
257                )
258        elif self._allow_bearer and self._env_vars:
259            self.guidance = (
260                f"Provide `bearer_token`, or both `client_id` and `client_secret`, as "
261                f"arguments or via the `{CLOUD_BEARER_TOKEN_ENV_VAR}`, "
262                f"`{CLOUD_CLIENT_ID_ENV_VAR}`, and `{CLOUD_CLIENT_SECRET_ENV_VAR}` "
263                "environment variables."
264            )
265        elif self._allow_bearer:
266            self.guidance = "Provide `bearer_token`, or both `client_id` and `client_secret`."
267        elif self._env_vars:
268            self.guidance = (
269                f"Provide both `client_id` and `client_secret`, as arguments or via the "
270                f"`{CLOUD_CLIENT_ID_ENV_VAR}` and `{CLOUD_CLIENT_SECRET_ENV_VAR}` "
271                "environment variables."
272            )
273        else:
274            self.guidance = "Provide both `client_id` and `client_secret`."

No Airbyte credentials found.

AirbyteNoCloudCredentialsError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, input_value: str | None = None, _allow_bearer: bool = True, _env_vars: bool = True)
guidance: str | None = None
@dataclass
class AirbyteMissingWorkspaceContextError(PyAirbyteInputError):
277@dataclass
278class AirbyteMissingWorkspaceContextError(PyAirbyteInputError):
279    """Workspace ID is required but not provided."""
280
281    guidance: str | None = None
282
283    def __post_init__(self) -> None:
284        """Set guidance for the current execution mode."""
285        if self.guidance is not None:
286            return
287        if is_hosted_mcp_mode():
288            self.guidance = (
289                "Call `list_cloud_organizations` and `list_cloud_workspaces` first. "
290                "If discovery returns exactly one workspace, provide its ID via the "
291                f"`{MCP_WORKSPACE_ID_HEADER}` header or the `workspace_id` parameter; "
292                "otherwise ask the user to choose."
293            )
294        else:
295            self.guidance = (
296                "Call `list_cloud_organizations` and `list_cloud_workspaces` first. "
297                "If discovery returns exactly one workspace, set its ID in "
298                f"`{CLOUD_WORKSPACE_ID_ENV_VAR}` or pass the `workspace_id` parameter; "
299                "otherwise ask the user to choose."
300            )

Workspace ID is required but not provided.

AirbyteMissingWorkspaceContextError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, input_value: str | None = None)
guidance: str | None = None
@dataclass
class AirbyteMCPError(PyAirbyteError):
306@dataclass
307class AirbyteMCPError(PyAirbyteError):
308    """An error occurred in the Airbyte MCP server."""

An error occurred in the Airbyte MCP server.

AirbyteMCPError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None)
@dataclass
class AirbyteTrustedExecutionRequiredError(AirbyteMCPError):
311@dataclass
312class AirbyteTrustedExecutionRequiredError(AirbyteMCPError):
313    """A trusted-execution-only capability was invoked while trusted execution is disabled.
314
315    Trusted execution grants the MCP server its trusted-machine capabilities: local
316    filesystem access, local connector installation/execution, and server-side secret
317    resolution. It defaults to *off* on every transport and is permanently unavailable
318    over the HTTP transport, so a backend helper that exposes one of those capabilities
319    hard-fails when the gate is disabled -- independently of whether the corresponding
320    tool was hidden from the tool listing.
321    """
322
323    guidance = (
324        "Set `AIRBYTE_MCP_TRUSTED_EXECUTION=1` on the MCP server process and restart it. "
325        "Trusted execution is only available on the stdio transport; it can never be "
326        "enabled for an HTTP/hosted deployment."
327    )
328    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.

AirbyteTrustedExecutionRequiredError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, feature: str | None = None)
guidance = 'Set `AIRBYTE_MCP_TRUSTED_EXECUTION=1` on the MCP server process and restart it. Trusted execution is only available on the stdio transport; it can never be enabled for an HTTP/hosted deployment.'
feature: str | None = None
@dataclass
class PyAirbyteNameNormalizationError(PyAirbyteError, builtins.ValueError):
334@dataclass
335class PyAirbyteNameNormalizationError(PyAirbyteError, ValueError):
336    """Error occurred while normalizing a table or column name."""
337
338    guidance = (
339        "Please consider renaming the source object if possible, or "
340        "raise an issue in GitHub if not."
341    )
342    help_url = NEW_ISSUE_URL
343
344    raw_name: str | None = None
345    normalization_result: str | None = None

Error occurred while normalizing a table or column name.

PyAirbyteNameNormalizationError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, raw_name: str | None = None, normalization_result: str | None = None)
guidance = 'Please consider renaming the source object if possible, or raise an issue in GitHub if not.'
help_url = 'https://github.com/airbytehq/airbyte/issues/new/choose'
raw_name: str | None = None
normalization_result: str | None = None
class PyAirbyteCacheError(PyAirbyteError):
351class PyAirbyteCacheError(PyAirbyteError):
352    """Error occurred while accessing the cache."""

Error occurred while accessing the cache.

@dataclass
class PyAirbyteCacheTableValidationError(PyAirbyteCacheError):
355@dataclass
356class PyAirbyteCacheTableValidationError(PyAirbyteCacheError):
357    """Cache table validation failed."""
358
359    violation: str | None = None

Cache table validation failed.

PyAirbyteCacheTableValidationError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, violation: str | None = None)
violation: str | None = None
@dataclass
class AirbyteConnectorConfigurationMissingError(PyAirbyteCacheError):
362@dataclass
363class AirbyteConnectorConfigurationMissingError(PyAirbyteCacheError):
364    """Connector is missing configuration."""
365
366    connector_name: str | None = None

Connector is missing configuration.

AirbyteConnectorConfigurationMissingError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, connector_name: str | None = None)
connector_name: str | None = None
@dataclass
class AirbyteSubprocessError(PyAirbyteError):
372@dataclass
373class AirbyteSubprocessError(PyAirbyteError):
374    """Error when running subprocess."""
375
376    run_args: list[str] | None = None

Error when running subprocess.

AirbyteSubprocessError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, run_args: list[str] | None = None)
run_args: list[str] | None = None
@dataclass
class AirbyteSubprocessFailedError(AirbyteSubprocessError):
379@dataclass
380class AirbyteSubprocessFailedError(AirbyteSubprocessError):
381    """Subprocess failed."""
382
383    exit_code: int | None = None

Subprocess failed.

AirbyteSubprocessFailedError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, run_args: list[str] | None = None, exit_code: int | None = None)
exit_code: int | None = None
class AirbyteConnectorRegistryError(PyAirbyteError):
389class AirbyteConnectorRegistryError(PyAirbyteError):
390    """Error when accessing the connector registry."""

Error when accessing the connector registry.

@dataclass
class AirbyteConnectorNotRegisteredError(AirbyteConnectorRegistryError):
393@dataclass
394class AirbyteConnectorNotRegisteredError(AirbyteConnectorRegistryError):
395    """Connector not found in registry."""
396
397    connector_name: str | None = None
398    guidance = (
399        "Please double check the connector name. "
400        "Alternatively, you can provide an explicit connector install method to `get_source()`: "
401        "`pip_url`, `local_executable`, `docker_image`, or `source_manifest`."
402    )
403    help_url = DOCS_URL_BASE + "/airbyte/sources/util.html#get_source"

Connector not found in registry.

AirbyteConnectorNotRegisteredError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, connector_name: str | None = None)
connector_name: str | None = None
guidance = 'Please double check the connector name. Alternatively, you can provide an explicit connector install method to `get_source()`: `pip_url`, `local_executable`, `docker_image`, or `source_manifest`.'
help_url = 'https://airbytehq.github.io/PyAirbyte/airbyte/sources/util.html#get_source'
@dataclass
class AirbyteConnectorNotPyPiPublishedError(AirbyteConnectorRegistryError):
406@dataclass
407class AirbyteConnectorNotPyPiPublishedError(AirbyteConnectorRegistryError):
408    """Connector found, but not published to PyPI."""
409
410    connector_name: str | None = None
411    guidance = "This likely means that the connector is not ready for use with PyAirbyte."

Connector found, but not published to PyPI.

AirbyteConnectorNotPyPiPublishedError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, connector_name: str | None = None)
connector_name: str | None = None
guidance = 'This likely means that the connector is not ready for use with PyAirbyte.'
@dataclass
class AirbyteConnectorError(PyAirbyteError):
417@dataclass
418class AirbyteConnectorError(PyAirbyteError):
419    """Error when running the connector."""
420
421    connector_name: str | None = None
422
423    def __post_init__(self) -> None:
424        """Set the log file path for the connector."""
425        self.log_file = self._get_log_file()
426        if not self.guidance and self.log_file:
427            self.guidance = "Please review the log file for more information."
428
429    def _get_log_file(self) -> Path | None:
430        """Return the log file path for the connector."""
431        if self.connector_name:
432            logger = logging.getLogger(f"airbyte.{self.connector_name}")
433
434            log_paths: list[Path] = [
435                Path(handler.baseFilename).absolute()
436                for handler in logger.handlers
437                if isinstance(handler, logging.FileHandler)
438            ]
439
440            if log_paths:
441                return log_paths[0]
442
443        return None

Error when running the connector.

AirbyteConnectorError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, connector_name: str | None = None)
connector_name: str | None = None
class AirbyteConnectorExecutableNotFoundError(AirbyteConnectorError):
446class AirbyteConnectorExecutableNotFoundError(AirbyteConnectorError):
447    """Connector executable not found."""

Connector executable not found.

class AirbyteConnectorInstallationError(AirbyteConnectorError):
450class AirbyteConnectorInstallationError(AirbyteConnectorError):
451    """Error when installing the connector."""

Error when installing the connector.

class AirbyteConnectorReadError(AirbyteConnectorError):
454class AirbyteConnectorReadError(AirbyteConnectorError):
455    """Error when reading from the connector."""

Error when reading from the connector.

class AirbyteConnectorWriteError(AirbyteConnectorError):
458class AirbyteConnectorWriteError(AirbyteConnectorError):
459    """Error when writing to the connector."""

Error when writing to the connector.

class AirbyteConnectorSpecFailedError(AirbyteConnectorError):
462class AirbyteConnectorSpecFailedError(AirbyteConnectorError):
463    """Error when getting spec from the connector."""

Error when getting spec from the connector.

class AirbyteConnectorDiscoverFailedError(AirbyteConnectorError):
466class AirbyteConnectorDiscoverFailedError(AirbyteConnectorError):
467    """Error when running discovery on the connector."""

Error when running discovery on the connector.

class AirbyteNoDataFromConnectorError(AirbyteConnectorError):
470class AirbyteNoDataFromConnectorError(AirbyteConnectorError):
471    """No data was provided from the connector."""

No data was provided from the connector.

class AirbyteConnectorMissingCatalogError(AirbyteConnectorError):
474class AirbyteConnectorMissingCatalogError(AirbyteConnectorError):
475    """Connector did not return a catalog."""

Connector did not return a catalog.

class AirbyteConnectorMissingSpecError(AirbyteConnectorError):
478class AirbyteConnectorMissingSpecError(AirbyteConnectorError):
479    """Connector did not return a spec."""

Connector did not return a spec.

class AirbyteConnectorValidationFailedError(AirbyteConnectorError):
482class AirbyteConnectorValidationFailedError(AirbyteConnectorError):
483    """Connector config validation failed."""
484
485    guidance = (
486        "Please double-check your config and review the validation errors for more information."
487    )

Connector config validation failed.

guidance = 'Please double-check your config and review the validation errors for more information.'
class AirbyteConnectorCheckFailedError(AirbyteConnectorError):
490class AirbyteConnectorCheckFailedError(AirbyteConnectorError):
491    """Connector check failed."""
492
493    guidance = (
494        "Please double-check your config or review the connector's logs for more information."
495    )

Connector check failed.

guidance = "Please double-check your config or review the connector's logs for more information."
@dataclass
class AirbyteConnectorFailedError(AirbyteConnectorError):
498@dataclass
499class AirbyteConnectorFailedError(AirbyteConnectorError):
500    """Connector failed."""
501
502    exit_code: int | None = None

Connector failed.

AirbyteConnectorFailedError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, connector_name: str | None = None, exit_code: int | None = None)
exit_code: int | None = None
@dataclass
class AirbyteStreamNotFoundError(AirbyteConnectorError):
505@dataclass
506class AirbyteStreamNotFoundError(AirbyteConnectorError):
507    """Connector stream not found."""
508
509    stream_name: str | None = None
510    available_streams: list[str] | None = None

Connector stream not found.

AirbyteStreamNotFoundError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, connector_name: str | None = None, stream_name: str | None = None, available_streams: list[str] | None = None)
stream_name: str | None = None
available_streams: list[str] | None = None
@dataclass
class AirbyteStateNotFoundError(AirbyteConnectorError, builtins.KeyError):
513@dataclass
514class AirbyteStateNotFoundError(AirbyteConnectorError, KeyError):
515    """State entry not found."""
516
517    stream_name: str | None = None
518    available_streams: list[str] | None = None

State entry not found.

AirbyteStateNotFoundError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, connector_name: str | None = None, stream_name: str | None = None, available_streams: list[str] | None = None)
stream_name: str | None = None
available_streams: list[str] | None = None
@dataclass
class PyAirbyteSecretNotFoundError(PyAirbyteError):
521@dataclass
522class PyAirbyteSecretNotFoundError(PyAirbyteError):
523    """Secret not found."""
524
525    guidance = "Please ensure that the secret is set."
526    help_url = (
527        "https://docs.airbyte.com/using-airbyte/airbyte-lib/getting-started#secrets-management"
528    )
529
530    secret_name: str | None = None
531    sources: list[str] | None = None

Secret not found.

PyAirbyteSecretNotFoundError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, secret_name: str | None = None, sources: list[str] | None = None)
guidance = 'Please ensure that the secret is set.'
help_url = 'https://docs.airbyte.com/using-airbyte/airbyte-lib/getting-started#secrets-management'
secret_name: str | None = None
sources: list[str] | None = None
@dataclass
class AirbyteError(PyAirbyteError):
550@dataclass
551class AirbyteError(PyAirbyteError):
552    """An error occurred while communicating with the hosted Airbyte instance."""
553
554    response: AirbyteApiResponseDuckType | None = None
555    """The API response from the failed request."""
556
557    workspace: _WorkspaceWithUrl | None = None
558    """The workspace where the error occurred."""
559
560    @property
561    def workspace_url(self) -> str | None:
562        """The URL to the workspace where the error occurred."""
563        if self.workspace:
564            return self.workspace.workspace_url
565
566        return None

An error occurred while communicating with the hosted Airbyte instance.

AirbyteError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, response: airbyte._util.api_duck_types.AirbyteApiResponseDuckType | None = None, workspace: airbyte.exceptions._WorkspaceWithUrl | None = None)
response: airbyte._util.api_duck_types.AirbyteApiResponseDuckType | None = None

The API response from the failed request.

workspace: airbyte.exceptions._WorkspaceWithUrl | None = None

The workspace where the error occurred.

workspace_url: str | None
560    @property
561    def workspace_url(self) -> str | None:
562        """The URL to the workspace where the error occurred."""
563        if self.workspace:
564            return self.workspace.workspace_url
565
566        return None

The URL to the workspace where the error occurred.

@dataclass
class AirbyteConnectionError(AirbyteError):
569@dataclass
570class AirbyteConnectionError(AirbyteError):
571    """An connection error occurred while communicating with the hosted Airbyte instance."""
572
573    connection_id: str | None = None
574    """The connection ID where the error occurred."""
575
576    job_id: int | None = None
577    """The job ID where the error occurred (if applicable)."""
578
579    job_status: str | None = None
580    """The latest status of the job where the error occurred (if applicable)."""
581
582    @property
583    def connection_url(self) -> str | None:
584        """The web URL to the connection where the error occurred."""
585        if self.workspace_url and self.connection_id:
586            return f"{self.workspace_url}/connections/{self.connection_id}"
587
588        return None
589
590    @property
591    def job_history_url(self) -> str | None:
592        """The URL to the job history where the error occurred."""
593        if self.connection_url:
594            return f"{self.connection_url}/timeline"
595
596        return None
597
598    @property
599    def job_url(self) -> str | None:
600        """The URL to the job where the error occurred."""
601        if self.job_history_url and self.job_id:
602            return f"{self.job_history_url}#{self.job_id}::0"
603
604        return None

An connection error occurred while communicating with the hosted Airbyte instance.

AirbyteConnectionError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, response: airbyte._util.api_duck_types.AirbyteApiResponseDuckType | None = None, workspace: airbyte.exceptions._WorkspaceWithUrl | None = None, connection_id: str | None = None, job_id: int | None = None, job_status: str | None = None)
connection_id: str | None = None

The connection ID where the error occurred.

job_id: int | None = None

The job ID where the error occurred (if applicable).

job_status: str | None = None

The latest status of the job where the error occurred (if applicable).

connection_url: str | None
582    @property
583    def connection_url(self) -> str | None:
584        """The web URL to the connection where the error occurred."""
585        if self.workspace_url and self.connection_id:
586            return f"{self.workspace_url}/connections/{self.connection_id}"
587
588        return None

The web URL to the connection where the error occurred.

job_history_url: str | None
590    @property
591    def job_history_url(self) -> str | None:
592        """The URL to the job history where the error occurred."""
593        if self.connection_url:
594            return f"{self.connection_url}/timeline"
595
596        return None

The URL to the job history where the error occurred.

job_url: str | None
598    @property
599    def job_url(self) -> str | None:
600        """The URL to the job where the error occurred."""
601        if self.job_history_url and self.job_id:
602            return f"{self.job_history_url}#{self.job_id}::0"
603
604        return None

The URL to the job where the error occurred.

@dataclass
class AirbyteConnectionSyncError(AirbyteConnectionError):
607@dataclass
608class AirbyteConnectionSyncError(AirbyteConnectionError):
609    """An error occurred while executing the remote Airbyte job."""

An error occurred while executing the remote Airbyte job.

AirbyteConnectionSyncError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, response: airbyte._util.api_duck_types.AirbyteApiResponseDuckType | None = None, workspace: airbyte.exceptions._WorkspaceWithUrl | None = None, connection_id: str | None = None, job_id: int | None = None, job_status: str | None = None)
@dataclass
class AirbyteConnectionSyncActiveError(AirbyteConnectionError):
612@dataclass
613class AirbyteConnectionSyncActiveError(AirbyteConnectionError):
614    """State update rejected because a sync is currently running (HTTP 423)."""

State update rejected because a sync is currently running (HTTP 423).

AirbyteConnectionSyncActiveError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, response: airbyte._util.api_duck_types.AirbyteApiResponseDuckType | None = None, workspace: airbyte.exceptions._WorkspaceWithUrl | None = None, connection_id: str | None = None, job_id: int | None = None, job_status: str | None = None)
@dataclass
class AirbyteWorkspaceMismatchError(AirbyteError):
617@dataclass
618class AirbyteWorkspaceMismatchError(AirbyteError):
619    """Resource does not belong to the expected workspace.
620
621    This error is raised when a resource (connection, source, or destination) is fetched
622    from the API and the workspace ID in the response does not match the expected workspace.
623    """
624
625    resource_type: str | None = None
626    """The type of resource (e.g., 'connection', 'source', 'destination')."""
627
628    resource_id: str | None = None
629    """The ID of the resource that was fetched."""
630
631    expected_workspace_id: str | None = None
632    """The workspace ID that was expected."""
633
634    actual_workspace_id: str | None = None
635    """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.

AirbyteWorkspaceMismatchError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, response: airbyte._util.api_duck_types.AirbyteApiResponseDuckType | None = None, workspace: airbyte.exceptions._WorkspaceWithUrl | None = None, resource_type: str | None = None, resource_id: str | None = None, expected_workspace_id: str | None = None, actual_workspace_id: str | None = None)
resource_type: str | None = None

The type of resource (e.g., 'connection', 'source', 'destination').

resource_id: str | None = None

The ID of the resource that was fetched.

expected_workspace_id: str | None = None

The workspace ID that was expected.

actual_workspace_id: str | None = None

The workspace ID returned by the API.

@dataclass
class AirbyteWorkspaceNotEmptyError(AirbyteError):
638@dataclass
639class AirbyteWorkspaceNotEmptyError(AirbyteError):
640    """Workspace cannot be deleted because it contains connections."""
641
642    workspace_id: str | None = None
643    """The workspace ID that was expected to be empty."""
644
645    connection_ids: list[str] | None = None
646    """The connection IDs found in the workspace."""

Workspace cannot be deleted because it contains connections.

AirbyteWorkspaceNotEmptyError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, response: airbyte._util.api_duck_types.AirbyteApiResponseDuckType | None = None, workspace: airbyte.exceptions._WorkspaceWithUrl | None = None, workspace_id: str | None = None, connection_ids: list[str] | None = None)
workspace_id: str | None = None

The workspace ID that was expected to be empty.

connection_ids: list[str] | None = None

The connection IDs found in the workspace.

@dataclass
class AirbyteConnectionSyncTimeoutError(AirbyteConnectionSyncError):
649@dataclass
650class AirbyteConnectionSyncTimeoutError(AirbyteConnectionSyncError):
651    """An timeout occurred while waiting for the remote Airbyte job to complete."""
652
653    timeout: int | None = None
654    """The timeout in seconds that was reached."""

An timeout occurred while waiting for the remote Airbyte job to complete.

AirbyteConnectionSyncTimeoutError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, response: airbyte._util.api_duck_types.AirbyteApiResponseDuckType | None = None, workspace: airbyte.exceptions._WorkspaceWithUrl | None = None, connection_id: str | None = None, job_id: int | None = None, job_status: str | None = None, timeout: int | None = None)
timeout: int | None = None

The timeout in seconds that was reached.

@dataclass
class AirbyteMissingResourceError(AirbyteError):
660@dataclass
661class AirbyteMissingResourceError(AirbyteError):
662    """Remote Airbyte resources does not exist."""
663
664    resource_type: str | None = None
665    resource_name_or_id: str | None = None

Remote Airbyte resources does not exist.

AirbyteMissingResourceError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, response: airbyte._util.api_duck_types.AirbyteApiResponseDuckType | None = None, workspace: airbyte.exceptions._WorkspaceWithUrl | None = None, resource_type: str | None = None, resource_name_or_id: str | None = None)
resource_type: str | None = None
resource_name_or_id: str | None = None
@dataclass
class AirbyteDuplicateResourcesError(AirbyteError):
668@dataclass
669class AirbyteDuplicateResourcesError(AirbyteError):
670    """Process failed because resource name was not unique."""
671
672    resource_type: str | None = None
673    resource_name: str | None = None

Process failed because resource name was not unique.

AirbyteDuplicateResourcesError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, response: airbyte._util.api_duck_types.AirbyteApiResponseDuckType | None = None, workspace: airbyte.exceptions._WorkspaceWithUrl | None = None, resource_type: str | None = None, resource_name: str | None = None)
resource_type: str | None = None
resource_name: str | None = None
@dataclass
class AirbyteMultipleResourcesError(AirbyteError):
677@dataclass
678class AirbyteMultipleResourcesError(AirbyteError):
679    """Could not locate the resource because multiple matching resources were found."""
680
681    resource_type: str | None = None
682    resource_name_or_id: str | None = None

Could not locate the resource because multiple matching resources were found.

AirbyteMultipleResourcesError( guidance: str | None = None, help_url: str | None = None, log_text: str | list[str] | None = None, log_file: pathlib.Path | None = None, print_full_log: bool = True, context: dict[str, typing.Any] | None = None, message: str | None = None, original_exception: Exception | None = None, response: airbyte._util.api_duck_types.AirbyteApiResponseDuckType | None = None, workspace: airbyte.exceptions._WorkspaceWithUrl | None = None, resource_type: str | None = None, resource_name_or_id: str | None = None)
resource_type: str | None = None
resource_name_or_id: str | None = None
class AirbyteExperimentalFeatureWarning(builtins.FutureWarning):
688class AirbyteExperimentalFeatureWarning(FutureWarning):
689    """Warning whenever using experimental features in PyAirbyte."""

Warning whenever using experimental features in PyAirbyte.

class PyAirbyteWarning(builtins.Warning):
695class PyAirbyteWarning(Warning):
696    """General warnings from PyAirbyte."""

General warnings from PyAirbyte.

class PyAirbyteDataLossWarning(PyAirbyteWarning):
699class PyAirbyteDataLossWarning(PyAirbyteWarning):
700    """Warning for potential data loss.
701
702    Users can ignore this warning by running:
703    > warnings.filterwarnings("ignore", category="airbyte.exceptions.PyAirbyteDataLossWarning")
704    """

Warning for potential data loss.

Users can ignore this warning by running:

warnings.filterwarnings("ignore", category="airbyte.exceptions.PyAirbyteDataLossWarning")