Coverage for aiocoap/numbers/codes.py: 97%

131 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-15 22:10 +0000

1# SPDX-FileCopyrightText: Christian Amsüss and the aiocoap contributors 

2# 

3# SPDX-License-Identifier: MIT 

4 

5"""List of known values for the CoAP "Code" field. 

6 

7The values in this module correspond to the IANA registry "`CoRE Parameters`_", 

8subregistries "CoAP Method Codes" and "CoAP Response Codes". 

9 

10The codes come with methods that can be used to get their rough meaning, see 

11the :class:`Code` class for details. 

12 

13.. _`CoRE Parameters`: https://www.iana.org/assignments/core-parameters/core-parameters.xhtml 

14""" 

15 

16import warnings 

17 

18from ..util import ExtensibleIntEnum 

19 

20 

21class Code(ExtensibleIntEnum): 

22 """Value for the CoAP "Code" field. 

23 

24 As the number range for the code values is separated, the rough meaning of 

25 a code can be determined using the :meth:`is_request`, :meth:`is_response` and 

26 :meth:`is_successful` methods.""" 

27 

28 EMPTY = 0 

29 GET = 1 

30 POST = 2 

31 PUT = 3 

32 DELETE = 4 

33 FETCH = 5 

34 PATCH = 6 

35 iPATCH = 7 

36 CREATED = 65 

37 DELETED = 66 

38 VALID = 67 

39 CHANGED = 68 

40 CONTENT = 69 

41 CONTINUE = 95 

42 BAD_REQUEST = 128 

43 UNAUTHORIZED = 129 

44 BAD_OPTION = 130 

45 FORBIDDEN = 131 

46 NOT_FOUND = 132 

47 METHOD_NOT_ALLOWED = 133 

48 NOT_ACCEPTABLE = 134 

49 REQUEST_ENTITY_INCOMPLETE = 136 

50 CONFLICT = (4 << 5) + 9 

51 PRECONDITION_FAILED = 140 

52 REQUEST_ENTITY_TOO_LARGE = 141 

53 UNSUPPORTED_CONTENT_FORMAT = 143 

54 

55 @property 

56 def UNSUPPORTED_MEDIA_TYPE(self): 

57 warnings.warn( 

58 "UNSUPPORTED_MEDIA_TYPE is a deprecated alias for UNSUPPORTED_CONTENT_FORMAT" 

59 ) 

60 return self.UNSUPPORTED_CONTENT_FORMAT 

61 

62 UNPROCESSABLE_ENTITY = (4 << 5) + 22 

63 TOO_MANY_REQUESTS = (4 << 5) + 29 

64 INTERNAL_SERVER_ERROR = 160 

65 NOT_IMPLEMENTED = 161 

66 BAD_GATEWAY = 162 

67 SERVICE_UNAVAILABLE = 163 

68 GATEWAY_TIMEOUT = 164 

69 PROXYING_NOT_SUPPORTED = 165 

70 HOP_LIMIT_REACHED = (5 << 5) + 8 

71 

72 CSM = 225 

73 PING = 226 

74 PONG = 227 

75 RELEASE = 228 

76 ABORT = 229 

77 

78 def is_request(self): 

79 """True if the code is in the request code range""" 

80 return True if (self >= 1 and self < 32) else False 

81 

82 def is_response(self): 

83 """True if the code is in the response code range""" 

84 return True if (self >= 64 and self < 192) else False 

85 

86 def is_signalling(self): 

87 return True if self >= 224 else False 

88 

89 def is_successful(self): 

90 """True if the code is in the successful subrange of the response code range""" 

91 return True if (self >= 64 and self < 96) else False 

92 

93 def can_have_payload(self): 

94 """True if a message with that code can carry a payload. This is not 

95 checked for strictly, but used as an indicator.""" 

96 return self.is_response() or self in ( 

97 self.POST, 

98 self.PUT, 

99 self.FETCH, 

100 self.PATCH, 

101 self.iPATCH, 

102 ) 

103 

104 @property 

105 def class_(self): 

106 """The class of a code (distinguishing whether it's successful, a 

107 request or a response error or more). 

108 

109 >>> Code.CONTENT 

110 <Successful Response Code 69 "2.05 Content"> 

111 >>> Code.CONTENT.class_ 

112 2 

113 >>> Code.BAD_GATEWAY 

114 <Response Code 162 "5.02 Bad Gateway"> 

115 >>> Code.BAD_GATEWAY.class_ 

116 5 

117 """ 

118 return self >> 5 

119 

120 @property 

121 def dotted(self): 

122 """The numeric value three-decimal-digits (c.dd) form""" 

123 return "%d.%02d" % divmod(self, 32) 

124 

125 @property 

126 def name_printable(self): 

127 """The name of the code in human-readable form""" 

128 return self.name.replace("_", " ").title() 

129 

130 def __str__(self): 

131 """ 

132 >>> print(Code.GET) 

133 GET 

134 >>> print(Code.CONTENT) 

135 2.05 Content 

136 >>> print(Code.BAD_GATEWAY) 

137 5.02 Bad Gateway 

138 >>> print(Code(32)) 

139 32 

140 """ 

141 if self.is_request() or self is self.EMPTY: 

142 return self.name 

143 elif self.is_response() or self.is_signalling(): 

144 return "%s %s" % (self.dotted, self.name_printable) 

145 else: 

146 return "%d" % self 

147 

148 def _classification(self): 

149 return ("Successful " if self.is_successful() else "") + ( 

150 "Request " 

151 if self.is_request() 

152 else "Response " 

153 if self.is_response() 

154 else "" 

155 ) 

156 

157 def __repr__(self): 

158 """ 

159 >>> Code.GET 

160 <Request Code 1 "GET"> 

161 >>> Code.CONTENT 

162 <Successful Response Code 69 "2.05 Content"> 

163 >>> Code.BAD_GATEWAY 

164 <Response Code 162 "5.02 Bad Gateway"> 

165 >>> Code(32) 

166 <Code 32 "32"> 

167 """ 

168 return '<%sCode %d "%s">' % (self._classification(), self, self) 

169 

170 def _repr_html_(self): 

171 """ 

172 >>> Code.GET._repr_html_() 

173 '<abbr title="Request Code 0.01">GET</abbr>' 

174 >>> Code(31)._repr_html_() 

175 '<abbr title="Unknown Request Code">0.31</abbr>' 

176 """ 

177 import html 

178 

179 if self.name == "(unknown)": 

180 return f'<abbr title="Unknown {self._classification()}Code">{self.dotted}</abbr>' 

181 else: 

182 return f'<abbr title="{self._classification()}Code {self.dotted}">{html.escape(self.name)}</abbr>' 

183 

184 @classmethod 

185 def _missing_(cls, value): 

186 constructed = super()._missing_(value) 

187 constructed._name_ = "(unknown)" 

188 return constructed 

189 

190 

191# List is copied down to help mypy and other typing dependent tools to 

192# understand the types. 

193EMPTY = Code.EMPTY 

194GET = Code.GET 

195POST = Code.POST 

196PUT = Code.PUT 

197DELETE = Code.DELETE 

198FETCH = Code.FETCH 

199PATCH = Code.PATCH 

200iPATCH = Code.iPATCH 

201CREATED = Code.CREATED 

202DELETED = Code.DELETED 

203VALID = Code.VALID 

204CHANGED = Code.CHANGED 

205CONTENT = Code.CONTENT 

206CONTINUE = Code.CONTINUE 

207BAD_REQUEST = Code.BAD_REQUEST 

208UNAUTHORIZED = Code.UNAUTHORIZED 

209BAD_OPTION = Code.BAD_OPTION 

210FORBIDDEN = Code.FORBIDDEN 

211NOT_FOUND = Code.NOT_FOUND 

212METHOD_NOT_ALLOWED = Code.METHOD_NOT_ALLOWED 

213NOT_ACCEPTABLE = Code.NOT_ACCEPTABLE 

214REQUEST_ENTITY_INCOMPLETE = Code.REQUEST_ENTITY_INCOMPLETE 

215CONFLICT = Code.CONFLICT 

216PRECONDITION_FAILED = Code.PRECONDITION_FAILED 

217REQUEST_ENTITY_TOO_LARGE = Code.REQUEST_ENTITY_TOO_LARGE 

218UNSUPPORTED_CONTENT_FORMAT = Code.UNSUPPORTED_CONTENT_FORMAT 

219UNPROCESSABLE_ENTITY = Code.UNPROCESSABLE_ENTITY 

220TOO_MANY_REQUESTS = Code.TOO_MANY_REQUESTS 

221INTERNAL_SERVER_ERROR = Code.INTERNAL_SERVER_ERROR 

222NOT_IMPLEMENTED = Code.NOT_IMPLEMENTED 

223BAD_GATEWAY = Code.BAD_GATEWAY 

224SERVICE_UNAVAILABLE = Code.SERVICE_UNAVAILABLE 

225GATEWAY_TIMEOUT = Code.GATEWAY_TIMEOUT 

226PROXYING_NOT_SUPPORTED = Code.PROXYING_NOT_SUPPORTED 

227HOP_LIMIT_REACHED = Code.HOP_LIMIT_REACHED 

228CSM = Code.CSM 

229PING = Code.PING 

230PONG = Code.PONG 

231RELEASE = Code.RELEASE 

232ABORT = Code.ABORT 

233if __debug__: 

234 for _code in Code: 

235 if locals().get(_code.name) is not _code: 

236 warnings.warn( 

237 f"Locals list is out of sync; entry `{_code.name} = Code.{_code.name}` is missing" 

238 ) 

239 

240 

241__all__ = ["Code"] + [ 

242 k for (k, v) in locals().items() if isinstance(v, Code) and not k.startswith("_") 

243]