Coverage for src/aiocoap/error.py: 0%
158 statements
« prev ^ index » next coverage.py v7.7.0, created at 2025-03-20 17:26 +0000
« prev ^ index » next coverage.py v7.7.0, created at 2025-03-20 17:26 +0000
1# SPDX-FileCopyrightText: Christian Amsüss and the aiocoap contributors
2#
3# SPDX-License-Identifier: MIT
5"""
6Common errors for the aiocoap library
7"""
9import warnings
10import abc
11import errno
12from typing import Optional
14from .numbers import codes
15from . import util
18class Error(Exception):
19 """
20 Base exception for all exceptions that indicate a failed request
21 """
24class HelpfulError(Error):
25 def __str__(self):
26 """User presentable string. This should start with "Error:", or with
27 "Something Error:", because the context will not show that this was an
28 error."""
29 return type(self).__name__
31 def extra_help(self, hints={}) -> Optional[str]:
32 """Information printed at aiocoap-client or similar occasions when the
33 error message itself may be insufficient to point the user in the right
34 direction
36 The `hints` dictonary may be populated with context that the caller
37 has; the implementation must tolerate their absence. Currently
38 established keys:
40 * original_uri (str): URI that was attemted to access
41 * request (Message): Request that was assembled to be sent
42 """
43 return None
46class RenderableError(Error, metaclass=abc.ABCMeta):
47 """
48 Exception that can meaningfully be represented in a CoAP response
49 """
51 @abc.abstractmethod
52 def to_message(self):
53 """Create a CoAP message that should be sent when this exception is
54 rendered"""
57class ResponseWrappingError(Error):
58 """
59 An exception that is raised due to an unsuccessful but received response.
61 A better relationship with :mod:`.numbers.codes` should be worked out to do
62 ``except UnsupportedMediaType`` (similar to the various ``OSError``
63 subclasses).
64 """
66 def __init__(self, coapmessage):
67 self.coapmessage = coapmessage
69 def to_message(self):
70 return self.coapmessage
72 def __repr__(self):
73 return "<%s: %s %r>" % (
74 type(self).__name__,
75 self.coapmessage.code,
76 self.coapmessage.payload,
77 )
80class ConstructionRenderableError(RenderableError):
81 """
82 RenderableError that is constructed from class attributes :attr:`code` and
83 :attr:`message` (where the can be overridden in the constructor).
84 """
86 def __init__(self, message=None):
87 if message is not None:
88 self.message = message
90 def to_message(self):
91 from .message import Message
93 return Message(code=self.code, payload=self.message.encode("utf8"))
95 code = codes.INTERNAL_SERVER_ERROR #: Code assigned to messages built from it
96 message = "" #: Text sent in the built message's payload
99# This block is code-generated to make the types available to static checkers.
100# The __debug__ check below ensures that it stays up to date.
101class BadRequest(ConstructionRenderableError):
102 code = codes.BAD_REQUEST
105class Unauthorized(ConstructionRenderableError):
106 code = codes.UNAUTHORIZED
109class BadOption(ConstructionRenderableError):
110 code = codes.BAD_OPTION
113class Forbidden(ConstructionRenderableError):
114 code = codes.FORBIDDEN
117class NotFound(ConstructionRenderableError):
118 code = codes.NOT_FOUND
121class MethodNotAllowed(ConstructionRenderableError):
122 code = codes.METHOD_NOT_ALLOWED
125class NotAcceptable(ConstructionRenderableError):
126 code = codes.NOT_ACCEPTABLE
129class RequestEntityIncomplete(ConstructionRenderableError):
130 code = codes.REQUEST_ENTITY_INCOMPLETE
133class Conflict(ConstructionRenderableError):
134 code = codes.CONFLICT
137class PreconditionFailed(ConstructionRenderableError):
138 code = codes.PRECONDITION_FAILED
141class RequestEntityTooLarge(ConstructionRenderableError):
142 code = codes.REQUEST_ENTITY_TOO_LARGE
145class UnsupportedContentFormat(ConstructionRenderableError):
146 code = codes.UNSUPPORTED_CONTENT_FORMAT
149class UnprocessableEntity(ConstructionRenderableError):
150 code = codes.UNPROCESSABLE_ENTITY
153class TooManyRequests(ConstructionRenderableError):
154 code = codes.TOO_MANY_REQUESTS
157class InternalServerError(ConstructionRenderableError):
158 code = codes.INTERNAL_SERVER_ERROR
161class NotImplemented(ConstructionRenderableError):
162 code = codes.NOT_IMPLEMENTED
165class BadGateway(ConstructionRenderableError):
166 code = codes.BAD_GATEWAY
169class ServiceUnavailable(ConstructionRenderableError):
170 code = codes.SERVICE_UNAVAILABLE
173class GatewayTimeout(ConstructionRenderableError):
174 code = codes.GATEWAY_TIMEOUT
177class ProxyingNotSupported(ConstructionRenderableError):
178 code = codes.PROXYING_NOT_SUPPORTED
181class HopLimitReached(ConstructionRenderableError):
182 code = codes.HOP_LIMIT_REACHED
185if __debug__:
186 _missing_codes = False
187 _full_code = ""
188 for code in codes.Code:
189 if code.is_successful() or not code.is_response():
190 continue
191 classname = "".join(w.title() for w in code.name.split("_"))
192 _full_code += f"""
193class {classname}(ConstructionRenderableError):
194 code = codes.{code.name}"""
195 if classname not in locals():
196 warnings.warn(f"Missing exception type: f{classname}")
197 _missing_codes = True
198 continue
199 if locals()[classname].code != code:
200 warnings.warn(
201 f"Mismatched code for {classname}: Should be {code}, is {locals()[classname].code}"
202 )
203 _missing_codes = True
204 continue
205 if _missing_codes:
206 warnings.warn(
207 "Generated exception list is out of sync, should be:\n" + _full_code
208 )
210# More detailed versions of code based errors
213class NoResource(NotFound):
214 """
215 Raised when resource is not found.
216 """
218 message = "Error: Resource not found!"
220 def __init__(self):
221 warnings.warn(
222 "NoResource is deprecated in favor of NotFound",
223 DeprecationWarning,
224 stacklevel=2,
225 )
228class UnallowedMethod(MethodNotAllowed):
229 """
230 Raised by a resource when request method is understood by the server
231 but not allowed for that particular resource.
232 """
234 message = "Error: Method not allowed!"
237class UnsupportedMethod(MethodNotAllowed):
238 """
239 Raised when request method is not understood by the server at all.
240 """
242 message = "Error: Method not recognized!"
245class NetworkError(HelpfulError):
246 """Base class for all "something went wrong with name resolution, sending
247 or receiving packages".
249 Errors of these kinds are raised towards client callers when things went
250 wrong network-side, or at context creation. They are often raised from
251 socket.gaierror or similar classes, but these are wrapped in order to make
252 catching them possible independently of the underlying transport."""
254 def __str__(self):
255 return f"Network error: {type(self).__name__}"
257 def extra_help(self, hints={}):
258 if isinstance(self.__cause__, OSError):
259 if self.__cause__.errno == errno.ECONNREFUSED:
260 # seen trying to reach any used address with the port closed
261 return "The remote host could be reached, but reported that the requested port is not open. Check whether a CoAP server is running at the address, or whether it is running on a different port."
262 if self.__cause__.errno == errno.EHOSTUNREACH:
263 # seen trying to reach any unused local address
264 return "No way of contacting the remote host could be found. This could be because a host on the local network is offline or firewalled. Tools for debugging in the next step could be ping or traceroute."
265 if self.__cause__.errno == errno.ENETUNREACH:
266 # seen trying to reach an IPv6 host through an IP literal from a v4-only system, or trying to reach 2001:db8::1
267 return "No way of contacting the remote network could be found. This may be due to lack of IPv6 connectivity, lack of a concrete route (eg. trying to reach a private use network which there is no route to). Tools for debugging in the next step could be ping or traceroute."
268 if self.__cause__.errno == errno.EACCES:
269 # seen trying to reach the broadcast address of a local network
270 return "The operating system refused to send the request. For example, this can occur when attempting to send broadcast requests instead of multicast requests."
273class NoRequestInterface(RuntimeError, ConstructionRenderableError, NetworkError):
274 code = codes.PROXYING_NOT_SUPPORTED
275 message = "Error: No CoAP transport available for this scheme on any request interface." # or address, but we don't take transport-indication in full yet
277 def extra_help(self, hints={}):
278 return "More transports may be enabled by installing extra optional dependencies. Alternatively, consider using a CoAP-CoAP proxy."
281class ResolutionError(NetworkError):
282 """Resolving the host component of a URI to a usable transport address was
283 not possible"""
285 def __str__(self):
286 return f"Name resolution error: {self.args[0]}"
289class MessageError(NetworkError):
290 """Received an error from the remote on the CoAP message level (typically a
291 RST)"""
294class RemoteServerShutdown(NetworkError):
295 """The peer a request was sent to in a stateful connection closed the
296 connection around the time the request was sent"""
299class TimeoutError(NetworkError):
300 """Base for all timeout-ish errors.
302 Like NetworkError, receiving this alone does not indicate whether the
303 request may have reached the server or not.
304 """
306 def extra_help(self, hints={}):
307 return "Neither a response nor an error was received. This can have a wide range of causes, from the address being wrong to the server being stuck."
310class ConRetransmitsExceeded(TimeoutError):
311 """A transport that retransmits CON messages has failed to obtain a response
312 within its retransmission timeout.
314 When this is raised in a transport, requests failing with it may or may
315 have been received by the server.
316 """
319class RequestTimedOut(TimeoutError):
320 """
321 Raised when request is timed out.
323 This error is currently not produced by aiocoap; it is deprecated. Users
324 can now catch error.TimeoutError, or newer more detailed subtypes
325 introduced later.
326 """
329class WaitingForClientTimedOut(TimeoutError):
330 """
331 Raised when server expects some client action:
333 - sending next PUT/POST request with block1 or block2 option
334 - sending next GET request with block2 option
336 but client does nothing.
338 This error is currently not produced by aiocoap; it is deprecated. Users
339 can now catch error.TimeoutError, or newer more detailed subtypes
340 introduced later.
341 """
344class ResourceChanged(Error):
345 """
346 The requested resource was modified during the request and could therefore
347 not be received in a consistent state.
348 """
351class UnexpectedBlock1Option(Error):
352 """
353 Raised when a server responds with block1 options that just don't match.
354 """
357class UnexpectedBlock2(Error):
358 """
359 Raised when a server responds with another block2 than expected.
360 """
363class MissingBlock2Option(Error):
364 """
365 Raised when response with Block2 option is expected
366 (previous response had Block2 option with More flag set),
367 but response without Block2 option is received.
368 """
371class NotObservable(Error):
372 """
373 The server did not accept the request to observe the resource.
374 """
377class ObservationCancelled(Error):
378 """
379 The server claimed that it will no longer sustain the observation.
380 """
383class UnparsableMessage(Error):
384 """
385 An incoming message does not look like CoAP.
387 Note that this happens rarely -- the requirements are just two bit at the
388 beginning of the message, and a minimum length.
389 """
392class LibraryShutdown(Error):
393 """The library or a transport registered with it was requested to shut
394 down; this error is raised in all outstanding requests."""
397class AnonymousHost(Error):
398 """This is raised when it is attempted to express as a reference a (base)
399 URI of a host or a resource that can not be reached by any process other
400 than this.
402 Typically, this happens when trying to serialize a link to a resource that
403 is hosted on a CoAP-over-TCP or -WebSockets client: Such resources can be
404 accessed for as long as the connection is active, but can not be used any
405 more once it is closed or even by another system."""
408class MalformedUrlError(ValueError, HelpfulError):
409 def __str__(self):
410 if self.args:
411 return f"Malformed URL: {self.args[0]}"
412 else:
413 return f"Malformed URL: {self.__cause__}"
416class IncompleteUrlError(ValueError, HelpfulError):
417 def __str__(self):
418 return "URL incomplete: Must start with a scheme."
420 def extra_help(self, hints={}):
421 return "Most URLs in aiocoap need to be given with a scheme, eg. the 'coap' in 'coap://example.com/path'."
424class MissingRemoteError(HelpfulError):
425 """A request is sent without a .remote attribute"""
427 def __str__(self):
428 return "Error: No remote endpoint set for request."
430 def extra_help(self, hints={}):
431 original_uri = hints.get("original_uri", None)
432 requested_message = hints.get("request", None)
433 if requested_message and (
434 requested_message.opt.proxy_uri or requested_message.opt.proxy_scheme
435 ):
436 if original_uri:
437 return f"The message is set up for use with a proxy (because the scheme of {original_uri!r} is not supported), but no proxy was set."
438 else:
439 return (
440 "The message is set up for use with a proxy, but no proxy was set."
441 )
442 # Nothing helpful otherwise: The message was probably constructed in a
443 # rather manual way.
446__getattr__ = util.deprecation_getattr(
447 {
448 "UnsupportedMediaType": "UnsupportedContentFormat",
449 "RequestTimedOut": "TimeoutError",
450 "WaitingForClientTimedOut": "TimeoutError",
451 },
452 globals(),
453)