2222implementations in :mod:`lewis.adapters`. It also contains :class:`AdapterCollection` which can
2323be used to store multiple adapters and manage them together.
2424"""
25+
2526import inspect
27+ import logging
2628import threading
2729from collections import namedtuple
30+ from types import TracebackType
31+ from typing import Any , Optional , Type
2832
33+ from lewis .core .devices import DeviceBase , InterfaceBase
2934from lewis .core .exceptions import LewisException
3035from lewis .core .logging import has_log
3136from lewis .core .utils import dict_strict_update
@@ -38,13 +43,18 @@ class NoLock:
3843 that device/interface access is synchronous.
3944 """
4045
41- def __enter__ (self ):
46+ def __enter__ (self ) -> None :
4247 raise RuntimeError (
4348 "The attempted action requires a proper threading.Lock-object, "
4449 "but none was available."
4550 )
4651
47- def __exit__ (self , exc_type , exc_val , exc_tb ):
52+ def __exit__ (
53+ self ,
54+ exctype : Optional [Type [BaseException ]],
55+ excinst : Optional [BaseException ],
56+ exctb : Optional [TracebackType ],
57+ ) -> None :
4858 pass
4959
5060
@@ -89,11 +99,11 @@ class Adapter:
8999
90100 default_options = {}
91101
92- def __init__ (self , options = None ):
102+ def __init__ (self , options : dict [ str , Any ] | None = None ) -> None :
93103 super (Adapter , self ).__init__ ()
94104 self ._interface = None
95105
96- self .device_lock = NoLock ()
106+ self .device_lock : threading . Lock | NoLock = NoLock ()
97107
98108 options = options or {}
99109 combined_options = dict (self .default_options )
@@ -111,14 +121,14 @@ def __init__(self, options=None):
111121 self ._options = options_type (** combined_options )
112122
113123 @property
114- def protocol (self ):
124+ def protocol (self ) -> str | None :
115125 if self .interface is None :
116126 return None
117127
118128 return self .interface .protocol
119129
120130 @property
121- def interface (self ):
131+ def interface (self ) -> InterfaceBase | None :
122132 """
123133 The device property contains the device-object exposed by the adapter.
124134
@@ -129,18 +139,18 @@ def interface(self):
129139 return self ._interface
130140
131141 @interface .setter
132- def interface (self , new_interface ) :
142+ def interface (self , new_interface : InterfaceBase | None ) -> None :
133143 self ._interface = new_interface
134144
135145 @property
136- def documentation (self ):
146+ def documentation (self ) -> str :
137147 """
138148 This property can be overridden in a sub-class to provide protocol documentation to users
139149 at runtime. By default it returns the indentation cleaned-up docstring of the class.
140150 """
141151 return inspect .getdoc (self ) or ""
142152
143- def start_server (self ):
153+ def start_server (self ) -> None :
144154 """
145155 This method must be re-implemented to start the infrastructure required for the
146156 protocol in question. These startup operations are not supposed to be carried out on
@@ -159,7 +169,7 @@ def start_server(self):
159169 "required for network communication."
160170 )
161171
162- def stop_server (self ):
172+ def stop_server (self ) -> None :
163173 """
164174 This method must be re-implemented to stop and tear down anything that has been setup
165175 in :meth:`start_server`. This method should close all connections to clients that have
@@ -176,7 +186,7 @@ def stop_server(self):
176186 )
177187
178188 @property
179- def is_running (self ):
189+ def is_running (self ) -> bool :
180190 """
181191 This property indicates whether the Adapter's server is running and listening. The result
182192 of calls to :meth:`start_server` and :meth:`stop_server` should be reflected as expected.
@@ -186,7 +196,7 @@ def is_running(self):
186196 "a server is currently running and listening for requests."
187197 )
188198
189- def handle (self , cycle_delay = 0.1 ):
199+ def handle (self , cycle_delay : float = 0.1 ) -> None :
190200 """
191201 This function is called on each cycle of a simulation. It should process requests that are
192202 made via the protocol that exposes the device. The time spent processing should be
@@ -222,18 +232,19 @@ class AdapterCollection:
222232 :param args: List of adapters to add to the container
223233 """
224234
225- def __init__ (self , * args ) :
235+ def __init__ (self , * args : Adapter ) -> None :
226236 self ._adapters = {}
227237
228238 self ._threads = {}
229239 self ._running = {}
230240 self ._lock = threading .Lock ()
241+ self .log : logging .Logger
231242
232243 for adapter in args :
233244 self .add_adapter (adapter )
234245
235246 @property
236- def device_lock (self ):
247+ def device_lock (self ) -> threading . Lock :
237248 """
238249 This lock is passed to each adapter when it's started. It's supposed to be used to ensure
239250 that the device is only accessed from one thread at a time, for example during network IO.
@@ -242,12 +253,12 @@ def device_lock(self):
242253 """
243254 return self ._lock
244255
245- def set_device (self , new_device ) :
256+ def set_device (self , new_device : DeviceBase ) -> None :
246257 """Bind the new device to all interfaces managed by the adapters in the collection."""
247258 for adapter in self ._adapters .values ():
248259 adapter .interface .device = new_device
249260
250- def add_adapter (self , adapter ) :
261+ def add_adapter (self , adapter : Adapter ) -> None :
251262 """
252263 Adds the supplied adapter to the container but raises a ``RuntimeError`` if there's
253264 already an adapter registered for the same protocol.
@@ -256,14 +267,12 @@ def add_adapter(self, adapter):
256267 """
257268 if adapter .protocol in self ._adapters :
258269 raise RuntimeError (
259- "Adapter for protocol '{}' is already registered." .format (
260- adapter .protocol
261- )
270+ "Adapter for protocol '{}' is already registered." .format (adapter .protocol )
262271 )
263272
264273 self ._adapters [adapter .protocol ] = adapter
265274
266- def remove_adapter (self , protocol ) :
275+ def remove_adapter (self , protocol : str ) -> None :
267276 """
268277 Tries to remove the adapter for the specified protocol, raises a ``RuntimeError`` if there
269278 is no adapter registered for that particular protocol.
@@ -272,37 +281,31 @@ def remove_adapter(self, protocol):
272281 """
273282 if protocol not in self ._adapters :
274283 raise RuntimeError (
275- "Can not remove adapter for protocol '{}', none registered." .format (
276- protocol
277- )
284+ "Can not remove adapter for protocol '{}', none registered." .format (protocol )
278285 )
279286
280287 del self ._adapters [protocol ]
281288
282289 @property
283- def protocols (self ):
290+ def protocols (self ) -> list [ str ] :
284291 """List of protocols for which adapters are registered."""
285292 return list (self ._adapters .keys ())
286293
287- def connect (self , * args ) :
294+ def connect (self , * args : str ) -> None :
288295 """
289296 This method starts an adapter for each specified protocol in a separate thread, if the
290297 adapter is not already running.
291298
292299 :param args: List of protocols for which to start adapters or empty for all.
293300 """
294- for adapter in self ._get_adapters (args ):
301+ for adapter in self ._get_adapters (list ( args ) ):
295302 self ._start_server (adapter )
296303
297- def _start_server (self , adapter ) :
304+ def _start_server (self , adapter : Adapter ) -> None :
298305 if adapter .protocol not in self ._threads :
299- self .log .info (
300- "Connecting device interface for protocol '%s'" , adapter .protocol
301- )
306+ self .log .info ("Connecting device interface for protocol '%s'" , adapter .protocol )
302307
303- adapter_thread = threading .Thread (
304- target = self ._adapter_loop , args = (adapter , 0.01 )
305- )
308+ adapter_thread = threading .Thread (target = self ._adapter_loop , args = (adapter , 0.01 ))
306309 adapter_thread .daemon = True
307310
308311 self ._threads [adapter .protocol ] = adapter_thread
@@ -313,14 +316,10 @@ def _start_server(self, adapter):
313316 # Block until server is actually listening
314317 self ._running [adapter .protocol ].wait (2.0 )
315318 if not self ._running [adapter .protocol ].is_set ():
316- raise LewisException (
317- "Adapter for '%s' failed to start!" % adapter .protocol
318- )
319+ raise LewisException ("Adapter for '%s' failed to start!" % adapter .protocol )
319320
320- def _adapter_loop (self , adapter , dt ):
321- adapter .device_lock = (
322- self ._lock
323- ) # This ensures that the adapter is using the correct lock
321+ def _adapter_loop (self , adapter : Adapter , dt : float ) -> None :
322+ adapter .device_lock = self ._lock # This ensures that the adapter is using the correct lock
324323 adapter .start_server ()
325324
326325 self ._running [adapter .protocol ].set ()
@@ -331,29 +330,27 @@ def _adapter_loop(self, adapter, dt):
331330
332331 adapter .stop_server ()
333332
334- def disconnect (self , * args ) :
333+ def disconnect (self , * args : str ) -> None :
335334 """
336335 Stops all adapters for the specified protocols. The method waits for each adapter thread
337336 to join, so it might hang if the thread is not terminating correctly.
338337
339338 :param args: List of protocols for which to stop adapters or empty for all.
340339 """
341- for adapter in self ._get_adapters (args ):
340+ for adapter in self ._get_adapters (list ( args ) ):
342341 self ._stop_server (adapter )
343342
344- def _stop_server (self , adapter ) :
343+ def _stop_server (self , adapter : Adapter ) -> None :
345344 if adapter .protocol in self ._threads :
346- self .log .info (
347- "Disconnecting device interface for protocol '%s'" , adapter .protocol
348- )
345+ self .log .info ("Disconnecting device interface for protocol '%s'" , adapter .protocol )
349346
350347 self ._running [adapter .protocol ].clear ()
351348 self ._threads [adapter .protocol ].join ()
352349
353350 del self ._threads [adapter .protocol ]
354351 del self ._running [adapter .protocol ]
355352
356- def is_connected (self , * args ) :
353+ def is_connected (self , * args : str ) -> bool | dict [ str | None , bool ] :
357354 """
358355 If only one protocol is supplied, a single bool is returned with the connection status.
359356 Otherwise, this method returns a dictionary of adapter connection statuses for the supplied
@@ -363,15 +360,18 @@ def is_connected(self, *args):
363360 :return: Boolean for single adapter or dict of statuses for multiple.
364361 """
365362 status_dict = {
366- adapter .protocol : adapter .is_running for adapter in self ._get_adapters (args )
363+ adapter .protocol : adapter .is_running for adapter in self ._get_adapters (list ( args ) )
367364 }
368365
369366 if len (args ) == 1 :
370- return list (status_dict .values ())[0 ]
367+ status = list (status_dict .values ())[0 ]
368+ if status is None :
369+ return False
370+ return status
371371
372372 return status_dict
373373
374- def configuration (self , * args ) :
374+ def configuration (self , * args : str ) -> dict [ str | None , dict [ str , Any ]] :
375375 """
376376 Returns a dictionary that contains the options for the specified adapter. The dictionary
377377 keys are the adapter protocols.
@@ -381,22 +381,20 @@ def configuration(self, *args):
381381 """
382382 return {
383383 adapter .protocol : adapter ._options ._asdict ()
384- for adapter in self ._get_adapters (args )
384+ for adapter in self ._get_adapters (list ( args ) )
385385 }
386386
387- def documentation (self , * args ) :
387+ def documentation (self , * args : str ) -> str :
388388 """
389389 Returns the concatenated documentation for the adapters specified by the supplied
390390 protocols or all of them if no arguments are provided.
391391
392392 :param args: List of protocols for which to get documentation or empty for all.
393393 :return: Documentation for all selected adapters.
394394 """
395- return "\n \n " .join (
396- adapter .documentation for adapter in self ._get_adapters (args )
397- )
395+ return "\n \n " .join (adapter .documentation for adapter in self ._get_adapters (list (args )))
398396
399- def _get_adapters (self , protocols ) :
397+ def _get_adapters (self , protocols : list [ str ]) -> list [ Adapter ] :
400398 """
401399 Internal method to map protocols back to adapters. If the list of protocols contains an
402400 invalid entry (e.g. a protocol for which there is no adapter), a ``RuntimeError``
@@ -409,9 +407,7 @@ def _get_adapters(self, protocols):
409407
410408 if invalid_protocols :
411409 raise RuntimeError (
412- "No adapter registered for protocols: {}" .format (
413- ", " .join (invalid_protocols )
414- )
410+ "No adapter registered for protocols: {}" .format (", " .join (invalid_protocols ))
415411 )
416412
417413 return [self ._adapters [proto ] for proto in protocols or self .protocols ]
0 commit comments