aboutsummaryrefslogtreecommitdiffstats
path: root/hypervideo_dl/networking/exceptions.py
diff options
context:
space:
mode:
Diffstat (limited to 'hypervideo_dl/networking/exceptions.py')
-rw-r--r--hypervideo_dl/networking/exceptions.py217
1 files changed, 217 insertions, 0 deletions
diff --git a/hypervideo_dl/networking/exceptions.py b/hypervideo_dl/networking/exceptions.py
new file mode 100644
index 0000000..10afc9c
--- /dev/null
+++ b/hypervideo_dl/networking/exceptions.py
@@ -0,0 +1,217 @@
+from __future__ import annotations
+
+import typing
+import urllib.error
+
+from ..utils import YoutubeDLError, deprecation_warning
+
+if typing.TYPE_CHECKING:
+ from .common import RequestHandler, Response
+
+
+class RequestError(YoutubeDLError):
+ def __init__(
+ self,
+ msg: str | None = None,
+ cause: Exception | str | None = None,
+ handler: RequestHandler = None
+ ):
+ self.handler = handler
+ self.cause = cause
+ if not msg and cause:
+ msg = str(cause)
+ super().__init__(msg)
+
+
+class UnsupportedRequest(RequestError):
+ """raised when a handler cannot handle a request"""
+ pass
+
+
+class NoSupportingHandlers(RequestError):
+ """raised when no handlers can support a request for various reasons"""
+
+ def __init__(self, unsupported_errors: list[UnsupportedRequest], unexpected_errors: list[Exception]):
+ self.unsupported_errors = unsupported_errors or []
+ self.unexpected_errors = unexpected_errors or []
+
+ # Print a quick summary of the errors
+ err_handler_map = {}
+ for err in unsupported_errors:
+ err_handler_map.setdefault(err.msg, []).append(err.handler.RH_NAME)
+
+ reason_str = ', '.join([f'{msg} ({", ".join(handlers)})' for msg, handlers in err_handler_map.items()])
+ if unexpected_errors:
+ reason_str = ' + '.join(filter(None, [reason_str, f'{len(unexpected_errors)} unexpected error(s)']))
+
+ err_str = 'Unable to handle request'
+ if reason_str:
+ err_str += f': {reason_str}'
+
+ super().__init__(msg=err_str)
+
+
+class TransportError(RequestError):
+ """Network related errors"""
+
+
+class HTTPError(RequestError):
+ def __init__(self, response: Response, redirect_loop=False):
+ self.response = response
+ self.status = response.status
+ self.reason = response.reason
+ self.redirect_loop = redirect_loop
+ msg = f'HTTP Error {response.status}: {response.reason}'
+ if redirect_loop:
+ msg += ' (redirect loop detected)'
+
+ super().__init__(msg=msg)
+
+ def close(self):
+ self.response.close()
+
+ def __repr__(self):
+ return f'<HTTPError {self.status}: {self.reason}>'
+
+
+class IncompleteRead(TransportError):
+ def __init__(self, partial, expected=None, **kwargs):
+ self.partial = partial
+ self.expected = expected
+ msg = f'{len(partial)} bytes read'
+ if expected is not None:
+ msg += f', {expected} more expected'
+
+ super().__init__(msg=msg, **kwargs)
+
+ def __repr__(self):
+ return f'<IncompleteRead: {self.msg}>'
+
+
+class SSLError(TransportError):
+ pass
+
+
+class CertificateVerifyError(SSLError):
+ """Raised when certificate validated has failed"""
+ pass
+
+
+class ProxyError(TransportError):
+ pass
+
+
+class _CompatHTTPError(urllib.error.HTTPError, HTTPError):
+ """
+ Provides backwards compatibility with urllib.error.HTTPError.
+ Do not use this class directly, use HTTPError instead.
+ """
+
+ def __init__(self, http_error: HTTPError):
+ super().__init__(
+ url=http_error.response.url,
+ code=http_error.status,
+ msg=http_error.msg,
+ hdrs=http_error.response.headers,
+ fp=http_error.response
+ )
+ self._closer.file = None # Disable auto close
+ self._http_error = http_error
+ HTTPError.__init__(self, http_error.response, redirect_loop=http_error.redirect_loop)
+
+ @property
+ def status(self):
+ return self._http_error.status
+
+ @status.setter
+ def status(self, value):
+ return
+
+ @property
+ def reason(self):
+ return self._http_error.reason
+
+ @reason.setter
+ def reason(self, value):
+ return
+
+ @property
+ def headers(self):
+ deprecation_warning('HTTPError.headers is deprecated, use HTTPError.response.headers instead')
+ return self._http_error.response.headers
+
+ @headers.setter
+ def headers(self, value):
+ return
+
+ def info(self):
+ deprecation_warning('HTTPError.info() is deprecated, use HTTPError.response.headers instead')
+ return self.response.headers
+
+ def getcode(self):
+ deprecation_warning('HTTPError.getcode is deprecated, use HTTPError.status instead')
+ return self.status
+
+ def geturl(self):
+ deprecation_warning('HTTPError.geturl is deprecated, use HTTPError.response.url instead')
+ return self.response.url
+
+ @property
+ def code(self):
+ deprecation_warning('HTTPError.code is deprecated, use HTTPError.status instead')
+ return self.status
+
+ @code.setter
+ def code(self, value):
+ return
+
+ @property
+ def url(self):
+ deprecation_warning('HTTPError.url is deprecated, use HTTPError.response.url instead')
+ return self.response.url
+
+ @url.setter
+ def url(self, value):
+ return
+
+ @property
+ def hdrs(self):
+ deprecation_warning('HTTPError.hdrs is deprecated, use HTTPError.response.headers instead')
+ return self.response.headers
+
+ @hdrs.setter
+ def hdrs(self, value):
+ return
+
+ @property
+ def filename(self):
+ deprecation_warning('HTTPError.filename is deprecated, use HTTPError.response.url instead')
+ return self.response.url
+
+ @filename.setter
+ def filename(self, value):
+ return
+
+ def __getattr__(self, name):
+ # File operations are passed through the response.
+ # Warn for some commonly used ones
+ passthrough_warnings = {
+ 'read': 'response.read()',
+ # technically possibly due to passthrough, but we should discourage this
+ 'get_header': 'response.get_header()',
+ 'readable': 'response.readable()',
+ 'closed': 'response.closed',
+ 'tell': 'response.tell()',
+ }
+ if name in passthrough_warnings:
+ deprecation_warning(f'HTTPError.{name} is deprecated, use HTTPError.{passthrough_warnings[name]} instead')
+ return super().__getattr__(name)
+
+ def __str__(self):
+ return str(self._http_error)
+
+ def __repr__(self):
+ return repr(self._http_error)
+
+
+network_exceptions = (HTTPError, TransportError)