21
21
22
22
"""
23
23
24
- # imports
25
-
26
24
__version__ = "0.0.0+auto.0"
27
25
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ConnectionManager.git"
28
26
31
29
32
30
WIZNET5K_SSL_SUPPORT_VERSION = (9 , 1 )
33
31
34
- # typing
35
-
36
-
37
32
if not sys .implementation .name == "circuitpython" :
38
33
from typing import List , Optional , Tuple
39
34
46
41
)
47
42
48
43
49
- # ssl and pool helpers
50
-
51
-
52
44
class _FakeSSLSocket :
53
45
def __init__ (self , socket : CircuitPythonSocketType , tls_mode : int ) -> None :
54
46
self ._socket = socket
@@ -82,7 +74,7 @@ def wrap_socket( # pylint: disable=unused-argument
82
74
if hasattr (self ._iface , "TLS_MODE" ):
83
75
return _FakeSSLSocket (socket , self ._iface .TLS_MODE )
84
76
85
- raise AttributeError ("This radio does not support TLS/HTTPS" )
77
+ raise ValueError ("This radio does not support TLS/HTTPS" )
86
78
87
79
88
80
def create_fake_ssl_context (
@@ -167,7 +159,7 @@ def get_radio_socketpool(radio):
167
159
ssl_context = create_fake_ssl_context (pool , radio )
168
160
169
161
else :
170
- raise AttributeError (f"Unsupported radio class: { class_name } " )
162
+ raise ValueError (f"Unsupported radio class: { class_name } " )
171
163
172
164
_global_key_by_socketpool [pool ] = key
173
165
_global_socketpools [key ] = pool
@@ -189,11 +181,8 @@ def get_radio_ssl_context(radio):
189
181
return _global_ssl_contexts [_get_radio_hash_key (radio )]
190
182
191
183
192
- # main class
193
-
194
-
195
184
class ConnectionManager :
196
- """A library for managing sockets accross libraries."""
185
+ """A library for managing sockets across multiple hardware platforms and libraries."""
197
186
198
187
def __init__ (
199
188
self ,
@@ -215,6 +204,11 @@ def _free_sockets(self, force: bool = False) -> None:
215
204
for socket in open_sockets :
216
205
self .close_socket (socket )
217
206
207
+ def _register_connected_socket (self , key , socket ):
208
+ """Register a socket as managed."""
209
+ self ._key_by_managed_socket [socket ] = key
210
+ self ._managed_socket_by_key [key ] = socket
211
+
218
212
def _get_connected_socket ( # pylint: disable=too-many-arguments
219
213
self ,
220
214
addr_info : List [Tuple [int , int , int , str , Tuple [str , int ]]],
@@ -224,23 +218,24 @@ def _get_connected_socket( # pylint: disable=too-many-arguments
224
218
is_ssl : bool ,
225
219
ssl_context : Optional [SSLContextType ] = None ,
226
220
):
227
- try :
228
- socket = self ._socket_pool .socket (addr_info [0 ], addr_info [1 ])
229
- except (OSError , RuntimeError ) as exc :
230
- return exc
221
+
222
+ socket = self ._socket_pool .socket (addr_info [0 ], addr_info [1 ])
231
223
232
224
if is_ssl :
233
225
socket = ssl_context .wrap_socket (socket , server_hostname = host )
234
226
connect_host = host
235
227
else :
236
228
connect_host = addr_info [- 1 ][0 ]
237
- socket .settimeout (timeout ) # socket read timeout
229
+
230
+ # Set socket read and connect timeout.
231
+ socket .settimeout (timeout )
238
232
239
233
try :
240
234
socket .connect ((connect_host , port ))
241
- except (MemoryError , OSError ) as exc :
235
+ except (MemoryError , OSError ):
236
+ # If any connect problems, clean up and re-raise the problem exception.
242
237
socket .close ()
243
- return exc
238
+ raise
244
239
245
240
return socket
246
241
@@ -269,82 +264,78 @@ def close_socket(self, socket: SocketType) -> None:
269
264
self ._available_sockets .remove (socket )
270
265
271
266
def free_socket (self , socket : SocketType ) -> None :
272
- """Mark a managed socket as available so it can be reused."""
267
+ """Mark a managed socket as available so it can be reused. The socket is not closed. """
273
268
if socket not in self ._managed_socket_by_key .values ():
274
269
raise RuntimeError ("Socket not managed" )
275
270
self ._available_sockets .add (socket )
276
271
272
+ # pylint: disable=too-many-arguments
277
273
def get_socket (
278
274
self ,
279
275
host : str ,
280
276
port : int ,
281
277
proto : str ,
282
278
session_id : Optional [str ] = None ,
283
279
* ,
284
- timeout : float = 1 ,
280
+ timeout : float = 1.0 ,
285
281
is_ssl : bool = False ,
286
282
ssl_context : Optional [SSLContextType ] = None ,
287
283
) -> CircuitPythonSocketType :
288
284
"""
289
- Get a new socket and connect.
290
-
291
- - **host** *(str)* – The host you are want to connect to: "www.adaftuit.com"
292
- - **port** *(int)* – The port you want to connect to: 80
293
- - **proto** *(str)* – The protocal you want to use: "http:"
294
- - **session_id** *(Optional[str])* – A unique Session ID, when wanting to have multiple open
295
- connections to the same host
296
- - **timeout** *(float)* – Time timeout used for connecting
297
- - **is_ssl** *(bool)* – If the connection is to be over SSL (auto set when proto is
298
- "https:")
299
- - **ssl_context** *(Optional[SSLContextType])* – The SSL context to use when making SSL
300
- requests
285
+ Get a new socket and connect to the given host.
286
+
287
+ :param str host: host to connect to, such as ``"www.example.org"``
288
+ :param int port: port to use for connection, such as ``80`` or ``443``
289
+ :param str proto: connection protocol: ``"http:"``, ``"https:"``, etc.
290
+ :param Optional[str]: unique session ID,
291
+ used for multiple simultaneous connections to the same host
292
+ :param float timeout: how long to wait to connect
293
+ :param bool is_ssl: ``True`` If the connection is to be over SSL;
294
+ automatically set when ``proto`` is ``"https:"``
295
+ :param Optional[SSLContextType]: SSL context to use when making SSL requests
301
296
"""
302
297
if session_id :
303
298
session_id = str (session_id )
304
299
key = (host , port , proto , session_id )
300
+
301
+ # Do we have already have a socket available for the requested connection?
305
302
if key in self ._managed_socket_by_key :
306
303
socket = self ._managed_socket_by_key [key ]
307
304
if socket in self ._available_sockets :
308
305
self ._available_sockets .remove (socket )
309
306
return socket
310
307
311
- raise RuntimeError (f"Socket already connected to { proto } //{ host } :{ port } " )
308
+ raise RuntimeError (
309
+ f"An existing socket is already connected to { proto } //{ host } :{ port } "
310
+ )
312
311
313
312
if proto == "https:" :
314
313
is_ssl = True
315
314
if is_ssl and not ssl_context :
316
- raise AttributeError (
317
- "ssl_context must be set before using adafruit_requests for https"
318
- )
315
+ raise ValueError ("ssl_context must be provided if using ssl" )
319
316
320
317
addr_info = self ._socket_pool .getaddrinfo (
321
318
host , port , 0 , self ._socket_pool .SOCK_STREAM
322
319
)[0 ]
323
320
324
- first_exception = None
325
- result = self ._get_connected_socket (
326
- addr_info , host , port , timeout , is_ssl , ssl_context
327
- )
328
- if isinstance (result , Exception ):
329
- # Got an error, if there are any available sockets, free them and try again
321
+ try :
322
+ socket = self ._get_connected_socket (
323
+ addr_info , host , port , timeout , is_ssl , ssl_context
324
+ )
325
+ self ._register_connected_socket (key , socket )
326
+ return socket
327
+ except (MemoryError , OSError , RuntimeError ):
328
+ # Could not get a new socket (or two, if SSL).
329
+ # If there are any available sockets, free them all and try again.
330
330
if self .available_socket_count :
331
- first_exception = result
332
331
self ._free_sockets ()
333
- result = self ._get_connected_socket (
332
+ socket = self ._get_connected_socket (
334
333
addr_info , host , port , timeout , is_ssl , ssl_context
335
334
)
336
- if isinstance (result , Exception ):
337
- last_result = f", first error: { first_exception } " if first_exception else ""
338
- raise RuntimeError (
339
- f"Error connecting socket: { result } { last_result } "
340
- ) from result
341
-
342
- self ._key_by_managed_socket [result ] = key
343
- self ._managed_socket_by_key [key ] = result
344
- return result
345
-
346
-
347
- # global helpers
335
+ self ._register_connected_socket (key , socket )
336
+ return socket
337
+ # Re-raise exception if no sockets could be freed.
338
+ raise
348
339
349
340
350
341
def connection_manager_close_all (
@@ -353,10 +344,10 @@ def connection_manager_close_all(
353
344
"""
354
345
Close all open sockets for pool, optionally release references.
355
346
356
- - **socket_pool** *( Optional[SocketpoolModuleType])* – A specifc SocketPool you want to close
357
- sockets for, leave blank for all SocketPools
358
- - **release_references** *( bool)* – Set to True if you want to also clear stored references to
359
- the SocketPool and SSL contexts
347
+ :param Optional[SocketpoolModuleType] socket_pool:
348
+ a specific socket pool whose sockets you want to close; ``None`` means all socket pools
349
+ :param bool release_references: `` True`` if you also want the `ConnectionManager` to forget
350
+ all the socket pools and SSL contexts it knows about
360
351
"""
361
352
if socket_pool :
362
353
socket_pools = [socket_pool ]
@@ -383,10 +374,7 @@ def connection_manager_close_all(
383
374
384
375
def get_connection_manager (socket_pool : SocketpoolModuleType ) -> ConnectionManager :
385
376
"""
386
- Get the ConnectionManager singleton for the given pool.
387
-
388
- - **socket_pool** *(Optional[SocketpoolModuleType])* – The SocketPool you want the
389
- ConnectionManager for
377
+ Get or create the ConnectionManager singleton for the given pool.
390
378
"""
391
379
if socket_pool not in _global_connection_managers :
392
380
_global_connection_managers [socket_pool ] = ConnectionManager (socket_pool )
0 commit comments