diff --git a/hardware-specific-tests/picamera2/test_exposure_time_drift.py b/hardware-specific-tests/picamera2/test_exposure_time_drift.py index 1fd653f0..9a737622 100644 --- a/hardware-specific-tests/picamera2/test_exposure_time_drift.py +++ b/hardware-specific-tests/picamera2/test_exposure_time_drift.py @@ -105,7 +105,7 @@ def test_exposure_time_on_start_and_stop_stream(): time.sleep(0.5) # Take a couple of images to make sure that the exposure is adjusted to # a hardware compatible value. - for i in range(2): + for _i in range(2): client.capture_jpeg(resolution="full") # Save this time. set_time = client.exposure_time @@ -135,7 +135,7 @@ def _load_camera_and_return_exposure(tmpdir: str) -> int: time.sleep(0.5) # Take a couple of images to make sure that the exposure is adjusted to # a hardware compatible value. - for i in range(2): + for _i in range(2): client.capture_jpeg(resolution="full") # Save this time. return client.exposure_time diff --git a/pyproject.toml b/pyproject.toml index bbabc744..746cc24e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,7 +109,7 @@ select = [ "W", #pycodestyle warnings "F", # PyFlakes # "PL", # Pylint (a subset of, catches far less than pylint does) -# "B", # Flake8 bugbear + "B", # Flake8 bugbear "A", # Flake8 builtins checker "C", # Flake8 comprehensions # "FIX", #TODO/FIXME's in code. These should be added during development for ongoing tasks. @@ -136,6 +136,12 @@ ignore = [ # The checkers below should be turned on as they complain about missing docstrings. ] +[tool.ruff.lint.per-file-ignores] +# Tests are currently not fully docstring-ed, we'll ignore this for now. +"tests/*" = [ + "B018", # Complaining about useless attribute access in tests, but we need them to check errors are raised +] + [tool.ruff.lint.pydocstyle] # This lets the D401 checker understand that decorated thing properties and thing # settings act like properties so should be documented as such. diff --git a/src/openflexure_microscope_server/things/camera/picamera.py b/src/openflexure_microscope_server/things/camera/picamera.py index 1e2a98b0..130537de 100644 --- a/src/openflexure_microscope_server/things/camera/picamera.py +++ b/src/openflexure_microscope_server/things/camera/picamera.py @@ -346,8 +346,8 @@ class StreamingPiCamera2(BaseCamera): sensor_modes property, and then starts the streams. """ self._initialise_picamera() - # populate sensor modes by reading the property - self.sensor_modes + # Sensor modes is a cached property read it once after initialising the camera + _modes = self.sensor_modes self.start_streaming() return self diff --git a/src/openflexure_microscope_server/things/stage/__init__.py b/src/openflexure_microscope_server/things/stage/__init__.py index 85d2495a..a57f903d 100644 --- a/src/openflexure_microscope_server/things/stage/__init__.py +++ b/src/openflexure_microscope_server/things/stage/__init__.py @@ -98,7 +98,9 @@ class BaseStage(lt.Thing): if isinstance(position, (list, tuple)): return [ -int(pos) if inverted else int(pos) - for pos, inverted in zip(position, self.axis_inverted.values()) + for pos, inverted in zip( + position, self.axis_inverted.values(), strict=True + ) ] if isinstance(position, Mapping): try: diff --git a/src/openflexure_microscope_server/things/stage/dummy.py b/src/openflexure_microscope_server/things/stage/dummy.py index 8c01c186..8b71f78f 100644 --- a/src/openflexure_microscope_server/things/stage/dummy.py +++ b/src/openflexure_microscope_server/things/stage/dummy.py @@ -65,7 +65,7 @@ class DummyStage(BaseStage): fraction_complete = (time.time() - start_time) / (dt * max_displacement) self.instantaneous_position = { ax: self._hardware_position[ax] + int(fraction_complete * disp) - for ax, disp in zip(self.axis_names, displacement) + for ax, disp in zip(self.axis_names, displacement, strict=True) } fraction_complete = 1.0 except lt.exceptions.InvocationCancelledError as e: @@ -77,7 +77,7 @@ class DummyStage(BaseStage): self.moving = False self._hardware_position = { ax: self._hardware_position[ax] + int(fraction_complete * disp) - for ax, disp in zip(self.axis_names, displacement) + for ax, disp in zip(self.axis_names, displacement, strict=True) } self.instantaneous_position = self._hardware_position diff --git a/src/openflexure_microscope_server/things/stage/sangaboard.py b/src/openflexure_microscope_server/things/stage/sangaboard.py index 4f3d661d..d6ab3b29 100644 --- a/src/openflexure_microscope_server/things/stage/sangaboard.py +++ b/src/openflexure_microscope_server/things/stage/sangaboard.py @@ -77,7 +77,9 @@ class SangaboardThing(BaseStage): def update_position(self) -> None: """Read position from the stage and set the corresponding property.""" with self.sangaboard() as sb: - self._hardware_position = dict(zip(self.axis_names, sb.position)) + self._hardware_position = dict( + zip(self.axis_names, sb.position, strict=True) + ) def _hardware_move_relative( self, @@ -164,7 +166,7 @@ class SangaboardThing(BaseStage): "Brightness control is not yet implemented. Desired brightness: " f"{intended_brightness}. Set brightness: {on_brightness}" ) - for i in range(number_of_flashes): + for _i in range(number_of_flashes): sb.query(f"{led_command} 0") time.sleep(dt) sb.query(f"{led_command} {on_brightness}") diff --git a/tests/test_camera_buffer.py b/tests/test_camera_buffer.py index f7dda0c8..a4d199b2 100644 --- a/tests/test_camera_buffer.py +++ b/tests/test_camera_buffer.py @@ -147,13 +147,13 @@ def test_buffer_size_respected(): images = [] buffer_ids = [] - for i in range(10): + for _i in range(10): image = random_image() buffer_id = mem_buf.add_image(image, buffer_max=5) images.append(image) buffer_ids.append(buffer_id) - for i, (image, buffer_id) in enumerate(zip(images, buffer_ids)): + for i, (image, buffer_id) in enumerate(zip(images, buffer_ids, strict=True)): if i < 5: with pytest.raises(NoImageInMemoryError): mem_buf.get_image(buffer_id) @@ -168,7 +168,7 @@ def test_clear_buffer(): images = [] buffer_ids = [] - for i in range(10): + for _i in range(10): image = random_image() buffer_id = mem_buf.add_image(image, buffer_max=10) images.append(image) @@ -178,7 +178,7 @@ def test_clear_buffer(): mem_buf.clear() # They are now gone - for i, (image, buffer_id) in enumerate(zip(images, buffer_ids)): + for _i, (_image, buffer_id) in enumerate(zip(images, buffer_ids, strict=True)): with pytest.raises(NoImageInMemoryError): mem_buf.get_image(buffer_id) @@ -190,7 +190,7 @@ def test_get_metadata_too(): images = [] metadatas = [] buffer_ids = [] - for i in range(10): + for _i in range(10): image = random_image() metadata = random_metadata() buffer_id = mem_buf.add_image(image, metadata, buffer_max=10) @@ -199,10 +199,10 @@ def test_get_metadata_too(): buffer_ids.append(buffer_id) # Preallocate zipped data, to avoid long confusing lines - zipped = zip(images, metadatas, buffer_ids) + zipped = zip(images, metadatas, buffer_ids, strict=True) # Check both image and metadata - for i, (image, metadata, buffer_id) in enumerate(zipped): + for _i, (image, metadata, buffer_id) in enumerate(zipped): returned_image, returned_metadata = mem_buf.get_image(buffer_id) assert image is returned_image assert metadata is returned_metadata diff --git a/tests/test_dummy_server.py b/tests/test_dummy_server.py index 571a7159..66230e1b 100644 --- a/tests/test_dummy_server.py +++ b/tests/test_dummy_server.py @@ -97,11 +97,11 @@ def test_stage(client): move = {"x": 1, "y": 2, "z": 3} stage.move_relative(**move) pos = stage.position - for s, m, p in zip(start.values(), move.values(), pos.values()): + for s, m, p in zip(start.values(), move.values(), pos.values(), strict=True): assert s + m == p stage.move_relative(**{k: -v for k, v in move.items()}) pos = stage.position - for s, p in zip(start.values(), pos.values()): + for s, p in zip(start.values(), pos.values(), strict=True): assert s == p diff --git a/tests/test_scan_directories.py b/tests/test_scan_directories.py index f589c8a7..eae6a7d6 100644 --- a/tests/test_scan_directories.py +++ b/tests/test_scan_directories.py @@ -245,7 +245,7 @@ def test_scan_info(): scan_dir_manager = ScanDirectoryManager(BASE_SCAN_DIR) scan_dir = scan_dir_manager.new_scan_dir("fake_scan") - for i in range(17): + for _i in range(17): _add_fake_image(scan_dir) info = scan_dir.scan_info() now = time.time() @@ -273,7 +273,7 @@ def test_get_final_stitch(): scan_dir = scan_dir_manager.new_scan_dir("fake_scan") # Create some scan images files - for i in range(17): + for _i in range(17): _add_fake_image(scan_dir) # No scans, so None should be returned, from both manager and scan dir @@ -365,7 +365,7 @@ def test_zipping_scan_data(): scan_dir = scan_dir_manager.new_scan_dir("fake_scan") # Create 21 fake scan images, a fake stitch, and a fake zip - for i in range(21): + for _i in range(21): _add_fake_image(scan_dir) _add_fake_file(scan_dir, "fake_scan_0001_stitched.jpg", in_im_dir=True) _add_fake_file(scan_dir, "zipfile.zip") @@ -502,7 +502,7 @@ def test_extracting_files(): assert scan_dir._extract_dzi_files(scan_files) == [] # Add a number of images - for i in range(2321): + for _i in range(2321): _add_fake_image(scan_dir) scan_files = scan_dir.get_scan_files() diff --git a/tests/test_stage.py b/tests/test_stage.py index e4d395b6..c9ae0918 100644 --- a/tests/test_stage.py +++ b/tests/test_stage.py @@ -212,7 +212,7 @@ def _test_move_relative(dummy_stage, axis_inverted, path): dummy_stage.move_relative( cancel=cancel, x=movement[0], y=movement[1], z=movement[2] ) - position = [pos + move for pos, move in zip(position, movement)] + position = [pos + move for pos, move in zip(position, movement, strict=True)] stage_pos = dummy_stage.get_xyz_position() hw_pos = dummy_stage._hardware_position assert position[0] == stage_pos[0] == hw_pos["x"] * x_dir @@ -244,7 +244,7 @@ def test_move_relative(stage_client, dummy_stage, path): axis_names = stage_client.axis_names # Create every combination of True/False for x,y,z. inversion_combinations = [ - dict(zip(axis_names, inverted)) + dict(zip(axis_names, inverted, strict=True)) for inverted in itertools.product([True, False], repeat=len(axis_names)) ] print(path) @@ -305,7 +305,7 @@ def test_move_absolute(stage_client, dummy_stage, path): axis_names = stage_client.axis_names # Create every combination of True/False for x,y,z. inversion_combinations = [ - dict(zip(axis_names, inverted)) + dict(zip(axis_names, inverted, strict=True)) for inverted in itertools.product([True, False], repeat=len(axis_names)) ] for axis_inverted in inversion_combinations: diff --git a/tests/utilities/scan_test_helpers.py b/tests/utilities/scan_test_helpers.py index 8783e9f0..288996ac 100644 --- a/tests/utilities/scan_test_helpers.py +++ b/tests/utilities/scan_test_helpers.py @@ -60,8 +60,8 @@ def visualise_scan(sample: FakeSample, planner: scan_planners.ScanPlanner) -> Fi """For a given sample and scanner object return a matplotlib figure of the scan.""" fig, ax = plt.subplots(figsize=(8, 8)) ax.add_artist(sample.patch) - xh, yh = zip(*planner._path_history) - xi, yi, _ = zip(*planner._imaged_locations) + xh, yh = zip(*planner._path_history, strict=True) + xi, yi, _ = zip(*planner._imaged_locations, strict=True) # convert history to numpy array so can calculate quiver arrows xh = np.array(xh) @@ -91,7 +91,7 @@ def interp_closed_path(xy_points: list[tuple[int, int]], n_points: int) -> MatPa https://stackoverflow.com/questions/33962717/interpolating-a-closed-curve-using-scipy """ # Use zip to separate x and y points into tuples - x, y = zip(*xy_points) + x, y = zip(*xy_points, strict=True) # Append first point and convert to array x = np.array(x + (x[0],)) @@ -105,7 +105,7 @@ def interp_closed_path(xy_points: list[tuple[int, int]], n_points: int) -> MatPa xi, yi = interpolate.splev(np.linspace(0, 1, n_points), spline_data) # Convert to a matplotlib closed path - path_points = [[xp, yp] for xp, yp in (zip(xi, yi))] + path_points = [[xp, yp] for xp, yp in (zip(xi, yi, strict=True))] return MatPath(path_points, closed=True)