Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI - Lint
name: CI - Lint and Type Check

on:
push:
Expand All @@ -17,21 +17,21 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v6

- name: Set up uv with Python
- name: Set up uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57
with:
python-version: "3.14"
enable-cache: true

- name: Sync dependencies (locked, dev)
- name: Sync dependencies
run: |
uv sync --locked --dev

- name: Run ruff (as in pre-commit)
- name: Lint
run: |
uv run ruff check --exclude tests .

- name: Run ruff format (check)
- name: Format
run: |
uv run ruff format --check --exclude tests .

Expand All @@ -41,3 +41,25 @@ jobs:
# run: |
# uv run pytest

type-check:
name: Type Check
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Set up uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57
with:
python-version: "3.14"
enable-cache: true

- name: Sync dependencies
run: |
uv sync --locked --dev

- name: Type check
run: |
uv run ty check src

8 changes: 8 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,11 @@ repos:
- id: ruff-format
- id: ruff-check
args: ["--fix", "--exclude", "tests"]

- repo: local
hooks:
- id: ty-check
name: ty check (src)
entry: uv run ty check src
language: system
pass_filenames: false
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ dev = [
"ruff>=0.1.0",
# "mypy>=1.8.0",
"pre-commit>=3.6",
"ty>=0.0.30",
"pandas-stubs>=3.0.0.260204",
]
build = [
"pyinstaller>=6.15.0",
Expand Down Expand Up @@ -121,3 +123,6 @@ strict_optional = true
[[tool.mypy.overrides]]
module = "PySide6.*"
ignore_missing_imports = true

[tool.ty.rules]
unresolved-attribute = "ignore"
13 changes: 9 additions & 4 deletions src/clinical_dbs_annotator/controllers/wizard_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,12 +476,17 @@ def undo_last_session_entry(self, view) -> None:

# Read the TSV file and filter out rows with the last block_id
file_path = self.session_data.file_path
if file_path is None:
QMessageBox.warning(
view, "Error", "No file path is associated with this session."
)
return
rows_to_keep = []
rows_to_delete = []

with open(file_path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f, delimiter="\t")
fieldnames = reader.fieldnames
fieldnames = list(reader.fieldnames or [])

for row in reader:
block_id = row.get("block_id", "")
Expand Down Expand Up @@ -528,11 +533,11 @@ def close_session(self, parent) -> None:
parent,
"Confirm Close Session",
"Are you sure you want to close the current session? The session will be saved before closing.",
QMessageBox.Ok | QMessageBox.Cancel,
QMessageBox.Cancel,
QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel,
QMessageBox.StandardButton.Cancel,
)

if reply == QMessageBox.Ok:
if reply == QMessageBox.StandardButton.Ok:
self.session_data.close_file()
parent.close()

Expand Down
23 changes: 20 additions & 3 deletions src/clinical_dbs_annotator/logging_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import threading
from logging.handlers import RotatingFileHandler
from pathlib import Path
from types import TracebackType

from PySide6.QtCore import (
QMessageLogContext,
Expand All @@ -21,8 +22,24 @@
_crash_log_file = None


def _safe_exc_info(
exc_type: type[BaseException],
exc_value: BaseException | None,
exc_traceback: TracebackType | None,
) -> (
tuple[type[BaseException], BaseException, TracebackType | None]
| tuple[None, None, None]
):
"""Normalize exception tuple for logging APIs that require non-optional exception values."""
if exc_value is None:
return (None, None, None)
return (exc_type, exc_value, exc_traceback)


def _install_exception_hooks() -> None:
def exc_hook(exc_type, exc, tb):
def exc_hook(
exc_type: type[BaseException], exc: BaseException, tb: TracebackType | None
) -> None:
logging.getLogger("uncaught").critical(
"Uncaught exception",
exc_info=(exc_type, exc, tb),
Expand All @@ -33,7 +50,7 @@ def exc_hook(exc_type, exc, tb):
def thread_exc_hook(args: threading.ExceptHookArgs) -> None:
logging.getLogger("uncaught").critical(
"Uncaught thread exception",
exc_info=(args.exc_type, args.exc_value, args.exc_traceback),
exc_info=_safe_exc_info(args.exc_type, args.exc_value, args.exc_traceback),
)

threading.excepthook = thread_exc_hook
Expand All @@ -42,7 +59,7 @@ def unraisable_hook(args: sys.UnraisableHookArgs) -> None:
logging.getLogger("uncaught").error(
"Unraisable exception in %r",
args.object,
exc_info=(args.exc_type, args.exc_value, args.exc_traceback),
exc_info=_safe_exc_info(args.exc_type, args.exc_value, args.exc_traceback),
)

sys.unraisablehook = unraisable_hook
Expand Down
4 changes: 2 additions & 2 deletions src/clinical_dbs_annotator/models/session_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def open_file_append(
try:
with open(file_path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f, delimiter="\t")
existing_fieldnames = reader.fieldnames
existing_fieldnames = list(reader.fieldnames or [])
except Exception:
logger.warning(
"Failed to read existing TSV headers, using defaults: %s",
Expand Down Expand Up @@ -388,7 +388,7 @@ def open_simple_file_append(self, filepath: str) -> None:
try:
with open(filepath, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f, delimiter="\t")
fieldnames = reader.fieldnames
fieldnames = list(reader.fieldnames or [])
except Exception:
logger.warning(
"Failed reading annotation TSV headers, using defaults: %s",
Expand Down
29 changes: 12 additions & 17 deletions src/clinical_dbs_annotator/ui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import typing

from PySide6.QtCore import QEvent, QSize, Qt, Signal
from PySide6.QtCore import QByteArray, QEvent, QSize, Qt, Signal
from PySide6.QtGui import QIcon, QPixmap
from PySide6.QtWidgets import (
QFrame,
Expand Down Expand Up @@ -168,10 +168,10 @@ def _create_arrow_button(self, direction: str, double: bool) -> QPushButton:
"""
btn = QPushButton()
btn.setIcon(create_arrow_icon(direction, double))
btn.setIconSize(QSize(*ICON_SIZES["increment"]))
btn.setFixedSize(
BUTTON_SIZES["increment"]["width"], BUTTON_SIZES["increment"]["height"]
)
icon_width, icon_height = typing.cast(tuple[int, int], ICON_SIZES["increment"])
btn.setIconSize(QSize(icon_width, icon_height))
increment_size = typing.cast(dict[str, int], BUTTON_SIZES["increment"])
btn.setFixedSize(increment_size["width"], increment_size["height"])
btn.setCursor(Qt.PointingHandCursor)
btn.setStyleSheet(
"""
Expand Down Expand Up @@ -353,7 +353,7 @@ def _create_lr_arrow_icon(self, direction: str, double: bool) -> QIcon:
"""

pixmap = QPixmap()
pixmap.loadFromData(bytes(svg, encoding="utf-8"), "SVG")
pixmap.loadFromData(QByteArray(svg.encode("utf-8")))
return QIcon(pixmap)

def _create_x_icon(self) -> QIcon:
Expand All @@ -366,7 +366,7 @@ def _create_x_icon(self) -> QIcon:
</svg>
"""
pixmap = QPixmap()
pixmap.loadFromData(bytes(svg, encoding="utf-8"), "SVG")
pixmap.loadFromData(QByteArray(svg.encode("utf-8")))
return QIcon(pixmap)

@typing.override
Expand Down Expand Up @@ -437,26 +437,22 @@ def _toggle_disabled(self) -> None:
self.valueChanged.emit(self._value)

# Public API (mirrors old InteractiveProgressBar)
@typing.override
def setMinimum(self, value: int) -> None:
def setMinimum(self, value: int) -> None: # noqa: N802
"""Set the minimum internal value."""
self._minimum = int(value)
self.progress_bar.setMinimum(int(value))

@typing.override
def setMaximum(self, value: int) -> None:
def setMaximum(self, value: int) -> None: # noqa: N802
"""Set the maximum internal value."""
self._maximum = int(value)
self.progress_bar.setMaximum(int(value))

@typing.override
def setValue(self, value: int) -> None:
def setValue(self, value: int) -> None: # noqa: N802
"""Set the current value and update the progress bar."""
self._value = int(value)
self.progress_bar.setValue(int(value))

@typing.override
def setFormat(self, format_str: str) -> None:
def setFormat(self, format_str: str) -> None: # noqa: N802
"""Set the text format displayed on the progress bar."""
self.progress_bar.setFormat(format_str)

Expand All @@ -477,8 +473,7 @@ def value(self) -> int:
"""Return the current internal value."""
return self._value

@typing.override
def isDisabled(self) -> bool:
def isDisabled(self) -> bool: # noqa: N802
"""Return True if the widget is in disabled state."""
return self._disabled

Expand Down
4 changes: 2 additions & 2 deletions src/clinical_dbs_annotator/utils/graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
and button animations.
"""

from PySide6.QtCore import QRectF, Qt, QTimer
from PySide6.QtCore import QByteArray, QRectF, Qt, QTimer
from PySide6.QtGui import QIcon, QPainter, QPainterPath, QPixmap

from ..config import BUTTON_PULSE_COUNT, BUTTON_PULSE_DURATION
Expand Down Expand Up @@ -54,7 +54,7 @@ def create_arrow_icon(direction: str = "up", double: bool = False) -> QIcon:
"""

pixmap = QPixmap()
pixmap.loadFromData(bytes(svg, encoding="utf-8"), "SVG")
pixmap.loadFromData(QByteArray(svg.encode("utf-8")))
return QIcon(pixmap)


Expand Down
Loading
Loading