From 99f8972dcec55a8a4ecb2db70aa58e5250b92910 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Fri, 10 May 2024 16:47:37 -0400 Subject: [PATCH 1/8] Recover in more cases when a socket cannot be created. Also: - Clarify some documentation. Use sphinx argument documentation style. - Fix some typos. - Remove a few internal comments marking code sections. - Clarify an error message. - Internally, catch exceptions instead of passing them back. - Change one exception. - Update to pylint 3.1.0 so pre-commit can run under Python 3.12 --- .pre-commit-config.yaml | 2 +- adafruit_connection_manager.py | 115 +++++++++++++++------------------ 2 files changed, 52 insertions(+), 65 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 77ed663..4d2e392 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/pycqa/pylint - rev: v2.17.4 + rev: v3.1.0 hooks: - id: pylint name: pylint (library code) diff --git a/adafruit_connection_manager.py b/adafruit_connection_manager.py index 5b8a10c..27f8a7b 100644 --- a/adafruit_connection_manager.py +++ b/adafruit_connection_manager.py @@ -21,8 +21,6 @@ """ -# imports - __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ConnectionManager.git" @@ -31,9 +29,6 @@ WIZNET5K_SSL_SUPPORT_VERSION = (9, 1) -# typing - - if not sys.implementation.name == "circuitpython": from typing import List, Optional, Tuple @@ -46,9 +41,6 @@ ) -# ssl and pool helpers - - class _FakeSSLSocket: def __init__(self, socket: CircuitPythonSocketType, tls_mode: int) -> None: self._socket = socket @@ -189,11 +181,8 @@ def get_radio_ssl_context(radio): return _global_ssl_contexts[_get_radio_hash_key(radio)] -# main class - - class ConnectionManager: - """A library for managing sockets accross libraries.""" + """A library for managing sockets across multiple hardware platforms and libraries.""" def __init__( self, @@ -224,23 +213,24 @@ def _get_connected_socket( # pylint: disable=too-many-arguments is_ssl: bool, ssl_context: Optional[SSLContextType] = None, ): - try: - socket = self._socket_pool.socket(addr_info[0], addr_info[1]) - except (OSError, RuntimeError) as exc: - return exc + + socket = self._socket_pool.socket(addr_info[0], addr_info[1]) if is_ssl: socket = ssl_context.wrap_socket(socket, server_hostname=host) connect_host = host else: connect_host = addr_info[-1][0] - socket.settimeout(timeout) # socket read timeout + + # Set socket read and connect timeout. + socket.settimeout(timeout) try: socket.connect((connect_host, port)) - except (MemoryError, OSError) as exc: + except (MemoryError, OSError): + # If any connect problems, clean up and re-raise the problem exception. socket.close() - return exc + raise return socket @@ -269,11 +259,16 @@ def close_socket(self, socket: SocketType) -> None: self._available_sockets.remove(socket) def free_socket(self, socket: SocketType) -> None: - """Mark a managed socket as available so it can be reused.""" + """Mark a managed socket as available so it can be reused. The socket is not closed.""" if socket not in self._managed_socket_by_key.values(): raise RuntimeError("Socket not managed") self._available_sockets.add(socket) + def _register_connected_socket(self, key, socket): + self._key_by_managed_socket[socket] = key + self._managed_socket_by_key[key] = socket + + # pylint: disable=too-many-arguments def get_socket( self, host: str, @@ -281,70 +276,65 @@ def get_socket( proto: str, session_id: Optional[str] = None, *, - timeout: float = 1, + timeout: float = 1.0, is_ssl: bool = False, ssl_context: Optional[SSLContextType] = None, ) -> CircuitPythonSocketType: """ - Get a new socket and connect. - - - **host** *(str)* – The host you are want to connect to: "www.adaftuit.com" - - **port** *(int)* – The port you want to connect to: 80 - - **proto** *(str)* – The protocal you want to use: "http:" - - **session_id** *(Optional[str])* – A unique Session ID, when wanting to have multiple open - connections to the same host - - **timeout** *(float)* – Time timeout used for connecting - - **is_ssl** *(bool)* – If the connection is to be over SSL (auto set when proto is - "https:") - - **ssl_context** *(Optional[SSLContextType])* – The SSL context to use when making SSL - requests + Get a new socket and connect to the given host. + + :param str host: host to connect to, such as ``"www.example.org"`` + :param int port: port to use for connection, such as ``80`` or ``443`` + :param str proto: connection protocol: ``"http:"``, ``"https:"``, etc. + :param Optional[str]: unique session ID, + used for multiple simultaneous connections to the same host + :param float timeout: how long to wait to connect + :param bool is_ssl: ``True`` If the connection is to be over SSL; + automatically set when ``proto`` is ``"https:"` + :param Optional[SSLContextType]: SSL context to use when making SSL requests """ if session_id: session_id = str(session_id) key = (host, port, proto, session_id) + + # Do we have already have a socket available for the requested connection? if key in self._managed_socket_by_key: socket = self._managed_socket_by_key[key] if socket in self._available_sockets: self._available_sockets.remove(socket) return socket - raise RuntimeError(f"Socket already connected to {proto}//{host}:{port}") + raise RuntimeError( + f"An existing socket is already connected to {proto}//{host}:{port}" + ) if proto == "https:": is_ssl = True if is_ssl and not ssl_context: - raise AttributeError( - "ssl_context must be set before using adafruit_requests for https" - ) + raise ValueError("ssl_context must be provided if using ssl") addr_info = self._socket_pool.getaddrinfo( host, port, 0, self._socket_pool.SOCK_STREAM )[0] - first_exception = None - result = self._get_connected_socket( - addr_info, host, port, timeout, is_ssl, ssl_context - ) - if isinstance(result, Exception): - # Got an error, if there are any available sockets, free them and try again + try: + socket = self._get_connected_socket( + addr_info, host, port, timeout, is_ssl, ssl_context + ) + self._register_connected_socket(key, socket) + return socket + except (MemoryError, OSError, RuntimeError): + # Could not get a new socket (or two, if SSL). + # If there are any available sockets, free them all and try again. if self.available_socket_count: - first_exception = result self._free_sockets() - result = self._get_connected_socket( + socket = self._get_connected_socket( addr_info, host, port, timeout, is_ssl, ssl_context ) - if isinstance(result, Exception): - last_result = f", first error: {first_exception}" if first_exception else "" - raise RuntimeError( - f"Error connecting socket: {result}{last_result}" - ) from result - - self._key_by_managed_socket[result] = key - self._managed_socket_by_key[key] = result - return result - - -# global helpers + self._register_connected_socket(key, socket) + return socket + # Re-raise exception if no sockets could be freed. + raise def connection_manager_close_all( @@ -353,9 +343,9 @@ def connection_manager_close_all( """ Close all open sockets for pool, optionally release references. - - **socket_pool** *(Optional[SocketpoolModuleType])* – A specifc SocketPool you want to close - sockets for, leave blank for all SocketPools - - **release_references** *(bool)* – Set to True if you want to also clear stored references to + :param Optional[SocketpoolModuleType] socket_pool: + a specific `SocketPool` whose sockets you want to close; `None`` means all `SocketPool`s + :param bool release_references: ``True`` if you want to also clear stored references to the SocketPool and SSL contexts """ if socket_pool: @@ -383,10 +373,7 @@ def connection_manager_close_all( def get_connection_manager(socket_pool: SocketpoolModuleType) -> ConnectionManager: """ - Get the ConnectionManager singleton for the given pool. - - - **socket_pool** *(Optional[SocketpoolModuleType])* – The SocketPool you want the - ConnectionManager for + Get or create the ConnectionManager singleton for the given pool. """ if socket_pool not in _global_connection_managers: _global_connection_managers[socket_pool] = ConnectionManager(socket_pool) From 917edc8bdd4962e9053a55c902d0d7c9ab545c50 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Fri, 10 May 2024 17:09:43 -0400 Subject: [PATCH 2/8] use ValueError instead of AttributeError for incorrect args --- adafruit_connection_manager.py | 4 ++-- tests/get_radio_test.py | 4 ++-- tests/protocol_test.py | 4 ++-- tests/ssl_context_test.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/adafruit_connection_manager.py b/adafruit_connection_manager.py index 27f8a7b..b195d19 100644 --- a/adafruit_connection_manager.py +++ b/adafruit_connection_manager.py @@ -74,7 +74,7 @@ def wrap_socket( # pylint: disable=unused-argument if hasattr(self._iface, "TLS_MODE"): return _FakeSSLSocket(socket, self._iface.TLS_MODE) - raise AttributeError("This radio does not support TLS/HTTPS") + raise ValueError("This radio does not support TLS/HTTPS") def create_fake_ssl_context( @@ -159,7 +159,7 @@ def get_radio_socketpool(radio): ssl_context = create_fake_ssl_context(pool, radio) else: - raise AttributeError(f"Unsupported radio class: {class_name}") + raise ValueError(f"Unsupported radio class: {class_name}") _global_key_by_socketpool[pool] = key _global_socketpools[key] = pool diff --git a/tests/get_radio_test.py b/tests/get_radio_test.py index 9844e9e..5631bdb 100644 --- a/tests/get_radio_test.py +++ b/tests/get_radio_test.py @@ -55,7 +55,7 @@ def test_get_radio_socketpool_wiznet5k( # pylint: disable=unused-argument def test_get_radio_socketpool_unsupported(): radio = mocket.MockRadio.Unsupported() - with pytest.raises(AttributeError) as context: + with pytest.raises(ValueError) as context: adafruit_connection_manager.get_radio_socketpool(radio) assert "Unsupported radio class" in str(context) @@ -100,7 +100,7 @@ def test_get_radio_ssl_context_wiznet5k( # pylint: disable=unused-argument def test_get_radio_ssl_context_unsupported(): radio = mocket.MockRadio.Unsupported() - with pytest.raises(AttributeError) as context: + with pytest.raises(ValueError) as context: adafruit_connection_manager.get_radio_ssl_context(radio) assert "Unsupported radio class" in str(context) diff --git a/tests/protocol_test.py b/tests/protocol_test.py index 98b5296..50a071c 100644 --- a/tests/protocol_test.py +++ b/tests/protocol_test.py @@ -18,9 +18,9 @@ def test_get_https_no_ssl(): connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool) # verify not sending in a SSL context for a HTTPS call errors - with pytest.raises(AttributeError) as context: + with pytest.raises(ValueError) as context: connection_manager.get_socket(mocket.MOCK_HOST_1, 443, "https:") - assert "ssl_context must be set" in str(context) + assert "ssl_context must be provided if using ssl" in str(context) def test_connect_https(): diff --git a/tests/ssl_context_test.py b/tests/ssl_context_test.py index 2f2e370..02bf96e 100644 --- a/tests/ssl_context_test.py +++ b/tests/ssl_context_test.py @@ -58,7 +58,7 @@ def test_connect_wiznet5k_https_not_supported( # pylint: disable=unused-argumen connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool) # verify a HTTPS call for a board without built in WiFi and SSL support errors - with pytest.raises(AttributeError) as context: + with pytest.raises(ValueError) as context: connection_manager.get_socket( mocket.MOCK_HOST_1, 443, "https:", ssl_context=ssl_context ) From da3cd5fa8a9097be5b8c0f7cb293b6c58b7b082f Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Fri, 10 May 2024 21:41:40 -0400 Subject: [PATCH 3/8] redo test outputs --- adafruit_connection_manager.py | 17 +++++++++-------- tests/get_socket_test.py | 20 +++++++------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/adafruit_connection_manager.py b/adafruit_connection_manager.py index b195d19..658f338 100644 --- a/adafruit_connection_manager.py +++ b/adafruit_connection_manager.py @@ -204,6 +204,11 @@ def _free_sockets(self, force: bool = False) -> None: for socket in open_sockets: self.close_socket(socket) + def _register_connected_socket(self, key, socket): + """Register a socket as managed.""" + self._key_by_managed_socket[socket] = key + self._managed_socket_by_key[key] = socket + def _get_connected_socket( # pylint: disable=too-many-arguments self, addr_info: List[Tuple[int, int, int, str, Tuple[str, int]]], @@ -264,10 +269,6 @@ def free_socket(self, socket: SocketType) -> None: raise RuntimeError("Socket not managed") self._available_sockets.add(socket) - def _register_connected_socket(self, key, socket): - self._key_by_managed_socket[socket] = key - self._managed_socket_by_key[key] = socket - # pylint: disable=too-many-arguments def get_socket( self, @@ -290,7 +291,7 @@ def get_socket( used for multiple simultaneous connections to the same host :param float timeout: how long to wait to connect :param bool is_ssl: ``True`` If the connection is to be over SSL; - automatically set when ``proto`` is ``"https:"` + automatically set when ``proto`` is ``"https:"`` :param Optional[SSLContextType]: SSL context to use when making SSL requests """ if session_id: @@ -344,9 +345,9 @@ def connection_manager_close_all( Close all open sockets for pool, optionally release references. :param Optional[SocketpoolModuleType] socket_pool: - a specific `SocketPool` whose sockets you want to close; `None`` means all `SocketPool`s - :param bool release_references: ``True`` if you want to also clear stored references to - the SocketPool and SSL contexts + a specific socket pool whose sockets you want to close; ``None`` means all socket pools + :param bool release_references: ``True`` if you also want the `ConnectionManager` to forget + all the socket pools and SSL contexts it knows about """ if socket_pool: socket_pools = [socket_pool] diff --git a/tests/get_socket_test.py b/tests/get_socket_test.py index 9abbf98..46d053b 100644 --- a/tests/get_socket_test.py +++ b/tests/get_socket_test.py @@ -91,7 +91,7 @@ def test_get_socket_not_flagged_free(): # get a socket for the same host, should be a different one with pytest.raises(RuntimeError) as context: socket = connection_manager.get_socket(mocket.MOCK_HOST_1, 80, "http:") - assert "Socket already connected" in str(context) + assert "An existing socket is already connected" in str(context) def test_get_socket_os_error(): @@ -105,9 +105,8 @@ def test_get_socket_os_error(): connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool) # try to get a socket that returns a OSError - with pytest.raises(RuntimeError) as context: + with pytest.raises(OSError): connection_manager.get_socket(mocket.MOCK_HOST_1, 80, "http:") - assert "Error connecting socket: OSError" in str(context) def test_get_socket_runtime_error(): @@ -121,9 +120,8 @@ def test_get_socket_runtime_error(): connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool) # try to get a socket that returns a RuntimeError - with pytest.raises(RuntimeError) as context: + with pytest.raises(RuntimeError): connection_manager.get_socket(mocket.MOCK_HOST_1, 80, "http:") - assert "Error connecting socket: RuntimeError" in str(context) def test_get_socket_connect_memory_error(): @@ -139,9 +137,8 @@ def test_get_socket_connect_memory_error(): connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool) # try to connect a socket that returns a MemoryError - with pytest.raises(RuntimeError) as context: + with pytest.raises(MemoryError): connection_manager.get_socket(mocket.MOCK_HOST_1, 80, "http:") - assert "Error connecting socket: MemoryError" in str(context) def test_get_socket_connect_os_error(): @@ -157,9 +154,8 @@ def test_get_socket_connect_os_error(): connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool) # try to connect a socket that returns a OSError - with pytest.raises(RuntimeError) as context: + with pytest.raises(OSError): connection_manager.get_socket(mocket.MOCK_HOST_1, 80, "http:") - assert "Error connecting socket: OSError" in str(context) def test_get_socket_runtime_error_ties_again_at_least_one_free(): @@ -211,9 +207,8 @@ def test_get_socket_runtime_error_ties_again_only_once(): free_sockets_mock.assert_not_called() # try to get a socket that returns a RuntimeError twice - with pytest.raises(RuntimeError) as context: + with pytest.raises(RuntimeError): connection_manager.get_socket(mocket.MOCK_HOST_2, 80, "http:") - assert "Error connecting socket: error 2, first error: error 1" in str(context) free_sockets_mock.assert_called_once() @@ -248,8 +243,7 @@ def test_fake_ssl_context_connect_error( # pylint: disable=unused-argument ssl_context = adafruit_connection_manager.get_radio_ssl_context(radio) connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool) - with pytest.raises(RuntimeError) as context: + with pytest.raises(OSError): connection_manager.get_socket( mocket.MOCK_HOST_1, 443, "https:", ssl_context=ssl_context ) - assert "Error connecting socket: [Errno 12] RuntimeError" in str(context) From 0792b271bdb4afdefbad002638e60417abb15e3a Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Mon, 13 May 2024 06:09:34 -0700 Subject: [PATCH 4/8] Update tests --- tests/get_socket_test.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/get_socket_test.py b/tests/get_socket_test.py index 46d053b..f61cb26 100644 --- a/tests/get_socket_test.py +++ b/tests/get_socket_test.py @@ -98,30 +98,32 @@ def test_get_socket_os_error(): mock_pool = mocket.MocketPool() mock_socket_1 = mocket.Mocket() mock_pool.socket.side_effect = [ - OSError("OSError"), + OSError("OSError 1"), mock_socket_1, ] connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool) # try to get a socket that returns a OSError - with pytest.raises(OSError): + with pytest.raises(OSError) as context: connection_manager.get_socket(mocket.MOCK_HOST_1, 80, "http:") + assert "OSError 1" in str(context) def test_get_socket_runtime_error(): mock_pool = mocket.MocketPool() mock_socket_1 = mocket.Mocket() mock_pool.socket.side_effect = [ - RuntimeError("RuntimeError"), + RuntimeError("RuntimeError 1"), mock_socket_1, ] connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool) # try to get a socket that returns a RuntimeError - with pytest.raises(RuntimeError): + with pytest.raises(RuntimeError) as context: connection_manager.get_socket(mocket.MOCK_HOST_1, 80, "http:") + assert "RuntimeError 1" in str(context) def test_get_socket_connect_memory_error(): @@ -132,13 +134,14 @@ def test_get_socket_connect_memory_error(): mock_socket_1, mock_socket_2, ] - mock_socket_1.connect.side_effect = MemoryError("MemoryError") + mock_socket_1.connect.side_effect = MemoryError("MemoryError 1") connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool) # try to connect a socket that returns a MemoryError - with pytest.raises(MemoryError): + with pytest.raises(MemoryError) as context: connection_manager.get_socket(mocket.MOCK_HOST_1, 80, "http:") + assert "MemoryError 1" in str(context) def test_get_socket_connect_os_error(): @@ -149,13 +152,14 @@ def test_get_socket_connect_os_error(): mock_socket_1, mock_socket_2, ] - mock_socket_1.connect.side_effect = OSError("OSError") + mock_socket_1.connect.side_effect = OSError("OSError 1") connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool) # try to connect a socket that returns a OSError - with pytest.raises(OSError): + with pytest.raises(OSError) as context: connection_manager.get_socket(mocket.MOCK_HOST_1, 80, "http:") + assert "OSError 1" in str(context) def test_get_socket_runtime_error_ties_again_at_least_one_free(): @@ -190,9 +194,9 @@ def test_get_socket_runtime_error_ties_again_only_once(): mock_socket_2 = mocket.Mocket() mock_pool.socket.side_effect = [ mock_socket_1, - RuntimeError("error 1"), - RuntimeError("error 2"), - RuntimeError("error 3"), + RuntimeError("RuntimeError 1"), + RuntimeError("RuntimeError 2"), + RuntimeError("RuntimeError 3"), mock_socket_2, ] @@ -207,8 +211,9 @@ def test_get_socket_runtime_error_ties_again_only_once(): free_sockets_mock.assert_not_called() # try to get a socket that returns a RuntimeError twice - with pytest.raises(RuntimeError): + with pytest.raises(RuntimeError) as context: connection_manager.get_socket(mocket.MOCK_HOST_2, 80, "http:") + assert "RuntimeError 2" in str(context) free_sockets_mock.assert_called_once() @@ -237,13 +242,15 @@ def test_fake_ssl_context_connect_error( # pylint: disable=unused-argument mock_pool = mocket.MocketPool() mock_socket_1 = mocket.Mocket() mock_pool.socket.return_value = mock_socket_1 - mock_socket_1.connect.side_effect = RuntimeError("RuntimeError") + mock_socket_1.connect.side_effect = RuntimeError("RuntimeError 1") radio = mocket.MockRadio.ESP_SPIcontrol() ssl_context = adafruit_connection_manager.get_radio_ssl_context(radio) connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool) - with pytest.raises(OSError): + with pytest.raises(OSError) as context: connection_manager.get_socket( mocket.MOCK_HOST_1, 443, "https:", ssl_context=ssl_context ) + assert "12" in str(context) + assert "RuntimeError 1" in str(context) From 0cccbe4022d2b168ecd142c334a20911c694dc32 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Mon, 13 May 2024 14:59:24 -0700 Subject: [PATCH 5/8] Support CPython --- adafruit_connection_manager.py | 10 ++++++++++ tests/get_radio_test.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/adafruit_connection_manager.py b/adafruit_connection_manager.py index 658f338..58dcb0c 100644 --- a/adafruit_connection_manager.py +++ b/adafruit_connection_manager.py @@ -97,6 +97,10 @@ def create_fake_ssl_context( return _FakeSSLContext(iface) +class CPythonNetwork: # pylint: disable=too-few-public-methods + """Radio object to use when using ConnectionManager in CPython.""" + + _global_connection_managers = {} _global_key_by_socketpool = {} _global_socketpools = {} @@ -158,6 +162,12 @@ def get_radio_socketpool(radio): if ssl_context is None: ssl_context = create_fake_ssl_context(pool, radio) + elif class_name == "CPythonNetwork": + import socket as pool # pylint: disable=import-outside-toplevel + import ssl # pylint: disable=import-outside-toplevel + + ssl_context = ssl.create_default_context() + else: raise ValueError(f"Unsupported radio class: {class_name}") diff --git a/tests/get_radio_test.py b/tests/get_radio_test.py index 5631bdb..022aecd 100644 --- a/tests/get_radio_test.py +++ b/tests/get_radio_test.py @@ -53,6 +53,13 @@ def test_get_radio_socketpool_wiznet5k( # pylint: disable=unused-argument assert socket_pool in adafruit_connection_manager._global_socketpools.values() +def test_get_radio_socketpool_cpython(): + radio = adafruit_connection_manager.CPythonNetwork() + socket_pool = adafruit_connection_manager.get_radio_socketpool(radio) + assert socket_pool.__name__ == "socket" + assert socket_pool in adafruit_connection_manager._global_socketpools.values() + + def test_get_radio_socketpool_unsupported(): radio = mocket.MockRadio.Unsupported() with pytest.raises(ValueError) as context: @@ -98,6 +105,13 @@ def test_get_radio_ssl_context_wiznet5k( # pylint: disable=unused-argument assert ssl_context in adafruit_connection_manager._global_ssl_contexts.values() +def test_get_radio_ssl_context_cpython(): + radio = adafruit_connection_manager.CPythonNetwork() + ssl_context = adafruit_connection_manager.get_radio_ssl_context(radio) + assert isinstance(ssl_context, ssl.SSLContext) + assert ssl_context in adafruit_connection_manager._global_ssl_contexts.values() + + def test_get_radio_ssl_context_unsupported(): radio = mocket.MockRadio.Unsupported() with pytest.raises(ValueError) as context: From 1247dd4b7635a251795d0ae8b52de31165dc1dc5 Mon Sep 17 00:00:00 2001 From: Justin Myers Date: Mon, 10 Jun 2024 16:31:04 -0700 Subject: [PATCH 6/8] Update WIZNet version check for SSL --- adafruit_connection_manager.py | 9 +++++++-- tests/ssl_context_test.py | 12 ++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/adafruit_connection_manager.py b/adafruit_connection_manager.py index 58dcb0c..67152d7 100644 --- a/adafruit_connection_manager.py +++ b/adafruit_connection_manager.py @@ -149,8 +149,13 @@ def get_radio_socketpool(radio): # versions of the Wiznet5k library or on boards withouut the ssl module # see https://docs.circuitpython.org/en/latest/shared-bindings/support_matrix.html ssl_context = None - cp_version = sys.implementation[1] - if pool.SOCK_STREAM == 1 and cp_version >= WIZNET5K_SSL_SUPPORT_VERSION: + implementation_name = sys.implementation.name + implementation_version = sys.implementation.version + if ( + pool.SOCK_STREAM == 1 + and implementation_name == "circuitpython" + and implementation_version >= WIZNET5K_SSL_SUPPORT_VERSION + ): try: import ssl # pylint: disable=import-outside-toplevel diff --git a/tests/ssl_context_test.py b/tests/ssl_context_test.py index 02bf96e..25e389e 100644 --- a/tests/ssl_context_test.py +++ b/tests/ssl_context_test.py @@ -5,6 +5,7 @@ """ SLL Context Tests """ import ssl +from collections import namedtuple from unittest import mock import mocket @@ -13,6 +14,8 @@ import adafruit_connection_manager from adafruit_connection_manager import WIZNET5K_SSL_SUPPORT_VERSION +SimpleNamespace = namedtuple("SimpleNamespace", "name version") + def test_connect_esp32spi_https( # pylint: disable=unused-argument adafruit_esp32spi_socketpool_module, @@ -53,7 +56,9 @@ def test_connect_wiznet5k_https_not_supported( # pylint: disable=unused-argumen mock_pool = mocket.MocketPool() radio = mocket.MockRadio.WIZNET5K() old_version = (WIZNET5K_SSL_SUPPORT_VERSION[0] - 1, 0, 0) - with mock.patch("sys.implementation", (None, old_version)): + with mock.patch( + "sys.implementation", SimpleNamespace("circuitpython", old_version) + ): ssl_context = adafruit_connection_manager.get_radio_ssl_context(radio) connection_manager = adafruit_connection_manager.ConnectionManager(mock_pool) @@ -69,6 +74,9 @@ def test_connect_wiznet5k_https_supported( # pylint: disable=unused-argument adafruit_wiznet5k_with_ssl_socketpool_module, ): radio = mocket.MockRadio.WIZNET5K() - with mock.patch("sys.implementation", (None, WIZNET5K_SSL_SUPPORT_VERSION)): + with mock.patch( + "sys.implementation", + SimpleNamespace("circuitpython", WIZNET5K_SSL_SUPPORT_VERSION), + ): ssl_context = adafruit_connection_manager.get_radio_ssl_context(radio) assert isinstance(ssl_context, ssl.SSLContext) From d79982a13879ba60f884c8286e2ca0962bdf5100 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 7 Oct 2024 09:24:05 -0500 Subject: [PATCH 7/8] remove deprecated get_html_theme_path() call Signed-off-by: foamyguy --- docs/conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index b184b10..000d273 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -115,7 +115,6 @@ import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" -html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, From 42073559468d0c8af9bb1fe5e06fccd4d1d9a845 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 14 Jan 2025 11:32:34 -0600 Subject: [PATCH 8/8] add sphinx configuration to rtd.yaml Signed-off-by: foamyguy --- .readthedocs.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index b79ec5b..fe4faae 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,6 +8,9 @@ # Required version: 2 +sphinx: + configuration: docs/conf.py + build: os: ubuntu-20.04 tools: