From 8b94e31cf60f89a0840ff2dd647c8ddfc78fbef8 Mon Sep 17 00:00:00 2001 From: Clint Lawrence Date: Tue, 4 Jun 2024 20:40:49 +1000 Subject: [PATCH] A bunch of random stuff... - Run black - Bump mypy version - Drop 3.7 and add 3.12 to CI test runs - Fix some 3.7 specific imports. --- .github/workflows/test.yml | 2 +- setup.cfg | 4 +- src/fixate/core/switching.py | 93 ++++++++++++++++++++++------------ src/fixate/drivers/__init__.py | 6 +-- test/core/test_switching.py | 24 ++++++--- tox.ini | 4 +- 6 files changed, 84 insertions(+), 49 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 45ba0c43..47f8cc17 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 diff --git a/setup.cfg b/setup.cfg index fe6d196d..3189289b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,7 +22,7 @@ package_dir = packages = find: include_package_data = True zip_safe = False -python_requires = ~=3.7 +python_requires = ~=3.8 install_requires = pyvisa @@ -35,8 +35,6 @@ install_requires = numpy PyDAQmx # for typing.protocol - typing_extensions ; python_version < "3.8" - importlib-metadata >= 1.0 ; python_version < "3.8" platformdirs [options.packages.find] diff --git a/src/fixate/core/switching.py b/src/fixate/core/switching.py index 58722f5c..3801c504 100644 --- a/src/fixate/core/switching.py +++ b/src/fixate/core/switching.py @@ -43,7 +43,8 @@ Collection, Dict, Any, - FrozenSet, Set + FrozenSet, + Set, ) from dataclasses import dataclass from functools import reduce @@ -84,7 +85,9 @@ def __or__(self, other: PinUpdate) -> PinUpdate: return PinUpdate( setup=self.setup | other.setup, final=self.final | other.final, - minimum_change_time=max(self.minimum_change_time, other.minimum_change_time) + minimum_change_time=max( + self.minimum_change_time, other.minimum_change_time + ), ) return NotImplemented @@ -155,7 +158,9 @@ def multiplex(self, signal_output: Signal, trigger_update: bool = True) -> None: """ if signal_output not in self._signal_map: name = self.__class__.__name__ - raise ValueError(f"Signal '{signal_output}' not valid for multiplexer '{name}'") + raise ValueError( + f"Signal '{signal_output}' not valid for multiplexer '{name}'" + ) setup, final = self._calculate_pins(self._state, signal_output) self._update_pins(PinUpdate(setup, final, self.clearing_time), trigger_update) @@ -177,13 +182,15 @@ def switch_through_all_signals(self) -> Generator[str, None, None]: self.multiplex(signal) yield f"{self.__class__.__name__}: {signal}" - def reset(self, trigger_update: bool=True) -> None: + def reset(self, trigger_update: bool = True) -> None: self.multiplex("", trigger_update) - + ########################################################################### # The following methods are potential candidates to override in a subclass - def _calculate_pins(self, old_signal: Signal, new_signal: Signal) -> tuple[PinSetState, PinSetState]: + def _calculate_pins( + self, old_signal: Signal, new_signal: Signal + ) -> tuple[PinSetState, PinSetState]: """ Calculate the pin sets for the two-step state change. @@ -226,7 +233,9 @@ def _map_signals(self) -> SignalMap: elif hasattr(self, "map_list"): return {sig: frozenset(pins) for sig, *pins in self.map_list} else: - raise ValueError("VirtualMux subclass must define either map_tree or map_list") + raise ValueError( + "VirtualMux subclass must define either map_tree or map_list" + ) def _map_tree(self, tree: TreeDef, pins: PinList, fixed_pins: PinSet) -> SignalMap: """recursively add nested signal lists to the signal map. @@ -393,17 +402,21 @@ class Mux(VirtualMux): bits_at_this_level = (len(tree) - 1).bit_length() pins_at_this_level = pins[:bits_at_this_level] - for signal_or_tree, pins_for_signal in zip(tree, generate_bit_sets(pins_at_this_level)): + for signal_or_tree, pins_for_signal in zip( + tree, generate_bit_sets(pins_at_this_level) + ): if signal_or_tree is None: continue if isinstance(signal_or_tree, Signal): signal_map[signal_or_tree] = frozenset(pins_for_signal) | fixed_pins else: - signal_map.update(self._map_tree( - tree=signal_or_tree, - pins=pins[bits_at_this_level:], - fixed_pins=frozenset(pins_for_signal) | fixed_pins, - )) + signal_map.update( + self._map_tree( + tree=signal_or_tree, + pins=pins[bits_at_this_level:], + fixed_pins=frozenset(pins_for_signal) | fixed_pins, + ) + ) return signal_map @@ -412,8 +425,7 @@ def __repr__(self) -> str: @staticmethod def _default_update_pins( - pin_updates: PinUpdate, - trigger_update: bool = True + pin_updates: PinUpdate, trigger_update: bool = True ) -> None: """ Output callback to effect a state change in the mux. @@ -442,7 +454,9 @@ class VirtualSwitch(VirtualMux): pin_name: Pin = "" map_tree = ("FALSE", "TRUE") - def multiplex(self, signal_output: Union[Signal, bool], trigger_update: bool = True) -> None: + def multiplex( + self, signal_output: Union[Signal, bool], trigger_update: bool = True + ) -> None: if signal_output is True: signal = "TRUE" elif signal_output is False: @@ -463,7 +477,9 @@ def __init__( class RelayMatrixMux(VirtualMux): clearing_time = 0.01 - def _calculate_pins(self, old_signal: Signal, new_signal: Signal) -> tuple[PinSetState, PinSetState]: + def _calculate_pins( + self, old_signal: Signal, new_signal: Signal + ) -> tuple[PinSetState, PinSetState]: """ Override of _calculate_pins to implement break-before-make switching. """ @@ -503,7 +519,9 @@ class PinValueAddressHandler(AddressHandler): def __init__(self) -> None: super().__init__() - self._pin_lookup = {pin: bit for pin, bit in zip(self.pin_list, bit_generator())} + self._pin_lookup = { + pin: bit for pin, bit in zip(self.pin_list, bit_generator()) + } def set_pins(self, pins: Collection[Pin]) -> None: value = sum(self._pin_lookup[pin] for pin in pins) @@ -524,6 +542,7 @@ class FTDIAddressHandler(PinValueAddressHandler): often. FT232 is used to bit-bang to shift register that are control the switching in a jig. """ + def _update_output(self, value: int) -> None: raise NotImplementedError @@ -532,23 +551,22 @@ class VirtualAddressMap: """ The supervisor loops through the attached virtual multiplexers each time a mux update is triggered. """ + def __init__(self, handlers: Sequence[AddressHandler]): # used to work out which pins get routed to which address handler self._handler_pin_sets: list[tuple[PinSet, AddressHandler]] = [] for handler in handlers: self._handler_pin_sets.append((frozenset(handler.pin_list), handler)) - self._all_pins = frozenset(itertools.chain.from_iterable(handler.pin_list for handler in handlers)) + self._all_pins = frozenset( + itertools.chain.from_iterable(handler.pin_list for handler in handlers) + ) # a list of updates that haven't been sent to address handlers yet. This # allows a few mux changes to get updated at the same time. self._pending_updates: list[PinUpdate] = [] self._active_pins: set[Pin] = set() - def add_update( - self, - pin_update: PinUpdate, - trigger_update: bool = True - ) -> None: + def add_update(self, pin_update: PinUpdate, trigger_update: bool = True) -> None: """This method should be registered with each virtual mux to route pin changes.""" self._pending_updates.append(pin_update) @@ -601,7 +619,6 @@ def reset(self) -> None: """ self._dispatch_pin_state(PinSetState(off=self._all_pins)) - def update_input(self) -> None: """ Iterates through the address_handlers and reads the values back to update the pin values for the digital inputs @@ -610,11 +627,15 @@ def update_input(self) -> None: raise NotImplementedError # used in a few scripts - def update_pin_by_name(self, name: Pin, value: bool, trigger_update: bool =True) -> None: + def update_pin_by_name( + self, name: Pin, value: bool, trigger_update: bool = True + ) -> None: raise NotImplementedError # not used in any scripts - def update_pins_by_name(self, pins: Collection[Pin], trigger_update: bool=True) -> None: + def update_pins_by_name( + self, pins: Collection[Pin], trigger_update: bool = True + ) -> None: raise NotImplementedError def __getitem__(self, item: Pin) -> bool: @@ -638,6 +659,7 @@ class JigMuxGroup(MuxGroup): mux_one: MuxOne = field(default_factory=MuxOne) mux_two: MuxTwo = field(default_factory=MuxTwo) """ + def get_multiplexers(self) -> list[VirtualMux]: return [attr for attr in self.__dict__.values() if isinstance(attr, VirtualMux)] @@ -663,14 +685,19 @@ class JigDriver(Generic[JigSpecificMuxGroup]): The jig driver joins muxes to handlers by matching up pin definitions. """ - def __init__(self, mux_group_factory: Callable[[],JigSpecificMuxGroup], handlers: Sequence[AddressHandler]): + + def __init__( + self, + mux_group_factory: Callable[[], JigSpecificMuxGroup], + handlers: Sequence[AddressHandler], + ): self.virtual_map = VirtualAddressMap(handlers) - + self.mux = mux_group_factory() for mux in self.mux.get_multiplexers(): # Perhaps we should instantiate the virtual mux here - # and pass in the virtual_map.add_update. But we'd have to do some - # magic in the MuxGroup call to pass add_update to each VirtualMux + # and pass in the virtual_map.add_update. But we'd have to do some + # magic in the MuxGroup call to pass add_update to each VirtualMux # constructor, and I was hoping to just use a dataclass... mux._update_pins = self.virtual_map.add_update @@ -710,7 +737,9 @@ def generate_bit_sets(bits: Sequence[T]) -> Generator[set[T], None, None]: list(generate_bit_set(["x0", "x1"])) -> [set(), {'x0'}, {'x1'}, {'x0', 'x1'}] """ int_list = range(1 << len(bits)) if len(bits) != 0 else range(0) - return ({bit for i, bit in enumerate(bits) if (1 << i) & index} for index in int_list) + return ( + {bit for i, bit in enumerate(bits) if (1 << i) & index} for index in int_list + ) def bit_generator() -> Generator[int, None, None]: diff --git a/src/fixate/drivers/__init__.py b/src/fixate/drivers/__init__.py index eb24a5b7..6504c298 100644 --- a/src/fixate/drivers/__init__.py +++ b/src/fixate/drivers/__init__.py @@ -1,8 +1,4 @@ -try: - from typing import Protocol -except ImportError: - # Protocol added in python 3.8 - from typing_extensions import Protocol +from typing import Protocol import pubsub.pub diff --git a/test/core/test_switching.py b/test/core/test_switching.py index b943d8ee..fa428030 100644 --- a/test/core/test_switching.py +++ b/test/core/test_switching.py @@ -1,4 +1,10 @@ -from fixate.core.switching import generate_bit_sets, VirtualMux, bit_generator, PinSetState, PinUpdate +from fixate.core.switching import ( + generate_bit_sets, + VirtualMux, + bit_generator, + PinSetState, + PinUpdate, +) ################################################################ @@ -22,10 +28,11 @@ def test_generate_bit_sets_multiple_bits(): {"b2"}, {"b2", "b0"}, {"b2", "b1"}, - {"b2", "b1", "b0"} + {"b2", "b1", "b0"}, ] assert list(generate_bit_sets(["b0", "b1", "b2"])) == expected + def test_bit_generator(): """b1, b10, b100, b1000, ...""" bit_gen = bit_generator() @@ -34,6 +41,7 @@ def test_bit_generator(): expected = [1, 2, 4, 8, 16, 32, 64, 128] assert actual == expected + ################################################################ # virtual mux definitions @@ -107,27 +115,31 @@ class NestedVirtualMux(VirtualMux): "a2_b1_c1": {"x1", "x2", "x4"}, } + ################################################################ # Helper dataclasses + def test_pin_set_state_or(): a = PinSetState(frozenset("ab"), frozenset("xy")) b = PinSetState(frozenset("cd"), frozenset("x")) assert a | b == PinSetState(frozenset("abcd"), frozenset("xy")) + def test_pin_update_or(): a = PinUpdate( PinSetState(frozenset("a"), frozenset("b")), PinSetState(frozenset(), frozenset("yz")), - 1.0) + 1.0, + ) b = PinUpdate( PinSetState(frozenset("x"), frozenset()), PinSetState(frozenset("c"), frozenset("d")), - 2.0 + 2.0, ) expected = PinUpdate( PinSetState(frozenset("ax"), frozenset("b")), PinSetState(frozenset("c"), frozenset("yzd")), - 2.0 + 2.0, ) - assert expected == a | b \ No newline at end of file + assert expected == a | b diff --git a/tox.ini b/tox.ini index 4634a858..de5a566e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py37,py38,py39,py310,py311,black,mypy +envlist = py38,py39,py310,py311,py212,black,mypy isolated_build = True [testenv] @@ -38,7 +38,7 @@ markers = [testenv:mypy] basepython = python3 -deps = mypy==1.3 +deps = mypy==1.10.0 # mypy gives different results if you actually install the stuff before you check it # separate cache to stop weirdness around sharing cache with other instances of mypy commands = mypy --cache-dir="{envdir}/mypy_cache" --config-file=mypy.ini