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

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):
304@dataclass
305class AirbyteMCPError(PyAirbyteError):
306    """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):
309@dataclass
310class AirbyteTrustedExecutionRequiredError(AirbyteMCPError):
311    """A trusted-execution-only capability was invoked while trusted execution is disabled.
312
313    Trusted execution grants the MCP server its trusted-machine capabilities: local
314    filesystem access, local connector installation/execution, and server-side secret
315    resolution. It defaults to *off* on every transport and is permanently unavailable
316    over the HTTP transport, so a backend helper that exposes one of those capabilities
317    hard-fails when the gate is disabled -- independently of whether the corresponding
318    tool was hidden from the tool listing.
319    """
320
321    guidance = (
322        "Set `AIRBYTE_MCP_TRUSTED_EXECUTION=1` on the MCP server process and restart it. "
323        "Trusted execution is only available on the stdio transport; it can never be "
324        "enabled for an HTTP/hosted deployment."
325    )
326    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):
332@dataclass
333class PyAirbyteNameNormalizationError(PyAirbyteError, ValueError):
334    """Error occurred while normalizing a table or column name."""
335
336    guidance = (
337        "Please consider renaming the source object if possible, or "
338        "raise an issue in GitHub if not."
339    )
340    help_url = NEW_ISSUE_URL
341
342    raw_name: str | None = None
343    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):
349class PyAirbyteCacheError(PyAirbyteError):
350    """Error occurred while accessing the cache."""

Error occurred while accessing the cache.

@dataclass
class PyAirbyteCacheTableValidationError(PyAirbyteCacheError):
353@dataclass
354class PyAirbyteCacheTableValidationError(PyAirbyteCacheError):
355    """Cache table validation failed."""
356
357    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):
360@dataclass
361class AirbyteConnectorConfigurationMissingError(PyAirbyteCacheError):
362    """Connector is missing configuration."""
363
364    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):
370@dataclass
371class AirbyteSubprocessError(PyAirbyteError):
372    """Error when running subprocess."""
373
374    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):
377@dataclass
378class AirbyteSubprocessFailedError(AirbyteSubprocessError):
379    """Subprocess failed."""
380
381    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):
387class AirbyteConnectorRegistryError(PyAirbyteError):
388    """Error when accessing the connector registry."""

Error when accessing the connector registry.

@dataclass
class AirbyteConnectorNotRegisteredError(AirbyteConnectorRegistryError):
391@dataclass
392class AirbyteConnectorNotRegisteredError(AirbyteConnectorRegistryError):
393    """Connector not found in registry."""
394
395    connector_name: str | None = None
396    guidance = (
397        "Please double check the connector name. "
398        "Alternatively, you can provide an explicit connector install method to `get_source()`: "
399        "`pip_url`, `local_executable`, `docker_image`, or `source_manifest`."
400    )
401    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):
404@dataclass
405class AirbyteConnectorNotPyPiPublishedError(AirbyteConnectorRegistryError):
406    """Connector found, but not published to PyPI."""
407
408    connector_name: str | None = None
409    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):
415@dataclass
416class AirbyteConnectorError(PyAirbyteError):
417    """Error when running the connector."""
418
419    connector_name: str | None = None
420
421    def __post_init__(self) -> None:
422        """Set the log file path for the connector."""
423        self.log_file = self._get_log_file()
424        if not self.guidance and self.log_file:
425            self.guidance = "Please review the log file for more information."
426
427    def _get_log_file(self) -> Path | None:
428        """Return the log file path for the connector."""
429        if self.connector_name:
430            logger = logging.getLogger(f"airbyte.{self.connector_name}")
431
432            log_paths: list[Path] = [
433                Path(handler.baseFilename).absolute()
434                for handler in logger.handlers
435                if isinstance(handler, logging.FileHandler)
436            ]
437
438            if log_paths:
439                return log_paths[0]
440
441        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):
444class AirbyteConnectorExecutableNotFoundError(AirbyteConnectorError):
445    """Connector executable not found."""

Connector executable not found.

class AirbyteConnectorInstallationError(AirbyteConnectorError):
448class AirbyteConnectorInstallationError(AirbyteConnectorError):
449    """Error when installing the connector."""

Error when installing the connector.

class AirbyteConnectorReadError(AirbyteConnectorError):
452class AirbyteConnectorReadError(AirbyteConnectorError):
453    """Error when reading from the connector."""

Error when reading from the connector.

class AirbyteConnectorWriteError(AirbyteConnectorError):
456class AirbyteConnectorWriteError(AirbyteConnectorError):
457    """Error when writing to the connector."""

Error when writing to the connector.

class AirbyteConnectorSpecFailedError(AirbyteConnectorError):
460class AirbyteConnectorSpecFailedError(AirbyteConnectorError):
461    """Error when getting spec from the connector."""

Error when getting spec from the connector.

class AirbyteConnectorDiscoverFailedError(AirbyteConnectorError):
464class AirbyteConnectorDiscoverFailedError(AirbyteConnectorError):
465    """Error when running discovery on the connector."""

Error when running discovery on the connector.

class AirbyteNoDataFromConnectorError(AirbyteConnectorError):
468class AirbyteNoDataFromConnectorError(AirbyteConnectorError):
469    """No data was provided from the connector."""

No data was provided from the connector.

class AirbyteConnectorMissingCatalogError(AirbyteConnectorError):
472class AirbyteConnectorMissingCatalogError(AirbyteConnectorError):
473    """Connector did not return a catalog."""

Connector did not return a catalog.

class AirbyteConnectorMissingSpecError(AirbyteConnectorError):
476class AirbyteConnectorMissingSpecError(AirbyteConnectorError):
477    """Connector did not return a spec."""

Connector did not return a spec.

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

Connector config validation failed.

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

Connector check failed.

guidance = "Please double-check your config or review the connector's logs for more information."
@dataclass
class AirbyteConnectorFailedError(AirbyteConnectorError):
496@dataclass
497class AirbyteConnectorFailedError(AirbyteConnectorError):
498    """Connector failed."""
499
500    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):
503@dataclass
504class AirbyteStreamNotFoundError(AirbyteConnectorError):
505    """Connector stream not found."""
506
507    stream_name: str | None = None
508    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):
511@dataclass
512class AirbyteStateNotFoundError(AirbyteConnectorError, KeyError):
513    """State entry not found."""
514
515    stream_name: str | None = None
516    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):
519@dataclass
520class PyAirbyteSecretNotFoundError(PyAirbyteError):
521    """Secret not found."""
522
523    guidance = "Please ensure that the secret is set."
524    help_url = (
525        "https://docs.airbyte.com/using-airbyte/airbyte-lib/getting-started#secrets-management"
526    )
527
528    secret_name: str | None = None
529    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):
548@dataclass
549class AirbyteError(PyAirbyteError):
550    """An error occurred while communicating with the hosted Airbyte instance."""
551
552    response: AirbyteApiResponseDuckType | None = None
553    """The API response from the failed request."""
554
555    workspace: _WorkspaceWithUrl | None = None
556    """The workspace where the error occurred."""
557
558    @property
559    def workspace_url(self) -> str | None:
560        """The URL to the workspace where the error occurred."""
561        if self.workspace:
562            return self.workspace.workspace_url
563
564        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
558    @property
559    def workspace_url(self) -> str | None:
560        """The URL to the workspace where the error occurred."""
561        if self.workspace:
562            return self.workspace.workspace_url
563
564        return None

The URL to the workspace where the error occurred.

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

The web URL to the connection where the error occurred.

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

The URL to the job history where the error occurred.

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

The URL to the job where the error occurred.

@dataclass
class AirbyteConnectionSyncError(AirbyteConnectionError):
605@dataclass
606class AirbyteConnectionSyncError(AirbyteConnectionError):
607    """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):
610@dataclass
611class AirbyteConnectionSyncActiveError(AirbyteConnectionError):
612    """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):
615@dataclass
616class AirbyteWorkspaceMismatchError(AirbyteError):
617    """Resource does not belong to the expected workspace.
618
619    This error is raised when a resource (connection, source, or destination) is fetched
620    from the API and the workspace ID in the response does not match the expected workspace.
621    """
622
623    resource_type: str | None = None
624    """The type of resource (e.g., 'connection', 'source', 'destination')."""
625
626    resource_id: str | None = None
627    """The ID of the resource that was fetched."""
628
629    expected_workspace_id: str | None = None
630    """The workspace ID that was expected."""
631
632    actual_workspace_id: str | None = None
633    """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):
636@dataclass
637class AirbyteWorkspaceNotEmptyError(AirbyteError):
638    """Workspace cannot be deleted because it contains connections."""
639
640    workspace_id: str | None = None
641    """The workspace ID that was expected to be empty."""
642
643    connection_ids: list[str] | None = None
644    """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):
647@dataclass
648class AirbyteConnectionSyncTimeoutError(AirbyteConnectionSyncError):
649    """An timeout occurred while waiting for the remote Airbyte job to complete."""
650
651    timeout: int | None = None
652    """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):
658@dataclass
659class AirbyteMissingResourceError(AirbyteError):
660    """Remote Airbyte resources does not exist."""
661
662    resource_type: str | None = None
663    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):
666@dataclass
667class AirbyteDuplicateResourcesError(AirbyteError):
668    """Process failed because resource name was not unique."""
669
670    resource_type: str | None = None
671    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):
675@dataclass
676class AirbyteMultipleResourcesError(AirbyteError):
677    """Could not locate the resource because multiple matching resources were found."""
678
679    resource_type: str | None = None
680    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):
686class AirbyteExperimentalFeatureWarning(FutureWarning):
687    """Warning whenever using experimental features in PyAirbyte."""

Warning whenever using experimental features in PyAirbyte.

class PyAirbyteWarning(builtins.Warning):
693class PyAirbyteWarning(Warning):
694    """General warnings from PyAirbyte."""

General warnings from PyAirbyte.

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

Warning for potential data loss.

Users can ignore this warning by running:

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