From 6105d7ea13c8fc59f7d5f04791de9552f30f3169 Mon Sep 17 00:00:00 2001 From: themylogin Date: Mon, 23 Feb 2026 15:14:29 +0100 Subject: [PATCH 1/3] Fix AttributeError: 'ClientConnectorCertificateError' object has no attribute '_os_error'. ClientConnectorCertificateError is a subclass of ClientConnectorError and should have all of its attributes (LSP). However, when I try to access the os_error attribute, I get the mentioned exception. --- CHANGES/12136.bugfix.rst | 2 ++ CONTRIBUTORS.txt | 1 + aiohttp/client_exceptions.py | 10 +++++++++- tests/test_client_exceptions.py | 9 +++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 CHANGES/12136.bugfix.rst diff --git a/CHANGES/12136.bugfix.rst b/CHANGES/12136.bugfix.rst new file mode 100644 index 00000000000..14ad7edf326 --- /dev/null +++ b/CHANGES/12136.bugfix.rst @@ -0,0 +1,2 @@ +``ClientConnectorCertificateError.os_error`` no longer raises :exc:`AttributeError` +-- by :user:`themylogin`. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 7c5613648ca..487691f688f 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -392,6 +392,7 @@ Vladimir Kamarzin Vladimir Kozlovski Vladimir Rutsky Vladimir Shulyak +Vladimir Vinogradenko Vladimir Zakharov Vladyslav Bohaichuk Vladyslav Bondar diff --git a/aiohttp/client_exceptions.py b/aiohttp/client_exceptions.py index af83a42705e..8c5bf7c9a3e 100644 --- a/aiohttp/client_exceptions.py +++ b/aiohttp/client_exceptions.py @@ -342,10 +342,18 @@ class ClientConnectorSSLError(*ssl_error_bases): # type: ignore[misc] class ClientConnectorCertificateError(*cert_errors_bases): # type: ignore[misc] """Response certificate error.""" + _conn_key: ConnectionKey + def __init__( self, connection_key: ConnectionKey, certificate_error: Exception ) -> None: - self._conn_key = connection_key + if isinstance(certificate_error, cert_errors + (OSError,)): + # ssl.CertificateError has errno and strerror, so we should be fine + os_error = certificate_error + else: + os_error = OSError() + + super().__init__(connection_key, os_error) self._certificate_error = certificate_error self.args = (connection_key, certificate_error) diff --git a/tests/test_client_exceptions.py b/tests/test_client_exceptions.py index ed87b6d50f1..a7bf4db05ef 100644 --- a/tests/test_client_exceptions.py +++ b/tests/test_client_exceptions.py @@ -201,6 +201,15 @@ def test_str(self) -> None: " [Exception: ('Bad certificate',)]" ) + def test_oserror(self) -> None: + certificate_error = OSError(1, "Bad certificate") + err = client.ClientConnectorCertificateError( + connection_key=self.connection_key, certificate_error=certificate_error + ) + assert err.os_error == certificate_error + assert err.errno == 1 + assert err.strerror == "Bad certificate" + class TestServerDisconnectedError: def test_ctor(self) -> None: From fa9ed38a266426495e56572a4a7224a564781d53 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Thu, 26 Feb 2026 22:40:18 +0000 Subject: [PATCH 2/3] Add comment --- aiohttp/client_exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aiohttp/client_exceptions.py b/aiohttp/client_exceptions.py index 8c5bf7c9a3e..ff163b78229 100644 --- a/aiohttp/client_exceptions.py +++ b/aiohttp/client_exceptions.py @@ -345,6 +345,7 @@ class ClientConnectorCertificateError(*cert_errors_bases): # type: ignore[misc] _conn_key: ConnectionKey def __init__( + # TODO: If we require ssl in future, this can become ssl.CertificateError self, connection_key: ConnectionKey, certificate_error: Exception ) -> None: if isinstance(certificate_error, cert_errors + (OSError,)): From 481c13e046227c996d887151a94330187c5a174c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 22:40:57 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- aiohttp/client_exceptions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aiohttp/client_exceptions.py b/aiohttp/client_exceptions.py index ff163b78229..3b66c7fdcd2 100644 --- a/aiohttp/client_exceptions.py +++ b/aiohttp/client_exceptions.py @@ -346,7 +346,9 @@ class ClientConnectorCertificateError(*cert_errors_bases): # type: ignore[misc] def __init__( # TODO: If we require ssl in future, this can become ssl.CertificateError - self, connection_key: ConnectionKey, certificate_error: Exception + self, + connection_key: ConnectionKey, + certificate_error: Exception, ) -> None: if isinstance(certificate_error, cert_errors + (OSError,)): # ssl.CertificateError has errno and strerror, so we should be fine