Skip to content

Commit

Permalink
Raise proper exceptions for most cases
Browse files Browse the repository at this point in the history
  • Loading branch information
perklet committed Aug 3, 2024
1 parent 72222d9 commit 0f4d2f6
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 8 deletions.
8 changes: 4 additions & 4 deletions curl_cffi/curl.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import warnings
from http.cookies import SimpleCookie
from pathlib import Path
from typing import Any, List, Optional, Tuple, Union
from typing import Any, List, Literal, Optional, Tuple, Union

import certifi

from ._wrapper import ffi, lib
from .const import CurlHttpVersion, CurlInfo, CurlOpt, CurlWsFlag
from .const import CurlECode, CurlHttpVersion, CurlInfo, CurlOpt, CurlWsFlag

DEFAULT_CACERT = certifi.where()
REASON_PHRASE_RE = re.compile(rb"HTTP/\d\.\d [0-9]{3} (.*)")
Expand All @@ -17,9 +17,9 @@
class CurlError(Exception):
"""Base exception for curl_cffi package"""

def __init__(self, msg, code: int = 0, *args, **kwargs):
def __init__(self, msg, code: Union[CurlECode, Literal[0]] = 0, *args, **kwargs):
super().__init__(msg, *args, **kwargs)
self.code = code
self.code: Union[CurlECode, Literal[0]] = code


CURLINFO_TEXT = 0
Expand Down
85 changes: 84 additions & 1 deletion curl_cffi/requests/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
# Apache 2.0 License
# Vendored from https://github.com/psf/requests/blob/main/src/requests/exceptions.py
# With our own addtions

import json
from typing import Literal, Union

from .. import CurlError
from ..const import CurlECode


# Note IOError is an alias of OSError in Python 3.x
class RequestException(CurlError, OSError):
"""Base exception for curl_cffi.requests package"""

def __init__(self, msg, code=0, response=None, *args, **kwargs):
def __init__(self, msg, code: Union[CurlECode, Literal[0]] = 0, response=None, *args, **kwargs):
super().__init__(msg, code, *args, **kwargs)
self.response = response

Expand All @@ -27,10 +30,12 @@ class ImpersonateError(RequestException):
"""The impersonate config was wrong or impersonate failed."""


# not used
class InvalidJSONError(RequestException):
"""A JSON error occurred."""


# not used
class JSONDecodeError(InvalidJSONError, json.JSONDecodeError):
"""Couldn't decode the text into json"""

Expand All @@ -43,6 +48,10 @@ class ConnectionError(RequestException):
"""A Connection error occurred."""


class DNSError(ConnectionError):
"""Could not resolve"""


class ProxyError(RequestException):
"""A proxy error occurred."""

Expand All @@ -51,21 +60,28 @@ class SSLError(ConnectionError):
"""An SSL error occurred."""


class CertificateVerifyError(SSLError):
"""Raised when certificate validated has failed"""


class Timeout(RequestException):
"""The request timed out."""


# not used
class ConnectTimeout(ConnectionError, Timeout):
"""The request timed out while trying to connect to the remote server.
Requests that produced this error are safe to retry.
"""


# not used
class ReadTimeout(Timeout):
"""The server did not send any data in the allotted amount of time."""


# not used
class URLRequired(RequestException):
"""A valid URL is required to make a request."""

Expand All @@ -74,6 +90,7 @@ class TooManyRedirects(RequestException):
"""Too many redirects."""


# not used
class MissingSchema(RequestException, ValueError):
"""The URL scheme (e.g. http or https) is missing."""

Expand All @@ -86,44 +103,110 @@ class InvalidURL(RequestException, ValueError):
"""The URL provided was somehow invalid."""


# not used
class InvalidHeader(RequestException, ValueError):
"""The header value provided was somehow invalid."""


# not used
class InvalidProxyURL(InvalidURL):
"""The proxy URL provided is invalid."""


# not used
class ChunkedEncodingError(RequestException):
"""The server declared chunked encoding but sent an invalid chunk."""


# not used
class ContentDecodingError(RequestException):
"""Failed to decode response content."""


# not used
class StreamConsumedError(RequestException, TypeError):
"""The content for this response was already consumed."""


# does not support
class RetryError(RequestException):
"""Custom retries logic failed"""


# not used
class UnrewindableBodyError(RequestException):
"""Requests encountered an error when trying to rewind a body."""


class InterfaceError(RequestException):
"""A specified outgoing interface could not be used."""


# Warnings


# not used
class RequestsWarning(Warning):
"""Base warning for Requests."""


# not used
class FileModeWarning(RequestsWarning, DeprecationWarning):
"""A file was opened in text mode, but Requests determined its binary length."""


# not used
class RequestsDependencyWarning(RequestsWarning):
"""An imported dependency doesn't match the expected version range."""


CODE2ERROR = {
0: RequestException,
CurlECode.UNSUPPORTED_PROTOCOL: InvalidSchema,
CurlECode.URL_MALFORMAT: InvalidURL,
CurlECode.COULDNT_RESOLVE_PROXY: ProxyError,
CurlECode.COULDNT_RESOLVE_HOST: DNSError,
CurlECode.COULDNT_CONNECT: ConnectionError,
CurlECode.WEIRD_SERVER_REPLY: ConnectionError,
CurlECode.REMOTE_ACCESS_DENIED: ConnectionError,
CurlECode.HTTP2: HTTPError,
CurlECode.HTTP_RETURNED_ERROR: HTTPError,
CurlECode.WRITE_ERROR: RequestException,
CurlECode.READ_ERROR: RequestException,
CurlECode.OUT_OF_MEMORY: RequestException,
CurlECode.OPERATION_TIMEDOUT: TimeoutError,
CurlECode.SSL_CONNECT_ERROR: SSLError,
CurlECode.INTERFACE_FAILED: InterfaceError,
CurlECode.TOO_MANY_REDIRECTS: TooManyRedirects,
CurlECode.UNKNOWN_OPTION: RequestException,
CurlECode.SETOPT_OPTION_SYNTAX: RequestException,
CurlECode.GOT_NOTHING: ConnectionError,
CurlECode.SSL_ENGINE_NOTFOUND: SSLError,
CurlECode.SSL_ENGINE_SETFAILED: SSLError,
CurlECode.SEND_ERROR: ConnectionError,
CurlECode.RECV_ERROR: ConnectionError,
CurlECode.SSL_CERTPROBLEM: SSLError,
CurlECode.SSL_CIPHER: SSLError,
CurlECode.PEER_FAILED_VERIFICATION: CertificateVerifyError,
CurlECode.BAD_CONTENT_ENCODING: HTTPError,
CurlECode.SSL_ENGINE_INITFAILED: SSLError,
CurlECode.SSL_CACERT_BADFILE: SSLError,
CurlECode.SSL_CRL_BADFILE: SSLError,
CurlECode.SSL_ISSUER_ERROR: SSLError,
CurlECode.SSL_PINNEDPUBKEYNOTMATCH: SSLError,
CurlECode.SSL_INVALIDCERTSTATUS: SSLError,
CurlECode.HTTP2_STREAM: HTTPError,
CurlECode.HTTP3: HTTPError,
CurlECode.QUIC_CONNECT_ERROR: ConnectionError,
CurlECode.PROXY: ProxyError,
CurlECode.SSL_CLIENTCERT: SSLError,
CurlECode.ECH_REQUIRED: SSLError,
}


# credits: https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/networking/_curlcffi.py#L241
# Unlicense
def code2error(code: Union[CurlECode, Literal[0]], msg: str):
if code == CurlECode.RECV_ERROR and "CONNECT" in msg:
return ProxyError
return CODE2ERROR.get(code, RequestException)
8 changes: 5 additions & 3 deletions curl_cffi/requests/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from .. import AsyncCurl, Curl, CurlError, CurlHttpVersion, CurlInfo, CurlOpt, CurlSslVersion
from ..curl import CURL_WRITEFUNC_ERROR, CurlMime
from .cookies import Cookies, CookieTypes, CurlMorsel
from .exceptions import ImpersonateError, RequestException, SessionClosed
from .exceptions import ImpersonateError, RequestException, SessionClosed, code2error
from .headers import Headers, HeaderTypes
from .impersonate import BrowserType # noqa: F401
from .impersonate import (
Expand Down Expand Up @@ -1026,7 +1026,8 @@ def cleanup(fut):
except CurlError as e:
rsp = self._parse_response(c, buffer, header_buffer, default_encoding)
rsp.request = req
raise RequestException(str(e), e.code, rsp) from e
error = code2error(e.code, str(e))
raise error(str(e), e.code, rsp) from e
else:
rsp = self._parse_response(c, buffer, header_buffer, default_encoding)
rsp.request = req
Expand Down Expand Up @@ -1306,7 +1307,8 @@ def cleanup(fut):
except CurlError as e:
rsp = self._parse_response(curl, buffer, header_buffer, default_encoding)
rsp.request = req
raise RequestException(str(e), e.code, rsp) from e
error = code2error(e.code, str(e))
raise error(str(e), e.code, rsp) from e
else:
rsp = self._parse_response(curl, buffer, header_buffer, default_encoding)
rsp.request = req
Expand Down

0 comments on commit 0f4d2f6

Please sign in to comment.