Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/dns/exception.py: 70%

60 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-02 06:07 +0000

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 

24 

25from typing import Optional, Set 

26 

27 

28class DNSException(Exception): 

29 """Abstract base class shared by all dnspython exceptions. 

30 

31 It supports two basic modes of operation: 

32 

33 a) Old/compatible mode is used if ``__init__`` was called with 

34 empty *kwargs*. In compatible mode all *args* are passed 

35 to the standard Python Exception class as before and all *args* are 

36 printed by the standard ``__str__`` implementation. Class variable 

37 ``msg`` (or doc string if ``msg`` is ``None``) is returned from ``str()`` 

38 if *args* is empty. 

39 

40 b) New/parametrized mode is used if ``__init__`` was called with 

41 non-empty *kwargs*. 

42 In the new mode *args* must be empty and all kwargs must match 

43 those set in class variable ``supp_kwargs``. All kwargs are stored inside 

44 ``self.kwargs`` and used in a new ``__str__`` implementation to construct 

45 a formatted message based on the ``fmt`` class variable, a ``string``. 

46 

47 In the simplest case it is enough to override the ``supp_kwargs`` 

48 and ``fmt`` class variables to get nice parametrized messages. 

49 """ 

50 

51 msg: Optional[str] = None # non-parametrized message 

52 supp_kwargs: Set[str] = set() # accepted parameters for _fmt_kwargs (sanity check) 

53 fmt: Optional[str] = None # message parametrized with results from _fmt_kwargs 

54 

55 def __init__(self, *args, **kwargs): 

56 self._check_params(*args, **kwargs) 

57 if kwargs: 

58 # This call to a virtual method from __init__ is ok in our usage 

59 self.kwargs = self._check_kwargs(**kwargs) # lgtm[py/init-calls-subclass] 

60 self.msg = str(self) 

61 else: 

62 self.kwargs = dict() # defined but empty for old mode exceptions 

63 if self.msg is None: 

64 # doc string is better implicit message than empty string 

65 self.msg = self.__doc__ 

66 if args: 

67 super().__init__(*args) 

68 else: 

69 super().__init__(self.msg) 

70 

71 def _check_params(self, *args, **kwargs): 

72 """Old exceptions supported only args and not kwargs. 

73 

74 For sanity we do not allow to mix old and new behavior.""" 

75 if args or kwargs: 

76 assert bool(args) != bool( 

77 kwargs 

78 ), "keyword arguments are mutually exclusive with positional args" 

79 

80 def _check_kwargs(self, **kwargs): 

81 if kwargs: 

82 assert ( 

83 set(kwargs.keys()) == self.supp_kwargs 

84 ), "following set of keyword args is required: %s" % (self.supp_kwargs) 

85 return kwargs 

86 

87 def _fmt_kwargs(self, **kwargs): 

88 """Format kwargs before printing them. 

89 

90 Resulting dictionary has to have keys necessary for str.format call 

91 on fmt class variable. 

92 """ 

93 fmtargs = {} 

94 for kw, data in kwargs.items(): 

95 if isinstance(data, (list, set)): 

96 # convert list of <someobj> to list of str(<someobj>) 

97 fmtargs[kw] = list(map(str, data)) 

98 if len(fmtargs[kw]) == 1: 

99 # remove list brackets [] from single-item lists 

100 fmtargs[kw] = fmtargs[kw].pop() 

101 else: 

102 fmtargs[kw] = data 

103 return fmtargs 

104 

105 def __str__(self): 

106 if self.kwargs and self.fmt: 

107 # provide custom message constructed from keyword arguments 

108 fmtargs = self._fmt_kwargs(**self.kwargs) 

109 return self.fmt.format(**fmtargs) 

110 else: 

111 # print *args directly in the same way as old DNSException 

112 return super().__str__() 

113 

114 

115class FormError(DNSException): 

116 """DNS message is malformed.""" 

117 

118 

119class SyntaxError(DNSException): 

120 """Text input is malformed.""" 

121 

122 

123class UnexpectedEnd(SyntaxError): 

124 """Text input ended unexpectedly.""" 

125 

126 

127class TooBig(DNSException): 

128 """The DNS message is too big.""" 

129 

130 

131class Timeout(DNSException): 

132 """The DNS operation timed out.""" 

133 

134 supp_kwargs = {"timeout"} 

135 fmt = "The DNS operation timed out after {timeout:.3f} seconds" 

136 

137 # We do this as otherwise mypy complains about unexpected keyword argument 

138 # idna_exception 

139 def __init__(self, *args, **kwargs): 

140 super().__init__(*args, **kwargs) 

141 

142 

143class UnsupportedAlgorithm(DNSException): 

144 """The DNSSEC algorithm is not supported.""" 

145 

146 

147class AlgorithmKeyMismatch(UnsupportedAlgorithm): 

148 """The DNSSEC algorithm is not supported for the given key type.""" 

149 

150 

151class ValidationFailure(DNSException): 

152 """The DNSSEC signature is invalid.""" 

153 

154 

155class DeniedByPolicy(DNSException): 

156 """Denied by DNSSEC policy.""" 

157 

158 

159class ExceptionWrapper: 

160 def __init__(self, exception_class): 

161 self.exception_class = exception_class 

162 

163 def __enter__(self): 

164 return self 

165 

166 def __exit__(self, exc_type, exc_val, exc_tb): 

167 if exc_type is not None and not isinstance(exc_val, self.exception_class): 

168 raise self.exception_class(str(exc_val)) from exc_val 

169 return False