Skip to content

Commit

Permalink
getting help from ruff
Browse files Browse the repository at this point in the history
  • Loading branch information
vreuter committed Apr 26, 2024
1 parent 704fd1c commit 85e1582
Show file tree
Hide file tree
Showing 12 changed files with 77 additions and 68 deletions.
12 changes: 6 additions & 6 deletions looptrace_regionals_vis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
),
returns="List of paths to files in the given/default location within this package",
)
def find_package_files(subfolder: str = "examples") -> list[Path]:
search_folder = get_package_resources().joinpath(subfolder)
def find_package_files(subfolder: str = "examples") -> list[Path]: # noqa: D103
search_folder = _get_package_resources().joinpath(subfolder)
result = [path for path in search_folder.iterdir() if path.is_file()]
if len(result) == 0:
raise ValueError(
Expand All @@ -33,18 +33,18 @@ def find_package_files(subfolder: str = "examples") -> list[Path]:


@doc(summary="Get the path to this package's examples folder.")
def get_package_examples_folder() -> Path:
return get_package_resources().joinpath("examples")
def get_package_examples_folder() -> Path: # noqa: D103
return _get_package_resources().joinpath("examples")


@doc(
summary="Get the hook with which to access resources bundled with this packge.",
see_also="importlib.resources.files",
)
def get_package_resources() -> Traversable:
def _get_package_resources() -> Traversable:
return importlib.resources.files(_PACKAGE_NAME)


@doc(summary="List the files bundles as examples with this package.")
def list_package_example_files() -> list[Path]:
def list_package_example_files() -> list[Path]: # noqa: D103
return [path for path in get_package_examples_folder().iterdir() if path.is_file()]
31 changes: 21 additions & 10 deletions looptrace_regionals_vis/bounding_box.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Bounding boxes"""

from abc import abstractmethod
import dataclasses
from abc import abstractmethod
from collections.abc import Iterable
from math import floor
from typing import Iterable, Protocol
from typing import Protocol

from numpydoc_decorator import doc

Expand Down Expand Up @@ -71,8 +72,18 @@ class BoundingBox3D(RectangularPrismLike): # noqa: D101
x_max: float

@classmethod
def from_flat_arguments(
cls, *, zc, yc, xc, z_min, z_max, y_min, y_max, x_min, x_max
def from_flat_arguments( # noqa: D102
cls,
*,
zc, # noqa: ANN001
yc, # noqa: ANN001
xc, # noqa: ANN001
z_min, # noqa: ANN001
z_max, # noqa: ANN001
y_min, # noqa: ANN001
y_max, # noqa: ANN001
x_min, # noqa: ANN001
x_max, # noqa: ANN001
) -> "BoundingBox3D":
point = Point3D(z=zc, y=yc, x=xc)
return cls(
Expand All @@ -86,27 +97,27 @@ def from_flat_arguments(
)

@doc(summary="Left side in x dimension")
def get_x_min(self) -> float:
def get_x_min(self) -> float: # noqa: D102
return self.x_min

@doc(summary="Right side in x dimension")
def get_x_max(self) -> float:
def get_x_max(self) -> float: # noqa: D102
return self.x_max

@doc(summary="Left side in y dimension")
def get_y_min(self) -> float:
def get_y_min(self) -> float: # noqa: D102
return self.y_min

@doc(summary="Right side in y dimension")
def get_y_max(self) -> float:
def get_y_max(self) -> float: # noqa: D102
return self.y_max

@doc(summary="Left side in z dimension")
def get_z_min(self) -> float:
def get_z_min(self) -> float: # noqa: D102
return self.z_min

@doc(summary="Right side in z dimension")
def get_z_max(self) -> float:
def get_z_max(self) -> float: # noqa: D102
return self.z_max

def __post_init__(self) -> None:
Expand Down
12 changes: 6 additions & 6 deletions looptrace_regionals_vis/processing.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Data types related to encoding of data processing steps and status"""

from enum import Enum
import logging
from enum import Enum
from pathlib import Path
from typing import Optional

from numpydoc_decorator import doc
import pandas as pd
from numpydoc_decorator import doc

from .bounding_box import BoundingBox3D
from .colors import INDIGO, PALE_RED_CLAY, PALE_SKY_BLUE
Expand All @@ -32,7 +32,7 @@ def filename_extension(self) -> str:
def from_string(cls, s: str) -> Optional["ProcessingStep"]:
"""Attempt to parse given string as a processing step."""
for member in cls:
if s == member.name or s == member.value or s == member.filename_extension:
if s in {member.name, member.value, member.filename_extension}:
return member
return None

Expand Down Expand Up @@ -63,7 +63,7 @@ def color(self) -> str:
summary="Decide whether to use the given record.",
parameters=dict(record="Record (e.g., row from CSV) of data to consider for building box."),
)
def record_to_box(self, record: MappingLike) -> Optional[BoundingBox3D]:
def record_to_box(self, record: MappingLike) -> Optional[BoundingBox3D]: # noqa: D102
data = record.to_dict() if isinstance(record, pd.Series) else record
return BoundingBox3D.from_flat_arguments(**data)

Expand All @@ -72,10 +72,10 @@ def from_filename(cls, fn: str) -> Optional["ProcessingStatus"]:
"""Attempt to infer processing status from given filename."""
chunks = fn.split(".")
if not chunks[0].endswith("_rois"):
logging.debug(f"There's no ROI-indicative suffix in file basename ({chunks[0]})")
logging.debug("There's no ROI-indicative suffix in file basename (%s)", chunks[0])
return None
if chunks[-1] != "csv":
logging.debug(f"No CSV extension on filename '{fn}'")
logging.debug("No CSV extension on filename '%s'", fn)
return None
steps = tuple(ProcessingStep.from_string(c) for c in chunks[1:-1])
for member in cls:
Expand Down
25 changes: 15 additions & 10 deletions looptrace_regionals_vis/reader.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
"""Tools for creating the reader of regional points data"""

from collections import Counter
from collections.abc import Callable
import dataclasses
import logging
import os
from collections import Counter
from collections.abc import Callable
from pathlib import Path
from typing import Literal, Optional

import pandas as pd
from gertils.types import TimepointFrom0
from numpydoc_decorator import doc
import pandas as pd

from .bounding_box import BoundingBox3D
from .point import FloatLike, Point3D
Expand All @@ -36,7 +35,7 @@
def get_reader(path: PathOrPaths) -> Optional[Reader]:
"""Get a single-file parser with which to build layer data."""

def do_not_parse(msg, *, level=logging.DEBUG) -> None:
def do_not_parse(msg, *, level=logging.DEBUG) -> None: # noqa: ANN001
logging.log(msg=msg, level=level)

# Check that the given path is indeed a single extant file.
Expand All @@ -62,7 +61,7 @@ def do_not_parse(msg, *, level=logging.DEBUG) -> None:
return None

# Create the parser.
def build_layers(folder) -> list[FullDataLayer]:
def build_layers(folder) -> list[FullDataLayer]: # noqa: ANN001
# Map (uniquely!) each data kind/status to a file to parse.
file_by_kind: dict[ProcessingStatus, Path] = {}
for fp in Path(folder).iterdir():
Expand Down Expand Up @@ -90,7 +89,9 @@ def build_layers(folder) -> list[FullDataLayer]:
if box is None:
continue
for q1, q2, q3, q4, is_center_slice in box.iter_z_slices():
corners.append([[timepoint, channel] + point_to_list(pt) for pt in [q1, q2, q3, q4]])
corners.append(
[[timepoint, channel, *point_to_list(pt)] for pt in [q1, q2, q3, q4]]
)
shapes.append("rectangle" if is_center_slice else "ellipse")
logging.debug("Point count for status %s: %d", status.name, len(corners))
params: dict[str, object] = {
Expand All @@ -111,12 +112,16 @@ def build_layers(folder) -> list[FullDataLayer]:
parameters=dict(path="Path to data file from which to parse bounding boxes"),
raises=dict(ValueError="If data kind/status can't be inferred from given path"),
)
def parse_boxes(path: Path) -> tuple[ProcessingStatus, list[tuple[TimepointFrom0, Optional[BoundingBox3D]]]]:
def parse_boxes( # noqa: D103
path: Path,
) -> tuple[ProcessingStatus, list[tuple[TimepointFrom0, Optional[BoundingBox3D]]]]:
status = ProcessingStatus.from_filepath(path)
if status is None:
raise ValueError(f"Could not infer data kind/status from path: {path}")
box_cols = [f.name for f in dataclasses.fields(BoundingBox3D) if f.name != "center"]
spot_data = pd.read_csv(path, usecols=BOX_CENTER_COLUMN_NAMES + box_cols + [TIME_COLUMN, CHANNEL_COLUMN])
spot_data = pd.read_csv(
path, usecols=BOX_CENTER_COLUMN_NAMES + box_cols + [TIME_COLUMN, CHANNEL_COLUMN]
)
time_channel_box_trios: list[tuple[TimepointFrom0, int, Optional[BoundingBox3D]]] = []
for _, record in spot_data.iterrows():
data = record.to_dict() if isinstance(record, pd.Series) else record
Expand All @@ -132,5 +137,5 @@ def parse_boxes(path: Path) -> tuple[ProcessingStatus, list[tuple[TimepointFrom0
parameters=dict(pt="Point to flatten"),
returns="[z, y, x]",
)
def point_to_list(pt: Point3D) -> list[FloatLike]:
def point_to_list(pt: Point3D) -> list[FloatLike]: # noqa: D103
return [pt.z, pt.y, pt.x]
26 changes: 11 additions & 15 deletions tests/test_bounding_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from typing import Optional

import hypothesis as hyp
from hypothesis import strategies as st
import pytest
from hypothesis import strategies as st

from looptrace_regionals_vis.bounding_box import BoundingBox3D
from looptrace_regionals_vis.point import Point3D
Expand Down Expand Up @@ -129,20 +129,16 @@ def test_rectangle_protocol_support(box, api_member, validation_attribute):

@hyp.given(error_inducing_arguments=gen_bbox_arguments_with_contextually_illegal_center())
def test_center_must_be_within_bounds(error_inducing_arguments):
with pytest.raises(ValueError) as error_context:
return BoundingBox3D.from_flat_arguments(**error_inducing_arguments)
exp_msg = "For each dimension, center coordinate must be within min/max bounds!"
obs_msg = str(error_context.value)
assert obs_msg == exp_msg, f"Expected error message '{exp_msg}' but got '{obs_msg}'"
with pytest.raises(
ValueError, match="For each dimension, center coordinate must be within min/max bounds!"
):
BoundingBox3D.from_flat_arguments(**error_inducing_arguments)


@hyp.given(error_inducing_arguments=gen_bbox_arguments_with_contextually_illegal_endpoints())
def test_endpoints_must_make_sense(error_inducing_arguments):
with pytest.raises(ValueError) as error_context:
return BoundingBox3D.from_flat_arguments(**error_inducing_arguments)
exp_msg = "For each dimension, min must be no more than max!"
obs_msg = str(error_context.value)
assert obs_msg == exp_msg, f"Expected error message '{exp_msg}' but got '{obs_msg}'"
with pytest.raises(ValueError, match="For each dimension, min must be no more than max!"):
BoundingBox3D.from_flat_arguments(**error_inducing_arguments)


@hyp.given(box=gen_bbox_legit())
Expand Down Expand Up @@ -174,16 +170,16 @@ def test_iter_z_slices__always_designates_zero_or_one_z_slice_as_central(box):
@hyp.given(box=gen_bbox_legit(min_z=-5, max_z=5)) # smaller z range here for efficiency
def test_iter_z_slices__maintains_box_coordinates(box):
for i, (q1, q2, q3, q4, _) in enumerate(box.iter_z_slices()):
assert (
assert ( # noqa: PT018
q1.x == box.x_max and q1.y == box.y_min
), f"Bad top-left point ({q1}) in {i}-th z-slice, from box {box}"
assert (
assert ( # noqa: PT018
q2.x == box.x_min and q2.y == box.y_min
), f"Bad top-left point ({q2}) in {i}-th z-slice, from box {box}"
assert (
assert ( # noqa: PT018
q3.x == box.x_min and q3.y == box.y_max
), f"Bad top-left point ({q3}) in {i}-th z-slice, from box {box}"
assert (
assert ( # noqa: PT018
q4.x == box.x_max and q4.y == box.y_max
), f"Bad bottom-right point ({q4}) in {i}-th z-slice, from box {box}"

Expand Down
2 changes: 1 addition & 1 deletion tests/test_bounding_box_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def test_cannot_parse_boxes_without_center(tmp_path, spots_file, drop_cols):
pd.read_csv(spots_file, index_col=0).drop(list(drop_cols), axis=1).to_csv(target)

# We expect an error because of a request to read specific columns (usecols) that will now be absent (having been removed).
with pytest.raises(ValueError) as error_context:
with pytest.raises(ValueError) as error_context: # noqa: PT011
parse_boxes(target)
assert str(error_context.value).startswith(
"Usecols do not match columns, columns expected but not found"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_color_determination.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ def test_colors_are_as_expected(status, expected_color):
), f"Color for data processing status {status} wasn't as expected ({expected_color}): {observed_color}"


@pytest.mark.parametrize("status", [s for s in ProcessingStatus])
@pytest.mark.parametrize("status", list(ProcessingStatus))
def test_each_member_of_data_status_enum_resolves_to_a_color(status):
assert status.color is not None, f"Got null color for data processing status {status}"
11 changes: 6 additions & 5 deletions tests/test_find_package_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
from unittest import mock

import hypothesis as hyp
from hypothesis import strategies as st
import pytest
from hypothesis import strategies as st

import looptrace_regionals_vis
from looptrace_regionals_vis import find_package_files


gen_non_extant_resource_folder = st.text(
alphabet=string.ascii_letters + string.digits + "_-"
).filter(lambda sub: not importlib.resources.files(looptrace_regionals_vis).joinpath(sub).is_dir())
Expand Down Expand Up @@ -40,6 +39,8 @@ def test_empty_subfolder_search_raises_expected_error(tmp_path, subfolder):
"""Here we patch the resource-finding call so that we simulate injecting an empty folder into the package resources."""
filemock = mock.MagicMock()
filemock.joinpath.return_value = tmp_path
with mock.patch("looptrace_regionals_vis.importlib.resources.files", return_value=filemock):
with pytest.raises(ValueError):
find_package_files(subfolder)
with (
mock.patch("looptrace_regionals_vis.importlib.resources.files", return_value=filemock),
pytest.raises(ValueError), # noqa: PT011
):
find_package_files(subfolder)
4 changes: 2 additions & 2 deletions tests/test_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ def determine_parameters(folder) -> list[LayerParams]:
assert folder.is_dir(), f"Could not find example folder: {folder}"
read = get_reader(folder)
if not callable(read):
raise AssertionError(f"Expected to be able to read {folder} but couldn't!")
raise RuntimeError(f"Expected to be able to read {folder} but couldn't!") # noqa: TRY004
try:
layers = read(folder)
except ValueError as e:
raise AssertionError("Expected successful data parse but didn't get it!") from e
raise RuntimeError("Expected successful data parse but didn't get it!") from e
return [params for _, params, _ in layers]
9 changes: 3 additions & 6 deletions tests/test_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
import math

import hypothesis as hyp
from hypothesis import strategies as st
import pytest
from hypothesis import strategies as st

from looptrace_regionals_vis.point import Point3D


legal_float = st.floats(allow_nan=False, allow_infinity=False)
gen_int_or_float = st.one_of(st.integers(), legal_float)
gen_infinity = st.sampled_from((-math.inf, math.inf))
Expand Down Expand Up @@ -52,9 +51,8 @@ def test_point_cannot_be_constructed_with_non_float(coordinates):
)
def test_point_cannot_be_constructed_with_infinite(coordinates):
z, y, x = coordinates
with pytest.raises(ValueError) as error_context:
with pytest.raises(ValueError, match="Cannot use an infinite value as a point coordinate!"):
Point3D(z=z, y=y, x=x)
assert "Cannot use an infinite value as a point coordinate!" == str(error_context.value)


@hyp.given(
Expand All @@ -68,6 +66,5 @@ def test_point_cannot_be_constructed_with_infinite(coordinates):
)
def test_point_cannot_be_constructed_with_nan(coordinates):
z, y, x = coordinates
with pytest.raises(ValueError) as error_context:
with pytest.raises(ValueError, match="Cannot use a null numeric as a point coordinate!"):
Point3D(z=z, y=y, x=x)
assert "Cannot use a null numeric as a point coordinate!" == str(error_context.value)
5 changes: 2 additions & 3 deletions tests/test_processing_status_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
from typing import Optional

import hypothesis as hyp
from hypothesis import strategies as st
import pytest

from hypothesis import strategies as st
from numpydoc_decorator import doc

from looptrace_regionals_vis import find_package_files
Expand All @@ -23,7 +22,7 @@
),
)
@dataclass(kw_only=True, frozen=True)
class ProcessingStatusInferenceParameterization: # noqa: D101
class ProcessingStatusInferenceParameterization:
suffix: str
extension: str
expectation: Optional[ProcessingStatus]
Expand Down
Loading

0 comments on commit 85e1582

Please sign in to comment.