123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188 |
- # Copyright (c) "Neo4j"
- # Neo4j Sweden AB [https://neo4j.com]
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # https://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- # ruff: noqa: N818
- # Not going to rename all Error classes that don't end on Error,
- # which would break pretty much all users just to please the linter.
- """
- Module containing the core driver exceptions.
- Driver API Errors
- =================
- + Neo4jError
- + ClientError
- + CypherSyntaxError
- + CypherTypeError
- + ConstraintError
- + AuthError
- + TokenExpired
- + Forbidden
- + DatabaseError
- + TransientError
- + DatabaseUnavailable
- + NotALeader
- + ForbiddenOnReadOnlyDatabase
- + DriverError
- + SessionError
- + TransactionError
- + TransactionNestingError
- + ResultError
- + ResultFailedError
- + ResultConsumedError
- + ResultNotSingleError
- + BrokenRecordError
- + SessionExpired
- + ServiceUnavailable
- + RoutingServiceUnavailable
- + WriteServiceUnavailable
- + ReadServiceUnavailable
- + IncompleteCommit
- + ConfigurationError
- + AuthConfigurationError
- + CertificateConfigurationError
- """
- from __future__ import annotations
- import typing as t
- from copy import deepcopy as _deepcopy
- from enum import Enum as _Enum
- from ._meta import (
- deprecated,
- preview as _preview,
- )
- __all__ = [
- "AuthConfigurationError",
- "AuthError",
- "BrokenRecordError",
- "CertificateConfigurationError",
- "ClientError",
- "ConfigurationError",
- "ConstraintError",
- "CypherSyntaxError",
- "CypherTypeError",
- "DatabaseError",
- "DatabaseUnavailable",
- "DriverError",
- "Forbidden",
- "ForbiddenOnReadOnlyDatabase",
- "GqlError",
- "GqlErrorClassification",
- "IncompleteCommit",
- "Neo4jError",
- "NotALeader",
- "ReadServiceUnavailable",
- "ResultConsumedError",
- "ResultError",
- "ResultFailedError",
- "ResultNotSingleError",
- "RoutingServiceUnavailable",
- "ServiceUnavailable",
- "SessionError",
- "SessionExpired",
- "TokenExpired",
- "TransactionError",
- "TransactionNestingError",
- "TransientError",
- "UnsupportedServerProduct",
- "WriteServiceUnavailable",
- ]
- if t.TYPE_CHECKING:
- from collections.abc import Mapping
- import typing_extensions as te
- from ._async.work import (
- AsyncManagedTransaction,
- AsyncResult,
- AsyncSession,
- AsyncTransaction,
- )
- from ._sync.work import (
- ManagedTransaction,
- Result,
- Session,
- Transaction,
- )
- _TTransaction = t.Union[
- AsyncManagedTransaction,
- AsyncTransaction,
- ManagedTransaction,
- Transaction,
- ]
- _TResult = t.Union[AsyncResult, Result]
- _TSession = t.Union[AsyncSession, Session]
- _T = t.TypeVar("_T")
- else:
- _TTransaction = t.Union[
- "AsyncManagedTransaction",
- "AsyncTransaction",
- "ManagedTransaction",
- "Transaction",
- ]
- _TResult = t.Union["AsyncResult", "Result"]
- _TSession = t.Union["AsyncSession", "Session"]
- __all__ = [
- "CLASSIFICATION_CLIENT", # TODO: 6.0 - make constant private
- "CLASSIFICATION_DATABASE", # TODO: 6.0 - make constant private
- "CLASSIFICATION_TRANSIENT", # TODO: 6.0 - make constant private
- "ERROR_REWRITE_MAP", # TODO: 6.0 - make constant private
- "AuthConfigurationError",
- "AuthError",
- "BrokenRecordError",
- "CertificateConfigurationError",
- "ClientError",
- "ConfigurationError",
- "ConstraintError",
- "CypherSyntaxError",
- "CypherTypeError",
- "DatabaseError",
- "DatabaseUnavailable",
- "DriverError",
- "Forbidden",
- "ForbiddenOnReadOnlyDatabase",
- "IncompleteCommit",
- "Neo4jError",
- "NotALeader",
- "ReadServiceUnavailable",
- "ResultConsumedError",
- "ResultError",
- "ResultFailedError",
- "ResultNotSingleError",
- "RoutingServiceUnavailable",
- "ServiceUnavailable",
- "SessionError",
- "SessionExpired",
- "TokenExpired",
- "TransactionError",
- "TransactionNestingError",
- "TransientError",
- "UnsupportedServerProduct",
- "WriteServiceUnavailable",
- ]
- CLASSIFICATION_CLIENT: te.Final[str] = "ClientError"
- CLASSIFICATION_TRANSIENT: te.Final[str] = "TransientError"
- CLASSIFICATION_DATABASE: te.Final[str] = "DatabaseError"
- ERROR_REWRITE_MAP: dict[str, tuple[str, str | None]] = {
- # This error can be retried ed. The driver just needs to re-authenticate
- # with the same credentials.
- "Neo.ClientError.Security.AuthorizationExpired": (
- CLASSIFICATION_TRANSIENT,
- None,
- ),
- # In 5.0, this error has been re-classified as ClientError.
- # For backwards compatibility with Neo4j 4.4 and earlier, we re-map it in
- # the driver, too.
- "Neo.TransientError.Transaction.Terminated": (
- CLASSIFICATION_CLIENT,
- "Neo.ClientError.Transaction.Terminated",
- ),
- # In 5.0, this error has been re-classified as ClientError.
- # For backwards compatibility with Neo4j 4.4 and earlier, we re-map it in
- # the driver, too.
- "Neo.TransientError.Transaction.LockClientStopped": (
- CLASSIFICATION_CLIENT,
- "Neo.ClientError.Transaction.LockClientStopped",
- ),
- }
- _UNKNOWN_NEO4J_CODE: te.Final[str] = "Neo.DatabaseError.General.UnknownError"
- # TODO: 6.0 - Make _UNKNOWN_GQL_MESSAGE the default message
- _UNKNOWN_MESSAGE: te.Final[str] = "An unknown error occurred"
- _UNKNOWN_GQL_STATUS: te.Final[str] = "50N42"
- _UNKNOWN_GQL_DESCRIPTION: te.Final[str] = (
- "error: general processing exception - unexpected error"
- )
- _UNKNOWN_GQL_MESSAGE: te.Final[str] = (
- f"{_UNKNOWN_GQL_STATUS}: "
- "Unexpected error has occurred. See debug log for details."
- )
- _UNKNOWN_GQL_DIAGNOSTIC_RECORD: te.Final[tuple[tuple[str, t.Any], ...]] = (
- ("OPERATION", ""),
- ("OPERATION_CODE", "0"),
- ("CURRENT_SCHEMA", "/"),
- )
- class GqlErrorClassification(str, _Enum):
- """
- Server-side GQL error category.
- Inherits from :class:`str` and :class:`enum.Enum`.
- Hence, can also be compared to its string value::
- >>> GqlErrorClassification.CLIENT_ERROR == "CLIENT_ERROR"
- True
- >>> GqlErrorClassification.DATABASE_ERROR == "DATABASE_ERROR"
- True
- >>> GqlErrorClassification.TRANSIENT_ERROR == "TRANSIENT_ERROR"
- True
- **This is a preview**.
- It might be changed without following the deprecation policy.
- See also
- https://github.com/neo4j/neo4j-python-driver/wiki/preview-features
- .. seealso:: :attr:`.GqlError.gql_classification`
- .. versionadded:: 5.26
- """
- CLIENT_ERROR = "CLIENT_ERROR"
- DATABASE_ERROR = "DATABASE_ERROR"
- TRANSIENT_ERROR = "TRANSIENT_ERROR"
- #: Used when the server provides a Classification which the driver is
- #: unaware of.
- #: This can happen when connecting to a server newer than the driver or
- #: before GQL errors were introduced.
- UNKNOWN = "UNKNOWN"
- class GqlError(Exception):
- """
- The GQL compliant data of an error.
- This error isn't raised by the driver as is.
- Instead, only subclasses are raised.
- Further, it is used as the :attr:`__cause__` of GqlError subclasses.
- **This is a preview**.
- It might be changed without following the deprecation policy.
- See also
- https://github.com/neo4j/neo4j-python-driver/wiki/preview-features
- .. versionadded: 5.26
- """
- _gql_status: str
- # TODO: 6.0 - make message always str
- _message: str | None
- _gql_status_description: str
- _gql_raw_classification: str | None
- _gql_classification: GqlErrorClassification
- _status_diagnostic_record: dict[str, t.Any] # original, internal only
- _diagnostic_record: dict[str, t.Any] # copy to be used externally
- _gql_cause: GqlError | None
- @staticmethod
- def _hydrate_cause(**metadata: t.Any) -> GqlError:
- meta_extractor = _MetaExtractor(metadata)
- gql_status = meta_extractor.str_value("gql_status")
- description = meta_extractor.str_value("description")
- message = meta_extractor.str_value("message")
- diagnostic_record = meta_extractor.map_value("diagnostic_record")
- cause_map = meta_extractor.map_value("cause")
- if cause_map is not None:
- cause = GqlError._hydrate_cause(**cause_map)
- else:
- cause = None
- inst = GqlError()
- inst._init_gql(
- gql_status=gql_status,
- message=message,
- description=description,
- diagnostic_record=diagnostic_record,
- cause=cause,
- )
- return inst
- def _init_gql(
- self,
- *,
- gql_status: str | None = None,
- message: str | None = None,
- description: str | None = None,
- diagnostic_record: dict[str, t.Any] | None = None,
- cause: GqlError | None = None,
- ) -> None:
- if gql_status is None or message is None or description is None:
- self._gql_status = _UNKNOWN_GQL_STATUS
- self._message = _UNKNOWN_GQL_MESSAGE
- self._gql_status_description = _UNKNOWN_GQL_DESCRIPTION
- else:
- self._gql_status = gql_status
- self._message = message
- self._gql_status_description = description
- if diagnostic_record is not None:
- self._status_diagnostic_record = diagnostic_record
- self._gql_cause = cause
- def _set_unknown_gql(self):
- self._gql_status = _UNKNOWN_GQL_STATUS
- self._message = _UNKNOWN_GQL_MESSAGE
- self._gql_status_description = _UNKNOWN_GQL_DESCRIPTION
- def __getattribute__(self, item):
- if item != "__cause__":
- return super().__getattribute__(item)
- gql_cause = self._get_attr_or_none("_gql_cause")
- if gql_cause is None:
- # No GQL cause, no magic needed
- return super().__getattribute__(item)
- local_cause = self._get_attr_or_none("__cause__")
- if local_cause is None:
- # We have a GQL cause but no local cause
- # => set the GQL cause as the local cause
- self.__cause__ = gql_cause
- self.__suppress_context__ = True
- self._gql_cause = None
- return super().__getattribute__(item)
- # We have both a GQL cause and a local cause
- # => traverse the cause chain and append the local cause.
- root = gql_cause
- seen_errors = {id(self), id(root)}
- while True:
- cause = getattr(root, "__cause__", None)
- if cause is None:
- root.__cause__ = local_cause
- root.__suppress_context__ = True
- self.__cause__ = gql_cause
- self.__suppress_context__ = True
- self._gql_cause = None
- return gql_cause
- root = cause
- if id(root) in seen_errors:
- # Circular cause chain -> we have no choice but to either
- # overwrite the cause or ignore the new one.
- return local_cause
- seen_errors.add(id(root))
- def _get_attr_or_none(self, item):
- try:
- return super().__getattribute__(item)
- except AttributeError:
- return None
- @property
- def _gql_status_no_preview(self) -> str:
- if hasattr(self, "_gql_status"):
- return self._gql_status
- self._set_unknown_gql()
- return self._gql_status
- @property
- @_preview("GQLSTATUS support is a preview feature.")
- def gql_status(self) -> str:
- """
- The GQLSTATUS returned from the server.
- The status code ``50N42`` (unknown error) is a special code that the
- driver will use for polyfilling (when connected to an old,
- non-GQL-aware server).
- Further, it may be used by servers during the transition-phase to
- GQLSTATUS-awareness.
- .. note::
- This means that the code ``50N42`` is not guaranteed to be stable
- and may change in future versions of the driver or the server.
- """
- return self._gql_status_no_preview
- @property
- def _message_no_preview(self) -> str | None:
- if hasattr(self, "_message"):
- return self._message
- self._set_unknown_gql()
- return self._message
- @property
- @_preview("GQLSTATUS support is a preview feature.")
- def message(self) -> str | None:
- """
- The error message returned by the server.
- It is a string representation of the error that occurred.
- This message is meant for human consumption and debugging purposes.
- Don't rely on it in a programmatic way.
- This value is never :data:`None` unless the subclass in question
- states otherwise.
- """
- return self._message_no_preview
- @property
- def _gql_status_description_no_preview(self) -> str:
- if hasattr(self, "_gql_status_description"):
- return self._gql_status_description
- self._set_unknown_gql()
- return self._gql_status_description
- @property
- @_preview("GQLSTATUS support is a preview feature.")
- def gql_status_description(self) -> str:
- """
- A description of the GQLSTATUS returned from the server.
- It describes the error that occurred in detail.
- This description is meant for human consumption and debugging purposes.
- Don't rely on it in a programmatic way.
- """
- return self._gql_status_description_no_preview
- @property
- def _gql_raw_classification_no_preview(self) -> str | None:
- if hasattr(self, "_gql_raw_classification"):
- return self._gql_raw_classification
- diag_record = self._get_status_diagnostic_record()
- classification = diag_record.get("_classification")
- if not isinstance(classification, str):
- self._gql_raw_classification = None
- else:
- self._gql_raw_classification = classification
- return self._gql_raw_classification
- @property
- @_preview("GQLSTATUS support is a preview feature.")
- def gql_raw_classification(self) -> str | None:
- """
- Vendor specific classification of the error.
- This is a convenience accessor for ``_classification`` in the
- diagnostic record.
- :data:`None` is returned if the classification is not available
- or not a string.
- """
- return self._gql_raw_classification_no_preview
- @property
- def _gql_classification_no_preview(self) -> GqlErrorClassification:
- if hasattr(self, "_gql_classification"):
- return self._gql_classification
- classification = self._gql_raw_classification_no_preview
- if not (
- isinstance(classification, str)
- and classification
- in t.cast(t.Iterable[str], iter(GqlErrorClassification))
- ):
- self._gql_classification = GqlErrorClassification.UNKNOWN
- else:
- self._gql_classification = GqlErrorClassification(classification)
- return self._gql_classification
- @property
- @_preview("GQLSTATUS support is a preview feature.")
- def gql_classification(self) -> GqlErrorClassification:
- return self._gql_classification_no_preview
- def _get_status_diagnostic_record(self) -> dict[str, t.Any]:
- if hasattr(self, "_status_diagnostic_record"):
- return self._status_diagnostic_record
- self._status_diagnostic_record = dict(_UNKNOWN_GQL_DIAGNOSTIC_RECORD)
- return self._status_diagnostic_record
- @property
- def _diagnostic_record_no_preview(self) -> Mapping[str, t.Any]:
- if hasattr(self, "_diagnostic_record"):
- return self._diagnostic_record
- self._diagnostic_record = _deepcopy(
- self._get_status_diagnostic_record()
- )
- return self._diagnostic_record
- @property
- @_preview("GQLSTATUS support is a preview feature.")
- def diagnostic_record(self) -> Mapping[str, t.Any]:
- return self._diagnostic_record_no_preview
- def __str__(self):
- return (
- f"{{gql_status: {self._gql_status_no_preview}}} "
- f"{{gql_status_description: "
- f"{self._gql_status_description_no_preview}}} "
- f"{{message: {self._message_no_preview}}} "
- f"{{diagnostic_record: {self._diagnostic_record_no_preview}}} "
- f"{{raw_classification: "
- f"{self._gql_raw_classification_no_preview}}}"
- )
- # Neo4jError
- class Neo4jError(GqlError):
- """Raised when the Cypher engine returns an error to the client."""
- _neo4j_code: str | None
- _classification: str | None
- _category: str | None
- _title: str | None
- #: (dict) Any additional information returned by the server.
- _metadata: dict[str, t.Any] | None
- _retryable = False
- def __init__(self, *args) -> None:
- Exception.__init__(self, *args)
- self._neo4j_code = None
- self._classification = None
- self._category = None
- self._title = None
- self._metadata = None
- self._message = None
- # TODO: 6.0 - do this instead to get rid of all optional attributes
- # self._neo4j_code = _UNKNOWN_NEO4J_CODE
- # _, self._classification, self._category, self._title = (
- # self._neo4j_code.split(".")
- # )
- # self._metadata = {}
- # self._init_gql()
- # TODO: 6.0 - Remove this alias
- @classmethod
- @deprecated(
- "Neo4jError.hydrate is deprecated and will be removed in a future "
- "version. It is an internal method and not meant for external use."
- )
- def hydrate(
- cls,
- code: str | None = None,
- message: str | None = None,
- **metadata: t.Any,
- ) -> Neo4jError:
- # backward compatibility: make falsy values None
- code = code or None
- message = message or None
- return cls._hydrate_neo4j(code=code, message=message, **metadata)
- @classmethod
- def _hydrate_neo4j(cls, **metadata: t.Any) -> Neo4jError:
- meta_extractor = _MetaExtractor(metadata)
- code = meta_extractor.str_value("code") or _UNKNOWN_NEO4J_CODE
- message = meta_extractor.str_value("message") or _UNKNOWN_MESSAGE
- inst = cls._basic_hydrate(
- neo4j_code=code,
- message=message,
- )
- inst._init_gql(
- gql_status=_UNKNOWN_GQL_STATUS,
- message=message,
- description=f"{_UNKNOWN_GQL_DESCRIPTION}. {message}",
- )
- inst._metadata = meta_extractor.rest()
- return inst
- @classmethod
- def _hydrate_gql(cls, **metadata: t.Any) -> Neo4jError:
- meta_extractor = _MetaExtractor(metadata)
- gql_status = meta_extractor.str_value("gql_status")
- status_description = meta_extractor.str_value("description")
- message = meta_extractor.str_value("message")
- if gql_status is None or status_description is None or message is None:
- gql_status = _UNKNOWN_GQL_STATUS
- # TODO: 6.0 - Make this fall back to _UNKNOWN_GQL_MESSAGE
- message = _UNKNOWN_MESSAGE
- status_description = _UNKNOWN_GQL_DESCRIPTION
- neo4j_code = meta_extractor.str_value(
- "neo4j_code",
- _UNKNOWN_NEO4J_CODE,
- )
- diagnostic_record = meta_extractor.map_value("diagnostic_record")
- cause_map = meta_extractor.map_value("cause")
- if cause_map is not None:
- cause = cls._hydrate_cause(**cause_map)
- else:
- cause = None
- inst = cls._basic_hydrate(
- neo4j_code=neo4j_code,
- message=message,
- )
- inst._init_gql(
- gql_status=gql_status,
- message=message,
- description=status_description,
- diagnostic_record=diagnostic_record,
- cause=cause,
- )
- inst._metadata = meta_extractor.rest()
- return inst
- @classmethod
- def _basic_hydrate(cls, *, neo4j_code: str, message: str) -> Neo4jError:
- try:
- _, classification, category, title = neo4j_code.split(".")
- except ValueError:
- classification = CLASSIFICATION_DATABASE
- category = "General"
- title = "UnknownError"
- else:
- classification_override, code_override = ERROR_REWRITE_MAP.get(
- neo4j_code, (None, None)
- )
- if classification_override is not None:
- classification = classification_override
- if code_override is not None:
- neo4j_code = code_override
- error_class: type[Neo4jError] = cls._extract_error_class(
- classification, neo4j_code
- )
- inst = error_class(message)
- inst._neo4j_code = neo4j_code
- inst._classification = classification
- inst._category = category
- inst._title = title
- inst._message = message
- return inst
- @classmethod
- def _extract_error_class(cls, classification, code) -> type[Neo4jError]:
- if classification == CLASSIFICATION_CLIENT:
- try:
- return client_errors[code]
- except KeyError:
- return ClientError
- elif classification == CLASSIFICATION_TRANSIENT:
- try:
- return transient_errors[code]
- except KeyError:
- return TransientError
- elif classification == CLASSIFICATION_DATABASE:
- return DatabaseError
- else:
- return cls
- @property
- def message(self) -> str | None:
- """
- The error message returned by the server.
- This value is only :data:`None` for locally created errors.
- """
- return self._message
- @message.setter
- @deprecated("Altering the message of a Neo4jError is deprecated.")
- def message(self, value: str) -> None:
- self._message = value
- @property
- def code(self) -> str | None:
- """
- The neo4j error code returned by the server.
- For example, "Neo.ClientError.Security.AuthorizationExpired".
- This value is only :data:`None` for locally created errors.
- """
- return self._neo4j_code
- # TODO: 6.0 - Remove this and all other deprecated setters
- @code.setter
- @deprecated("Altering the code of a Neo4jError is deprecated.")
- def code(self, value: str) -> None:
- self._neo4j_code = value
- @property
- def classification(self) -> str | None:
- # Undocumented, will likely be removed with support for neo4j codes
- return self._classification
- @classification.setter
- @deprecated("Altering the classification of Neo4jError is deprecated.")
- def classification(self, value: str) -> None:
- self._classification = value
- @property
- def category(self) -> str | None:
- # Undocumented, will likely be removed with support for neo4j codes
- return self._category
- @category.setter
- @deprecated("Altering the category of Neo4jError is deprecated.")
- def category(self, value: str) -> None:
- self._category = value
- @property
- def title(self) -> str | None:
- # Undocumented, will likely be removed with support for neo4j codes
- return self._title
- @title.setter
- @deprecated("Altering the title of Neo4jError is deprecated.")
- def title(self, value: str) -> None:
- self._title = value
- @property
- def metadata(self) -> dict[str, t.Any] | None:
- # Undocumented, might be useful for debugging
- return self._metadata
- @metadata.setter
- @deprecated("Altering the metadata of Neo4jError is deprecated.")
- def metadata(self, value: dict[str, t.Any]) -> None:
- self._metadata = value
- # TODO: 6.0 - Remove this alias
- @deprecated(
- "Neo4jError.is_retriable is deprecated and will be removed in a "
- "future version. Please use Neo4jError.is_retryable instead."
- )
- def is_retriable(self) -> bool:
- """
- Whether the error is retryable.
- See :meth:`.is_retryable`.
- :returns: :data:`True` if the error is retryable,
- :data:`False` otherwise.
- .. deprecated:: 5.0
- This method will be removed in a future version.
- Please use :meth:`.is_retryable` instead.
- """
- return self.is_retryable()
- def is_retryable(self) -> bool:
- """
- Whether the error is retryable.
- Indicates whether a transaction that yielded this error makes sense to
- retry. This method makes mostly sense when implementing a custom
- retry policy in conjunction with :ref:`explicit-transactions-ref`.
- :returns: :data:`True` if the error is retryable,
- :data:`False` otherwise.
- .. versionadded:: 5.0
- """
- return self._retryable
- def _unauthenticates_all_connections(self) -> bool:
- return (
- self._neo4j_code == "Neo.ClientError.Security.AuthorizationExpired"
- )
- # TODO: 6.0 - Remove this alias
- invalidates_all_connections = deprecated(
- "Neo4jError.invalidates_all_connections is deprecated and will be "
- "removed in a future version. It is an internal method and not meant "
- "for external use."
- )(_unauthenticates_all_connections)
- def _is_fatal_during_discovery(self) -> bool:
- # checks if the code is an error that is caused by the client. In this
- # case the driver should fail fast during discovery.
- code = self._neo4j_code
- if not isinstance(code, str):
- return False
- if code in {
- "Neo.ClientError.Database.DatabaseNotFound",
- "Neo.ClientError.Transaction.InvalidBookmark",
- "Neo.ClientError.Transaction.InvalidBookmarkMixture",
- "Neo.ClientError.Statement.TypeError",
- "Neo.ClientError.Statement.ArgumentError",
- "Neo.ClientError.Request.Invalid",
- }:
- return True
- return (
- code.startswith("Neo.ClientError.Security.")
- and code != "Neo.ClientError.Security.AuthorizationExpired"
- )
- def _has_security_code(self) -> bool:
- if self._neo4j_code is None:
- return False
- return self._neo4j_code.startswith("Neo.ClientError.Security.")
- # TODO: 6.0 - Remove this alias
- is_fatal_during_discovery = deprecated(
- "Neo4jError.is_fatal_during_discovery is deprecated and will be "
- "removed in a future version. It is an internal method and not meant "
- "for external use."
- )(_is_fatal_during_discovery)
- def __str__(self):
- code = self._neo4j_code
- message = self._message
- if code or message:
- return f"{{code: {code}}} {{message: {message}}}"
- # TODO: 6.0 - Use gql status and status_description instead
- # something like:
- # return (
- # f"{{gql_status: {self.gql_status}}} "
- # f"{{neo4j_code: {self.neo4j_code}}} "
- # f"{{gql_status_description: {self.gql_status_description}}} "
- # f"{{diagnostic_record: {self.diagnostic_record}}}"
- # )
- return Exception.__str__(self)
- class _MetaExtractor:
- def __init__(self, metadata: dict[str, t.Any]):
- self._metadata = metadata
- def rest(self) -> dict[str, t.Any]:
- return self._metadata
- @t.overload
- def str_value(self, key: str) -> str | None: ...
- @t.overload
- def str_value(self, key: str, default: _T) -> str | _T: ...
- def str_value(
- self, key: str, default: _T | None = None
- ) -> str | _T | None:
- res = self._metadata.pop(key, default)
- if not isinstance(res, str):
- res = default
- return res
- @t.overload
- def map_value(self, key: str) -> dict[str, t.Any] | None: ...
- @t.overload
- def map_value(self, key: str, default: _T) -> dict[str, t.Any] | _T: ...
- def map_value(
- self, key: str, default: _T | None = None
- ) -> dict[str, t.Any] | _T | None:
- res = self._metadata.pop(key, default)
- if not (
- isinstance(res, dict) and all(isinstance(k, str) for k in res)
- ):
- res = default
- return res
- # Neo4jError > ClientError
- class ClientError(Neo4jError):
- """
- Bad client request.
- The Client sent a bad request - changing the request might yield a
- successful outcome.
- """
- # Neo4jError > ClientError > CypherSyntaxError
- class CypherSyntaxError(ClientError):
- pass
- # Neo4jError > ClientError > CypherTypeError
- class CypherTypeError(ClientError):
- pass
- # Neo4jError > ClientError > ConstraintError
- class ConstraintError(ClientError):
- pass
- # Neo4jError > ClientError > AuthError
- class AuthError(ClientError):
- """Raised when authentication failure occurs."""
- # Neo4jError > ClientError > AuthError > TokenExpired
- class TokenExpired(AuthError):
- """Raised when the authentication token has expired."""
- # Neo4jError > ClientError > Forbidden
- class Forbidden(ClientError):
- pass
- # Neo4jError > DatabaseError
- class DatabaseError(Neo4jError):
- """The database failed to service the request."""
- # Neo4jError > TransientError
- class TransientError(Neo4jError):
- """
- Transient Error.
- The database cannot service the request right now, retrying later might
- yield a successful outcome.
- """
- _retryable = True
- # Neo4jError > TransientError > DatabaseUnavailable
- class DatabaseUnavailable(TransientError):
- pass
- # Neo4jError > TransientError > NotALeader
- class NotALeader(TransientError):
- pass
- # Neo4jError > TransientError > ForbiddenOnReadOnlyDatabase
- class ForbiddenOnReadOnlyDatabase(TransientError):
- pass
- # TODO: 6.0 - Make map private
- client_errors: dict[str, type[Neo4jError]] = {
- # ConstraintError
- "Neo.ClientError.Schema.ConstraintValidationFailed": ConstraintError,
- "Neo.ClientError.Schema.ConstraintViolation": ConstraintError,
- "Neo.ClientError.Statement.ConstraintVerificationFailed": ConstraintError,
- "Neo.ClientError.Statement.ConstraintViolation": ConstraintError,
- # CypherSyntaxError
- "Neo.ClientError.Statement.InvalidSyntax": CypherSyntaxError,
- "Neo.ClientError.Statement.SyntaxError": CypherSyntaxError,
- # CypherTypeError
- "Neo.ClientError.Procedure.TypeError": CypherTypeError,
- "Neo.ClientError.Statement.InvalidType": CypherTypeError,
- "Neo.ClientError.Statement.TypeError": CypherTypeError,
- # Forbidden
- "Neo.ClientError.General.ForbiddenOnReadOnlyDatabase": ForbiddenOnReadOnlyDatabase, # noqa: E501
- "Neo.ClientError.General.ReadOnly": Forbidden,
- "Neo.ClientError.Schema.ForbiddenOnConstraintIndex": Forbidden,
- "Neo.ClientError.Schema.IndexBelongsToConstraint": Forbidden,
- "Neo.ClientError.Security.Forbidden": Forbidden,
- "Neo.ClientError.Transaction.ForbiddenDueToTransactionType": Forbidden,
- # AuthError
- "Neo.ClientError.Security.AuthorizationFailed": AuthError,
- "Neo.ClientError.Security.Unauthorized": AuthError,
- # TokenExpired
- "Neo.ClientError.Security.TokenExpired": TokenExpired,
- # NotALeader
- "Neo.ClientError.Cluster.NotALeader": NotALeader,
- }
- # TODO: 6.0 - Make map private
- transient_errors: dict[str, type[Neo4jError]] = {
- # DatabaseUnavailableError
- "Neo.TransientError.General.DatabaseUnavailable": DatabaseUnavailable
- }
- # DriverError
- class DriverError(GqlError):
- """Raised when the Driver raises an error."""
- def is_retryable(self) -> bool:
- """
- Whether the error is retryable.
- Indicates whether a transaction that yielded this error makes sense to
- retry. This method makes mostly sense when implementing a custom
- retry policy in conjunction with :ref:`explicit-transactions-ref`.
- :returns: :data:`True` if the error is retryable,
- :data:`False` otherwise.
- .. versionadded:: 5.0
- """
- return False
- def __str__(self):
- return Exception.__str__(self)
- # DriverError > SessionError
- class SessionError(DriverError):
- """Raised when an error occurs while using a session."""
- session: _TSession
- def __init__(self, session_, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.session = session_
- # DriverError > TransactionError
- class TransactionError(DriverError):
- """Raised when an error occurs while using a transaction."""
- transaction: _TTransaction
- def __init__(self, transaction_, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.transaction = transaction_
- # DriverError > TransactionError > TransactionNestingError
- class TransactionNestingError(TransactionError):
- """Raised when transactions are nested incorrectly."""
- # DriverError > ResultError
- class ResultError(DriverError):
- """Raised when an error occurs while using a result object."""
- result: _TResult
- def __init__(self, result_, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.result = result_
- # DriverError > ResultError > ResultFailedError
- class ResultFailedError(ResultError):
- """
- Raised when trying to access records of a failed result.
- A :class:`.Result` will be considered failed if
- * itself encountered an error while fetching records
- * another result within the same transaction encountered an error while
- fetching records
- """
- # DriverError > ResultError > ResultConsumedError
- class ResultConsumedError(ResultError):
- """Raised when trying to access records of a consumed result."""
- # DriverError > ResultError > ResultNotSingleError
- class ResultNotSingleError(ResultError):
- """Raised when a result should have exactly one record but does not."""
- # DriverError > BrokenRecordError
- class BrokenRecordError(DriverError):
- """
- Raised when accessing a Record's field that couldn't be decoded.
- This can for instance happen when the server sends a zoned datetime with a
- zone id unknown to the client.
- """
- # DriverError > SessionExpired
- class SessionExpired(DriverError):
- """
- The session has expired.
- Raised when a session is no longer able to fulfil the purpose described by
- its original parameters.
- """
- def __init__(self, *args):
- super().__init__(*args)
- self._init_gql(
- gql_status="08000",
- description="error: connection exception",
- )
- def is_retryable(self) -> bool:
- return True
- # DriverError > ServiceUnavailable
- class ServiceUnavailable(DriverError):
- """
- Raised when no database service is available.
- This may be due to incorrect configuration or could indicate a runtime
- failure of a database service that the driver is unable to route around.
- """
- def __init__(self, *args):
- super().__init__(*args)
- self._init_gql(
- gql_status="08000",
- description="error: connection exception",
- )
- def is_retryable(self) -> bool:
- return True
- # DriverError > ServiceUnavailable > RoutingServiceUnavailable
- class RoutingServiceUnavailable(ServiceUnavailable):
- """Raised when no routing service is available."""
- # DriverError > ServiceUnavailable > WriteServiceUnavailable
- class WriteServiceUnavailable(ServiceUnavailable):
- """Raised when no write service is available."""
- # DriverError > ServiceUnavailable > ReadServiceUnavailable
- class ReadServiceUnavailable(ServiceUnavailable):
- """Raised when no read service is available."""
- # DriverError > ServiceUnavailable > IncompleteCommit
- class IncompleteCommit(ServiceUnavailable):
- """
- Raised when the client looses connection while committing a transaction.
- Raised when a disconnection occurs while still waiting for a commit
- response. For non-idempotent write transactions, this leaves the data
- in an unknown state with regard to whether the transaction completed
- successfully or not.
- """
- def __init__(self, *args):
- super().__init__(*args)
- self._init_gql(
- gql_status="08007",
- description=(
- "error: connection exception - "
- "transaction resolution unknown"
- ),
- )
- def is_retryable(self) -> bool:
- return False
- # DriverError > ConfigurationError
- class ConfigurationError(DriverError):
- """Raised when there is an error concerning a configuration."""
- # DriverError > ConfigurationError > AuthConfigurationError
- class AuthConfigurationError(ConfigurationError):
- """Raised when there is an error with the authentication configuration."""
- # DriverError > ConfigurationError > CertificateConfigurationError
- class CertificateConfigurationError(ConfigurationError):
- """Raised when there is an error with the certificate configuration."""
- class UnsupportedServerProduct(Exception):
- """Raised when an unsupported server product is detected."""
|