Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1############################################################################## 

2# 

3# Copyright (c) 2002 Zope Foundation and Contributors. 

4# All Rights Reserved. 

5# 

6# This software is subject to the provisions of the Zope Public License, 

7# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 

8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 

9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 

10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 

11# FOR A PARTICULAR PURPOSE. 

12# 

13############################################################################## 

14"""Interface-specific exceptions 

15""" 

16 

17__all__ = [ 

18 # Invalid tree 

19 'Invalid', 

20 'DoesNotImplement', 

21 'BrokenImplementation', 

22 'BrokenMethodImplementation', 

23 'MultipleInvalid', 

24 # Other 

25 'BadImplements', 

26 'InvalidInterface', 

27] 

28 

29class Invalid(Exception): 

30 """A specification is violated 

31 """ 

32 

33 

34class _TargetInvalid(Invalid): 

35 # Internal use. Subclass this when you're describing 

36 # a particular target object that's invalid according 

37 # to a specific interface. 

38 # 

39 # For backwards compatibility, the *target* and *interface* are 

40 # optional, and the signatures are inconsistent in their ordering. 

41 # 

42 # We deal with the inconsistency in ordering by defining the index 

43 # of the two values in ``self.args``. *target* uses a marker object to 

44 # distinguish "not given" from "given, but None", because the latter 

45 # can be a value that gets passed to validation. For this reason, it must 

46 # always be the last argument (we detect absense by the ``IndexError``). 

47 

48 _IX_INTERFACE = 0 

49 _IX_TARGET = 1 

50 # The exception to catch when indexing self.args indicating that 

51 # an argument was not given. If all arguments are expected, 

52 # a subclass should set this to (). 

53 _NOT_GIVEN_CATCH = IndexError 

54 _NOT_GIVEN = '<Not Given>' 

55 

56 def _get_arg_or_default(self, ix, default=None): 

57 try: 

58 return self.args[ix] # pylint:disable=unsubscriptable-object 

59 except self._NOT_GIVEN_CATCH: 

60 return default 

61 

62 @property 

63 def interface(self): 

64 return self._get_arg_or_default(self._IX_INTERFACE) 

65 

66 @property 

67 def target(self): 

68 return self._get_arg_or_default(self._IX_TARGET, self._NOT_GIVEN) 

69 

70 ### 

71 # str 

72 # 

73 # The ``__str__`` of self is implemented by concatenating (%s), in order, 

74 # these properties (none of which should have leading or trailing 

75 # whitespace): 

76 # 

77 # - self._str_subject 

78 # Begin the message, including a description of the target. 

79 # - self._str_description 

80 # Provide a general description of the type of error, including 

81 # the interface name if possible and relevant. 

82 # - self._str_conjunction 

83 # Join the description to the details. Defaults to ": ". 

84 # - self._str_details 

85 # Provide details about how this particular instance of the error. 

86 # - self._str_trailer 

87 # End the message. Usually just a period. 

88 ### 

89 

90 @property 

91 def _str_subject(self): 

92 target = self.target 

93 if target is self._NOT_GIVEN: 

94 return "An object" 

95 return "The object %r" % (target,) 

96 

97 @property 

98 def _str_description(self): 

99 return "has failed to implement interface %s" % ( 

100 self.interface or '<Unknown>' 

101 ) 

102 

103 _str_conjunction = ": " 

104 _str_details = "<unknown>" 

105 _str_trailer = '.' 

106 

107 def __str__(self): 

108 return "%s %s%s%s%s" % ( 

109 self._str_subject, 

110 self._str_description, 

111 self._str_conjunction, 

112 self._str_details, 

113 self._str_trailer 

114 ) 

115 

116 

117class DoesNotImplement(_TargetInvalid): 

118 """ 

119 DoesNotImplement(interface[, target]) 

120 

121 The *target* (optional) does not implement the *interface*. 

122 

123 .. versionchanged:: 5.0.0 

124 Add the *target* argument and attribute, and change the resulting 

125 string value of this object accordingly. 

126 """ 

127 

128 _str_details = "Does not declaratively implement the interface" 

129 

130 

131class BrokenImplementation(_TargetInvalid): 

132 """ 

133 BrokenImplementation(interface, name[, target]) 

134 

135 The *target* (optional) is missing the attribute *name*. 

136 

137 .. versionchanged:: 5.0.0 

138 Add the *target* argument and attribute, and change the resulting 

139 string value of this object accordingly. 

140 

141 The *name* can either be a simple string or a ``Attribute`` object. 

142 """ 

143 

144 _IX_NAME = _TargetInvalid._IX_INTERFACE + 1 

145 _IX_TARGET = _IX_NAME + 1 

146 

147 @property 

148 def name(self): 

149 return self.args[1] # pylint:disable=unsubscriptable-object 

150 

151 @property 

152 def _str_details(self): 

153 return "The %s attribute was not provided" % ( 

154 repr(self.name) if isinstance(self.name, str) else self.name 

155 ) 

156 

157 

158class BrokenMethodImplementation(_TargetInvalid): 

159 """ 

160 BrokenMethodImplementation(method, message[, implementation, interface, target]) 

161 

162 The *target* (optional) has a *method* in *implementation* that violates 

163 its contract in a way described by *mess*. 

164 

165 .. versionchanged:: 5.0.0 

166 Add the *interface* and *target* argument and attribute, 

167 and change the resulting string value of this object accordingly. 

168 

169 The *method* can either be a simple string or a ``Method`` object. 

170 

171 .. versionchanged:: 5.0.0 

172 If *implementation* is given, then the *message* will have the 

173 string "implementation" replaced with an short but informative 

174 representation of *implementation*. 

175 

176 """ 

177 

178 _IX_IMPL = 2 

179 _IX_INTERFACE = _IX_IMPL + 1 

180 _IX_TARGET = _IX_INTERFACE + 1 

181 

182 @property 

183 def method(self): 

184 return self.args[0] # pylint:disable=unsubscriptable-object 

185 

186 @property 

187 def mess(self): 

188 return self.args[1] # pylint:disable=unsubscriptable-object 

189 

190 @staticmethod 

191 def __implementation_str(impl): 

192 # It could be a callable or some arbitrary object, we don't 

193 # know yet. 

194 import inspect # Inspect is a heavy-weight dependency, lots of imports 

195 try: 

196 sig = inspect.signature 

197 formatsig = str 

198 except AttributeError: 

199 sig = inspect.getargspec 

200 f = inspect.formatargspec 

201 formatsig = lambda sig: f(*sig) # pylint:disable=deprecated-method 

202 

203 try: 

204 sig = sig(impl) 

205 except (ValueError, TypeError): 

206 # Unable to introspect. Darn. 

207 # This could be a non-callable, or a particular builtin, 

208 # or a bound method that doesn't even accept 'self', e.g., 

209 # ``Class.method = lambda: None; Class().method`` 

210 return repr(impl) 

211 

212 try: 

213 name = impl.__qualname__ 

214 except AttributeError: 

215 name = impl.__name__ 

216 

217 return name + formatsig(sig) 

218 

219 @property 

220 def _str_details(self): 

221 impl = self._get_arg_or_default(self._IX_IMPL, self._NOT_GIVEN) 

222 message = self.mess 

223 if impl is not self._NOT_GIVEN and 'implementation' in message: 

224 message = message.replace("implementation", '%r') 

225 message = message % (self.__implementation_str(impl),) 

226 

227 return 'The contract of %s is violated because %s' % ( 

228 repr(self.method) if isinstance(self.method, str) else self.method, 

229 message, 

230 ) 

231 

232 

233class MultipleInvalid(_TargetInvalid): 

234 """ 

235 The *target* has failed to implement the *interface* in 

236 multiple ways. 

237 

238 The failures are described by *exceptions*, a collection of 

239 other `Invalid` instances. 

240 

241 .. versionadded:: 5.0 

242 """ 

243 

244 _NOT_GIVEN_CATCH = () 

245 

246 def __init__(self, interface, target, exceptions): 

247 super(MultipleInvalid, self).__init__(interface, target, tuple(exceptions)) 

248 

249 @property 

250 def exceptions(self): 

251 return self.args[2] # pylint:disable=unsubscriptable-object 

252 

253 @property 

254 def _str_details(self): 

255 # It would be nice to use tabs here, but that 

256 # is hard to represent in doctests. 

257 return '\n ' + '\n '.join( 

258 x._str_details.strip() if isinstance(x, _TargetInvalid) else str(x) 

259 for x in self.exceptions 

260 ) 

261 

262 _str_conjunction = ':' # We don't want a trailing space, messes up doctests 

263 _str_trailer = '' 

264 

265 

266class InvalidInterface(Exception): 

267 """The interface has invalid contents 

268 """ 

269 

270class BadImplements(TypeError): 

271 """An implementation assertion is invalid 

272 

273 because it doesn't contain an interface or a sequence of valid 

274 implementation assertions. 

275 """