exceptions.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. from __future__ import annotations
  2. import typing
  3. import urllib.error
  4. from ..utils import YoutubeDLError, deprecation_warning
  5. if typing.TYPE_CHECKING:
  6. from .common import RequestHandler, Response
  7. class RequestError(YoutubeDLError):
  8. def __init__(
  9. self,
  10. msg: str | None = None,
  11. cause: Exception | str | None = None,
  12. handler: RequestHandler = None
  13. ):
  14. self.handler = handler
  15. self.cause = cause
  16. if not msg and cause:
  17. msg = str(cause)
  18. super().__init__(msg)
  19. class UnsupportedRequest(RequestError):
  20. """raised when a handler cannot handle a request"""
  21. pass
  22. class NoSupportingHandlers(RequestError):
  23. """raised when no handlers can support a request for various reasons"""
  24. def __init__(self, unsupported_errors: list[UnsupportedRequest], unexpected_errors: list[Exception]):
  25. self.unsupported_errors = unsupported_errors or []
  26. self.unexpected_errors = unexpected_errors or []
  27. # Print a quick summary of the errors
  28. err_handler_map = {}
  29. for err in unsupported_errors:
  30. err_handler_map.setdefault(err.msg, []).append(err.handler.RH_NAME)
  31. reason_str = ', '.join([f'{msg} ({", ".join(handlers)})' for msg, handlers in err_handler_map.items()])
  32. if unexpected_errors:
  33. reason_str = ' + '.join(filter(None, [reason_str, f'{len(unexpected_errors)} unexpected error(s)']))
  34. err_str = 'Unable to handle request'
  35. if reason_str:
  36. err_str += f': {reason_str}'
  37. super().__init__(msg=err_str)
  38. class TransportError(RequestError):
  39. """Network related errors"""
  40. class HTTPError(RequestError):
  41. def __init__(self, response: Response, redirect_loop=False):
  42. self.response = response
  43. self.status = response.status
  44. self.reason = response.reason
  45. self.redirect_loop = redirect_loop
  46. msg = f'HTTP Error {response.status}: {response.reason}'
  47. if redirect_loop:
  48. msg += ' (redirect loop detected)'
  49. super().__init__(msg=msg)
  50. def close(self):
  51. self.response.close()
  52. def __repr__(self):
  53. return f'<HTTPError {self.status}: {self.reason}>'
  54. class IncompleteRead(TransportError):
  55. def __init__(self, partial, expected=None, **kwargs):
  56. self.partial = partial
  57. self.expected = expected
  58. msg = f'{len(partial)} bytes read'
  59. if expected is not None:
  60. msg += f', {expected} more expected'
  61. super().__init__(msg=msg, **kwargs)
  62. def __repr__(self):
  63. return f'<IncompleteRead: {self.msg}>'
  64. class SSLError(TransportError):
  65. pass
  66. class CertificateVerifyError(SSLError):
  67. """Raised when certificate validated has failed"""
  68. pass
  69. class ProxyError(TransportError):
  70. pass
  71. class _CompatHTTPError(urllib.error.HTTPError, HTTPError):
  72. """
  73. Provides backwards compatibility with urllib.error.HTTPError.
  74. Do not use this class directly, use HTTPError instead.
  75. """
  76. def __init__(self, http_error: HTTPError):
  77. super().__init__(
  78. url=http_error.response.url,
  79. code=http_error.status,
  80. msg=http_error.msg,
  81. hdrs=http_error.response.headers,
  82. fp=http_error.response
  83. )
  84. self._closer.file = None # Disable auto close
  85. self._http_error = http_error
  86. HTTPError.__init__(self, http_error.response, redirect_loop=http_error.redirect_loop)
  87. @property
  88. def status(self):
  89. return self._http_error.status
  90. @status.setter
  91. def status(self, value):
  92. return
  93. @property
  94. def reason(self):
  95. return self._http_error.reason
  96. @reason.setter
  97. def reason(self, value):
  98. return
  99. @property
  100. def headers(self):
  101. deprecation_warning('HTTPError.headers is deprecated, use HTTPError.response.headers instead')
  102. return self._http_error.response.headers
  103. @headers.setter
  104. def headers(self, value):
  105. return
  106. def info(self):
  107. deprecation_warning('HTTPError.info() is deprecated, use HTTPError.response.headers instead')
  108. return self.response.headers
  109. def getcode(self):
  110. deprecation_warning('HTTPError.getcode is deprecated, use HTTPError.status instead')
  111. return self.status
  112. def geturl(self):
  113. deprecation_warning('HTTPError.geturl is deprecated, use HTTPError.response.url instead')
  114. return self.response.url
  115. @property
  116. def code(self):
  117. deprecation_warning('HTTPError.code is deprecated, use HTTPError.status instead')
  118. return self.status
  119. @code.setter
  120. def code(self, value):
  121. return
  122. @property
  123. def url(self):
  124. deprecation_warning('HTTPError.url is deprecated, use HTTPError.response.url instead')
  125. return self.response.url
  126. @url.setter
  127. def url(self, value):
  128. return
  129. @property
  130. def hdrs(self):
  131. deprecation_warning('HTTPError.hdrs is deprecated, use HTTPError.response.headers instead')
  132. return self.response.headers
  133. @hdrs.setter
  134. def hdrs(self, value):
  135. return
  136. @property
  137. def filename(self):
  138. deprecation_warning('HTTPError.filename is deprecated, use HTTPError.response.url instead')
  139. return self.response.url
  140. @filename.setter
  141. def filename(self, value):
  142. return
  143. def __getattr__(self, name):
  144. # File operations are passed through the response.
  145. # Warn for some commonly used ones
  146. passthrough_warnings = {
  147. 'read': 'response.read()',
  148. # technically possibly due to passthrough, but we should discourage this
  149. 'get_header': 'response.get_header()',
  150. 'readable': 'response.readable()',
  151. 'closed': 'response.closed',
  152. 'tell': 'response.tell()',
  153. }
  154. if name in passthrough_warnings:
  155. deprecation_warning(f'HTTPError.{name} is deprecated, use HTTPError.{passthrough_warnings[name]} instead')
  156. return super().__getattr__(name)
  157. def __str__(self):
  158. return str(self._http_error)
  159. def __repr__(self):
  160. return repr(self._http_error)
  161. network_exceptions = (HTTPError, TransportError)