1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license 2 3# Copyright (C) 2003-2017 Nominum, Inc. 4# 5# Permission to use, copy, modify, and distribute this software and its 6# documentation for any purpose with or without fee is hereby granted, 7# provided that the above copyright notice and this permission notice 8# appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 16# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18"""Common DNS Exceptions. 19 20Dnspython modules may also define their own exceptions, which will 21always be subclasses of ``DNSException``. 22""" 23 24class DNSException(Exception): 25 """Abstract base class shared by all dnspython exceptions. 26 27 It supports two basic modes of operation: 28 29 a) Old/compatible mode is used if ``__init__`` was called with 30 empty *kwargs*. In compatible mode all *args* are passed 31 to the standard Python Exception class as before and all *args* are 32 printed by the standard ``__str__`` implementation. Class variable 33 ``msg`` (or doc string if ``msg`` is ``None``) is returned from ``str()`` 34 if *args* is empty. 35 36 b) New/parametrized mode is used if ``__init__`` was called with 37 non-empty *kwargs*. 38 In the new mode *args* must be empty and all kwargs must match 39 those set in class variable ``supp_kwargs``. All kwargs are stored inside 40 ``self.kwargs`` and used in a new ``__str__`` implementation to construct 41 a formatted message based on the ``fmt`` class variable, a ``string``. 42 43 In the simplest case it is enough to override the ``supp_kwargs`` 44 and ``fmt`` class variables to get nice parametrized messages. 45 """ 46 47 msg = None # non-parametrized message 48 supp_kwargs = set() # accepted parameters for _fmt_kwargs (sanity check) 49 fmt = None # message parametrized with results from _fmt_kwargs 50 51 def __init__(self, *args, **kwargs): 52 self._check_params(*args, **kwargs) 53 if kwargs: 54 self.kwargs = self._check_kwargs(**kwargs) 55 self.msg = str(self) 56 else: 57 self.kwargs = dict() # defined but empty for old mode exceptions 58 if self.msg is None: 59 # doc string is better implicit message than empty string 60 self.msg = self.__doc__ 61 if args: 62 super(DNSException, self).__init__(*args) 63 else: 64 super(DNSException, self).__init__(self.msg) 65 66 def _check_params(self, *args, **kwargs): 67 """Old exceptions supported only args and not kwargs. 68 69 For sanity we do not allow to mix old and new behavior.""" 70 if args or kwargs: 71 assert bool(args) != bool(kwargs), \ 72 'keyword arguments are mutually exclusive with positional args' 73 74 def _check_kwargs(self, **kwargs): 75 if kwargs: 76 assert set(kwargs.keys()) == self.supp_kwargs, \ 77 'following set of keyword args is required: %s' % ( 78 self.supp_kwargs) 79 return kwargs 80 81 def _fmt_kwargs(self, **kwargs): 82 """Format kwargs before printing them. 83 84 Resulting dictionary has to have keys necessary for str.format call 85 on fmt class variable. 86 """ 87 fmtargs = {} 88 for kw, data in kwargs.items(): 89 if isinstance(data, (list, set)): 90 # convert list of <someobj> to list of str(<someobj>) 91 fmtargs[kw] = list(map(str, data)) 92 if len(fmtargs[kw]) == 1: 93 # remove list brackets [] from single-item lists 94 fmtargs[kw] = fmtargs[kw].pop() 95 else: 96 fmtargs[kw] = data 97 return fmtargs 98 99 def __str__(self): 100 if self.kwargs and self.fmt: 101 # provide custom message constructed from keyword arguments 102 fmtargs = self._fmt_kwargs(**self.kwargs) 103 return self.fmt.format(**fmtargs) 104 else: 105 # print *args directly in the same way as old DNSException 106 return super(DNSException, self).__str__() 107 108 109class FormError(DNSException): 110 """DNS message is malformed.""" 111 112 113class SyntaxError(DNSException): 114 """Text input is malformed.""" 115 116 117class UnexpectedEnd(SyntaxError): 118 """Text input ended unexpectedly.""" 119 120 121class TooBig(DNSException): 122 """The DNS message is too big.""" 123 124 125class Timeout(DNSException): 126 """The DNS operation timed out.""" 127 supp_kwargs = {'timeout'} 128 fmt = "The DNS operation timed out after {timeout} seconds" 129