Source code for aioxmpp.errors

########################################################################
# File name: errors.py
# This file is part of: aioxmpp
#
# LICENSE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program.  If not, see
# <http://www.gnu.org/licenses/>.
#
########################################################################
"""
:mod:`~aioxmpp.errors` --- Exception classes
############################################

Exception classes mapping to XMPP stream errors
===============================================

.. autoclass:: StreamError

.. autoclass:: StreamErrorCondition

Exception classes mapping to XMPP stanza errors
===============================================

.. autoclass:: StanzaError

.. autoclass:: XMPPError

.. currentmodule:: aioxmpp

.. autoclass:: ErrorCondition

.. autoclass:: XMPPAuthError

.. autoclass:: XMPPModifyError

.. autoclass:: XMPPCancelError

.. autoclass:: XMPPWaitError

.. autoclass:: XMPPContinueError

.. currentmodule:: aioxmpp.errors

.. autoclass:: ErroneousStanza

Stream negotiation exceptions
=============================

.. autoclass:: StreamNegotiationFailure

.. autoclass:: SecurityNegotiationFailure

.. autoclass:: SASLUnavailable

.. autoclass:: TLSFailure

.. autoclass:: TLSUnavailable

I18N exceptions
===============

.. autoclass:: UserError

.. autoclass:: UserValueError

Other exceptions
================

.. autoclass:: MultiOSError

.. autoclass:: GatherError

"""
import enum
import gettext
import warnings

from . import xso, i18n, structs

from .utils import namespaces


def format_error_text(
        condition,
        text=None,
        application_defined_condition=None):
    error_tag = xso.tag_to_str(condition.value)
    if application_defined_condition is not None:
        error_tag += "/{}".format(
            xso.tag_to_str(application_defined_condition.TAG)
        )
    if text:
        error_tag += " ({!r})".format(text)
    return error_tag


[docs]class ErrorCondition(structs.CompatibilityMixin, xso.XSOEnumMixin, enum.Enum): """ Enumeration to represent a :rfc:`6120` stanza error condition. Please see :rfc:`6120`, section 8.3.3, for the semantics of the individual conditions. .. versionadded:: 0.10 .. attribute:: BAD_REQUEST :annotation: = namespaces.stanzas, "bad-request" .. attribute:: CONFLICT :annotation: = namespaces.stanzas, "conflict" .. attribute:: FEATURE_NOT_IMPLEMENTED :annotation: = namespaces.stanzas, "feature-not-implemented" .. attribute:: FORBIDDEN :annotation: = namespaces.stanzas, "forbidden" .. attribute:: GONE :annotation: = namespaces.stanzas, "gone" .. attribute:: xso_class .. attribute:: new_address The text content of the ``<gone/>`` element represtenting the URI at which the entity can now be found. May be :data:`None` if there is no such URI. .. attribute:: INTERNAL_SERVER_ERROR :annotation: = namespaces.stanzas, "internal-server-error" .. attribute:: ITEM_NOT_FOUND :annotation: = namespaces.stanzas, "item-not-found" .. attribute:: JID_MALFORMED :annotation: = namespaces.stanzas, "jid-malformed" .. attribute:: NOT_ACCEPTABLE :annotation: = namespaces.stanzas, "not-acceptable" .. attribute:: NOT_ALLOWED :annotation: = namespaces.stanzas, "not-allowed" .. attribute:: NOT_AUTHORIZED :annotation: = namespaces.stanzas, "not-authorized" .. attribute:: POLICY_VIOLATION :annotation: = namespaces.stanzas, "policy-violation" .. attribute:: RECIPIENT_UNAVAILABLE :annotation: = namespaces.stanzas, "recipient-unavailable" .. attribute:: REDIRECT :annotation: = namespaces.stanzas, "redirect" .. attribute:: xso_class .. attribute:: new_address The text content of the ``<redirect/>`` element represtenting the URI at which the entity can currently be found. May be :data:`None` if there is no such URI. .. attribute:: REGISTRATION_REQUIRED :annotation: = namespaces.stanzas, "registration-required" .. attribute:: REMOTE_SERVER_NOT_FOUND :annotation: = namespaces.stanzas, "remote-server-not-found" .. attribute:: REMOTE_SERVER_TIMEOUT :annotation: = namespaces.stanzas, "remote-server-timeout" .. attribute:: RESOURCE_CONSTRAINT :annotation: = namespaces.stanzas, "resource-constraint" .. attribute:: SERVICE_UNAVAILABLE :annotation: = namespaces.stanzas, "service-unavailable" .. attribute:: SUBSCRIPTION_REQUIRED :annotation: = namespaces.stanzas, "subscription-required" .. attribute:: UNDEFINED_CONDITION :annotation: = namespaces.stanzas, "undefined-condition" .. attribute:: UNEXPECTED_REQUEST :annotation: = namespaces.stanzas, "unexpected-request" """ BAD_REQUEST = (namespaces.stanzas, "bad-request") CONFLICT = (namespaces.stanzas, "conflict") FEATURE_NOT_IMPLEMENTED = (namespaces.stanzas, "feature-not-implemented") FORBIDDEN = (namespaces.stanzas, "forbidden") GONE = (namespaces.stanzas, "gone") INTERNAL_SERVER_ERROR = (namespaces.stanzas, "internal-server-error") ITEM_NOT_FOUND = (namespaces.stanzas, "item-not-found") JID_MALFORMED = (namespaces.stanzas, "jid-malformed") NOT_ACCEPTABLE = (namespaces.stanzas, "not-acceptable") NOT_ALLOWED = (namespaces.stanzas, "not-allowed") NOT_AUTHORIZED = (namespaces.stanzas, "not-authorized") POLICY_VIOLATION = (namespaces.stanzas, "policy-violation") RECIPIENT_UNAVAILABLE = (namespaces.stanzas, "recipient-unavailable") REDIRECT = (namespaces.stanzas, "redirect") REGISTRATION_REQUIRED = (namespaces.stanzas, "registration-required") REMOTE_SERVER_NOT_FOUND = (namespaces.stanzas, "remote-server-not-found") REMOTE_SERVER_TIMEOUT = (namespaces.stanzas, "remote-server-timeout") RESOURCE_CONSTRAINT = (namespaces.stanzas, "resource-constraint") SERVICE_UNAVAILABLE = (namespaces.stanzas, "service-unavailable") SUBSCRIPTION_REQUIRED = (namespaces.stanzas, "subscription-required") UNDEFINED_CONDITION = (namespaces.stanzas, "undefined-condition") UNEXPECTED_REQUEST = (namespaces.stanzas, "unexpected-request")
ErrorCondition.GONE.xso_class.new_address = xso.Text() ErrorCondition.REDIRECT.xso_class.new_address = xso.Text()
[docs]class StreamErrorCondition(structs.CompatibilityMixin, xso.XSOEnumMixin, enum.Enum): """ Enumeration to represent a :rfc:`6120` stream error condition. Please see :rfc:`6120`, section 4.9.3, for the semantics of the individual conditions. .. versionadded:: 0.10 .. attribute:: BAD_FORMAT :annotation: = (namespaces.streams, "bad-format") .. attribute:: BAD_NAMESPACE_PREFIX :annotation: = (namespaces.streams, "bad-namespace-prefix") .. attribute:: CONFLICT :annotation: = (namespaces.streams, "conflict") .. attribute:: CONNECTION_TIMEOUT :annotation: = (namespaces.streams, "connection-timeout") .. attribute:: HOST_GONE :annotation: = (namespaces.streams, "host-gone") .. attribute:: HOST_UNKNOWN :annotation: = (namespaces.streams, "host-unknown") .. attribute:: IMPROPER_ADDRESSING :annotation: = (namespaces.streams, "improper-addressing") .. attribute:: INTERNAL_SERVER_ERROR :annotation: = (namespaces.streams, "internal-server-error") .. attribute:: INVALID_FROM :annotation: = (namespaces.streams, "invalid-from") .. attribute:: INVALID_NAMESPACE :annotation: = (namespaces.streams, "invalid-namespace") .. attribute:: INVALID_XML :annotation: = (namespaces.streams, "invalid-xml") .. attribute:: NOT_AUTHORIZED :annotation: = (namespaces.streams, "not-authorized") .. attribute:: NOT_WELL_FORMED :annotation: = (namespaces.streams, "not-well-formed") .. attribute:: POLICY_VIOLATION :annotation: = (namespaces.streams, "policy-violation") .. attribute:: REMOTE_CONNECTION_FAILED :annotation: = (namespaces.streams, "remote-connection-failed") .. attribute:: RESET :annotation: = (namespaces.streams, "reset") .. attribute:: RESOURCE_CONSTRAINT :annotation: = (namespaces.streams, "resource-constraint") .. attribute:: RESTRICTED_XML :annotation: = (namespaces.streams, "restricted-xml") .. attribute:: SEE_OTHER_HOST :annotation: = (namespaces.streams, "see-other-host") .. attribute:: SYSTEM_SHUTDOWN :annotation: = (namespaces.streams, "system-shutdown") .. attribute:: UNDEFINED_CONDITION :annotation: = (namespaces.streams, "undefined-condition") .. attribute:: UNSUPPORTED_ENCODING :annotation: = (namespaces.streams, "unsupported-encoding") .. attribute:: UNSUPPORTED_FEATURE :annotation: = (namespaces.streams, "unsupported-feature") .. attribute:: UNSUPPORTED_STANZA_TYPE :annotation: = (namespaces.streams, "unsupported-stanza-type") .. attribute:: UNSUPPORTED_VERSION :annotation: = (namespaces.streams, "unsupported-version") """ BAD_FORMAT = (namespaces.streams, "bad-format") BAD_NAMESPACE_PREFIX = (namespaces.streams, "bad-namespace-prefix") CONFLICT = (namespaces.streams, "conflict") CONNECTION_TIMEOUT = (namespaces.streams, "connection-timeout") HOST_GONE = (namespaces.streams, "host-gone") HOST_UNKNOWN = (namespaces.streams, "host-unknown") IMPROPER_ADDRESSING = (namespaces.streams, "improper-addressing") INTERNAL_SERVER_ERROR = (namespaces.streams, "internal-server-error") INVALID_FROM = (namespaces.streams, "invalid-from") INVALID_NAMESPACE = (namespaces.streams, "invalid-namespace") INVALID_XML = (namespaces.streams, "invalid-xml") NOT_AUTHORIZED = (namespaces.streams, "not-authorized") NOT_WELL_FORMED = (namespaces.streams, "not-well-formed") POLICY_VIOLATION = (namespaces.streams, "policy-violation") REMOTE_CONNECTION_FAILED = (namespaces.streams, "remote-connection-failed") RESET = (namespaces.streams, "reset") RESOURCE_CONSTRAINT = (namespaces.streams, "resource-constraint") RESTRICTED_XML = (namespaces.streams, "restricted-xml") SEE_OTHER_HOST = (namespaces.streams, "see-other-host") SYSTEM_SHUTDOWN = (namespaces.streams, "system-shutdown") UNDEFINED_CONDITION = (namespaces.streams, "undefined-condition") UNSUPPORTED_ENCODING = (namespaces.streams, "unsupported-encoding") UNSUPPORTED_FEATURE = (namespaces.streams, "unsupported-feature") UNSUPPORTED_STANZA_TYPE = (namespaces.streams, "unsupported-stanza-type") UNSUPPORTED_VERSION = (namespaces.streams, "unsupported-version")
StreamErrorCondition.SEE_OTHER_HOST.xso_class.new_address = xso.Text()
[docs]class StreamError(ConnectionError): def __init__(self, condition, text=None): if not isinstance(condition, StreamErrorCondition): condition = StreamErrorCondition(condition) warnings.warn( "as of aioxmpp 1.0, stream error conditions must be members " "of the aioxmpp.errors.StreamErrorCondition enumeration", DeprecationWarning, stacklevel=2, ) super().__init__("stream error: {}".format( format_error_text(condition, text)) ) self.condition = condition self.text = text
[docs]class StanzaError(Exception): pass
[docs]class XMPPError(StanzaError): """ Exception representing an error defined in the XMPP protocol. :param condition: The :rfc:`6120` defined error condition as enumeration member or :class:`aioxmpp.xso.XSO` :type condition: :class:`aioxmpp.ErrorCondition` or :class:`aioxmpp.xso.XSO` :param text: Optional human-readable text explaining the error :type text: :class:`str` :param application_defined_condition: Object describing the error in more detail :type application_defined_condition: :class:`aioxmpp.xso.XSO` .. versionchanged:: 0.10 As of 0.10, `condition` should either be a :class:`aioxmpp.ErrorCondition` enumeration member or an XSO representing one of the error conditions. For compatibility, namespace-localpart tuples indicating the tag of the defined error condition are still accepted. .. deprecated:: 0.10 Starting with aioxmpp 1.0, namespace-localpart tuples will not be accepted anymore. See the changelog for notes on the transition. .. attribute:: condition_obj The :class:`aioxmpp.XSO` which represents the error condition. .. versionadded:: 0.10 .. autoattribute:: condition .. attribute:: text Optional human-readable text describing the error further. This is :data:`None` if the text is omitted. .. attribute:: application_defined_condition Optional :class:`aioxmpp.XSO` which further defines the error condition. Relevant subclasses: .. autosummary:: aioxmpp.XMPPAuthError aioxmpp.XMPPModifyError aioxmpp.XMPPCancelError aioxmpp.XMPPContinueError aioxmpp.XMPPWaitError """ TYPE = structs.ErrorType.CANCEL def __init__(self, condition, text=None, application_defined_condition=None): if not isinstance(condition, (ErrorCondition, xso.XSO)): condition = ErrorCondition(condition) warnings.warn( "as of aioxmpp 1.0, error conditions must be members of the " "aioxmpp.ErrorCondition enumeration", DeprecationWarning, stacklevel=2, ) super().__init__(format_error_text( condition.enum_member, text=text, application_defined_condition=application_defined_condition)) self.condition_obj = condition.to_xso() self.text = text self.application_defined_condition = application_defined_condition @property def condition(self): """ :class:`aioxmpp.ErrorCondition` enumeration member representing the error condition. """ return self.condition_obj.enum_member
class XMPPWarning(XMPPError, UserWarning): TYPE = structs.ErrorType.CONTINUE
[docs]class XMPPAuthError(XMPPError, PermissionError): TYPE = structs.ErrorType.AUTH
[docs]class XMPPModifyError(XMPPError, ValueError): TYPE = structs.ErrorType.MODIFY
[docs]class XMPPCancelError(XMPPError): TYPE = structs.ErrorType.CANCEL
[docs]class XMPPWaitError(XMPPError): TYPE = structs.ErrorType.WAIT
[docs]class XMPPContinueError(XMPPWarning): TYPE = structs.ErrorType.CONTINUE
[docs]class ErroneousStanza(StanzaError): """ This exception is thrown into listeners for IQ responses by :class:`aioxmpp.stream.StanzaStream` if a response for an IQ was received, but could not be decoded (due to malformed or unsupported payload). .. attribute:: partial_obj Contains the partially decoded stanza XSO. Do not rely on any members except those representing XML attributes (:attr:`~.StanzaBase.to`, :attr:`~.StanzaBase.from_`, :attr:`~.StanzaBase.type_`). """ def __init__(self, partial_obj): super().__init__("erroneous stanza received: {!r}".format( partial_obj)) self.partial_obj = partial_obj
[docs]class StreamNegotiationFailure(ConnectionError): pass
[docs]class SecurityNegotiationFailure(StreamNegotiationFailure): def __init__(self, xmpp_error, kind="Security negotiation failure", text=None): msg = "{}: {}".format(kind, xmpp_error) if text: msg += " ('{}')".format(text) super().__init__(msg) self.xmpp_error = xmpp_error self.text = text
[docs]class SASLUnavailable(SecurityNegotiationFailure): # we use this to tell the Client that SASL has not been available at all, # or that we could not agree on mechanisms. # it might be helpful to notify the peer about this before dying. pass
[docs]class TLSFailure(SecurityNegotiationFailure): def __init__(self, xmpp_error, text=None): super().__init__(xmpp_error, text=text, kind="TLS failure")
[docs]class TLSUnavailable(TLSFailure): pass
[docs]class UserError(Exception): """ An exception subclass, which should be used as a mix-in. It is intended to be used for exceptions which may be user-facing, such as connection errors, value validation issues and the like. `localizable_string` must be a :class:`.i18n.LocalizableString` instance. The `args` and `kwargs` will be passed to :class:`.LocalizableString.localize` when either :func:`str` is called on the :class:`UserError` or :meth:`localize` is called. The :func:`str` is created using the default :class:`~.i18n.LocalizingFormatter` and a :class:`gettext.NullTranslations` instance. The point in time at which the default localizing formatter is created is unspecified. .. automethod:: localize """ DEFAULT_FORMATTER = i18n.LocalizingFormatter() DEFAULT_TRANSLATIONS = gettext.NullTranslations() def __init__(self, localizable_string, *args, **kwargs): super().__init__() self._str = localizable_string.localize( self.DEFAULT_FORMATTER, self.DEFAULT_TRANSLATIONS, *args, **kwargs) self.localizable_string = localizable_string self.args = args self.kwargs = kwargs def __str__(self): return str(self._str)
[docs] def localize(self, formatter, translator): """ Return a localized version of the `localizable_string` passed to the constructor. It is formatted using the `formatter` with the `args` and `kwargs` passed to the constructor of :class:`UserError`. """ return self.localizable_string.localize( formatter, translator, *self.args, **self.kwargs )
[docs]class UserValueError(UserError, ValueError): """ This is a :class:`ValueError` with :class:`UserError` mixed in. """
[docs]class MultiOSError(OSError): """ Describe an error situation which has been caused by the sequential occurrence of multiple other `exceptions`. The `message` shall be descriptive and will be prepended to a concatenation of the error messages of the given `exceptions`. """ def __init__(self, message, exceptions): flattened_exceptions = [] for exc in exceptions: if hasattr(exc, "exceptions"): flattened_exceptions.extend(exc.exceptions) else: flattened_exceptions.append(exc) super().__init__( "{}: multiple errors: {}".format( message, ", ".join(map(str, flattened_exceptions)) ) ) self.exceptions = flattened_exceptions
[docs]class GatherError(RuntimeError): """ Describe an error situation which has been caused by the occurrence of multiple other `exceptions`. The `message` shall be descriptive and will be prepended to a concatenation of the error messages of the given `exceptions`. """ def __init__(self, message, exceptions): flattened_exceptions = [] for exc in exceptions: if hasattr(exc, "exceptions"): flattened_exceptions.extend(exc.exceptions) else: flattened_exceptions.append(exc) super().__init__( "{}: multiple errors: {}".format( message, ", ".join(map(str, flattened_exceptions)) ) ) self.exceptions = flattened_exceptions