diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml
index 90e34916..48adc9cd 100644
--- a/.gitea/workflows/ci.yml
+++ b/.gitea/workflows/ci.yml
@@ -16,7 +16,9 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: https://github.com/actions/checkout@v5
+ with:
+ fetch-depth: 0
- name: Setup
uses: ./.gitea/actions/install
@@ -30,16 +32,32 @@ jobs:
source .venv/bin/activate
ruff format --check
+ - name: Lint
+ run: |
+ source .venv/bin/activate
+ ruff check
+
+ - name: Checkout pyright diff plugin
+ uses: https://github.com/actions/checkout@v5
+ with:
+ repository: mx/diff_quality_basedpyright
+ path: diff_quality_basedpyright
+
+ - name: Typecheck Diff
+ run: |
+ source .venv/bin/activate
+ uv pip install -e diff_quality_basedpyright
+ diff-quality --violations=basedpyright
+
test:
runs-on: ubuntu-latest
- needs: "lint"
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13"]
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: https://github.com/actions/checkout@v5
- name: Setup
uses: ./.gitea/actions/install
@@ -58,14 +76,13 @@ jobs:
test-with-beamline-plugins:
runs-on: ubuntu-latest
- needs: ["lint", "test"]
strategy:
matrix:
plugin_repo: ["pxi_bec", "pxii_bec", "pxiii_bec"]
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: https://github.com/actions/checkout@v5
- name: Setup
uses: ./.gitea/actions/install
@@ -92,11 +109,10 @@ jobs:
test-with-coverage:
runs-on: ubuntu-latest
- needs: ["lint", "test", "test-with-beamline-plugins"]
steps:
- name: Checkout code
- uses: actions/checkout@v5
+ uses: https://github.com/actions/checkout@v5
- name: Setup
uses: ./.gitea/actions/install
@@ -112,3 +128,57 @@ jobs:
run: |
source .venv/bin/activate
pytest --cov=aare --cov-config=./pyproject.toml --cov-branch --cov-report=xml --no-cov-on-fail ./tests/unit
+
+ - name: Build coverage summary
+ run: |
+ source .venv/bin/activate
+ {
+ echo ""
+ echo "### Coverage report"
+ echo
+ echo "**Total line + branch coverage: $(coverage report --format=total)%**"
+ echo
+ echo "Per-file breakdown
"
+ echo
+ coverage report --format=markdown
+ echo
+ echo " "
+ } > coverage-summary.md
+
+ # Only the rendered summary is uploaded. coverage.xml (~1 MB) is rejected
+ # with an HTML 403 by the proxy in front of gitea.psi.ch.
+ - name: Upload coverage
+ uses: https://github.com/actions/upload-artifact@v3
+ with:
+ name: coverage
+ path: coverage-summary.md
+ if-no-files-found: error
+
+ coverage-analysis:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ needs: ["test-with-coverage"]
+ if: github.event_name == 'pull_request'
+ steps:
+ - name: Download coverage
+ uses: https://github.com/actions/download-artifact@v3
+ with:
+ name: coverage
+
+ - name: Find Comment
+ uses: https://github.com/peter-evans/find-comment@v3
+ id: fc
+ with:
+ issue-number: ${{ github.event.pull_request.number }}
+ comment-author: "github-actions[bot]"
+ body-includes: ""
+
+ - name: Create or update comment
+ uses: https://github.com/peter-evans/create-or-update-comment@v5
+ with:
+ comment-id: ${{ steps.fc.outputs.comment-id }}
+ issue-number: ${{ github.event.pull_request.number }}
+ body-path: coverage-summary.md
+ edit-mode: replace
diff --git a/pyproject.toml b/pyproject.toml
index 0a8e1aa3..ddaf69f3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,24 +7,24 @@ requires-python = ">=3.11"
dependencies = [
"uv",
"gunicorn",
- "aarecommon>=0.2.0",
- "pydantic==2.11.4",
- "numpy==2.2.5",
+ "aarecommon>=0.2.2",
+ "pydantic>=2.11",
+ "numpy",
"jfjoch_client==1.0.0rc146",
- "pyJWT==2.10.1",
- "pyzmq==26.4.0",
- "opencv-python-headless==4.11.0.86",
+ "pyJWT",
+ "pyzmq",
+ "opencv-python-headless",
"PySide6==6.9.0",
- "requests==2.32.4",
- "pyepics==3.5.8",
- "redis==6.2.0",
- "python-redis-lock==4.0.0",
- "fastapi==0.115.13",
+ "requests",
+ "pyepics~=3.5",
+ "redis",
+ "python-redis-lock",
+ "fastapi",
"uvicorn",
"aaredb>=0.2",
- "python_multipart==0.0.20",
- "websocket-client==1.8.0",
- "sseclient==0.0.27", #Was sseclient-py==1.8.0 but this was broken???
+ "python_multipart",
+ "websocket-client",
+ "sseclient",
"psi-pshell==2.1.0",
"bec_lib>=3.130.3",
"bec-ipython-client>=3.130.3",
@@ -36,18 +36,36 @@ dependencies = [
[project.optional-dependencies]
test = [
-# "pytest==9.0.3",
- "pytest-cov==7.1.0",
- "pytest-mock==3.14.0",
- "pytest-qt==4.4.0",
- "pytest-asyncio==0.25.3",
+ "basedpyright",
+ "pytest",
+ "pytest-cov",
+ "pytest-mock",
+ "pytest-qt",
+ "pytest-asyncio",
"pytest-timeout",
- "ruff==0.15.*"
+ "ruff>=0.15"
+]
+
+docs = [
+ "zensical"
]
[lint]
ignore = ["F401", "F541", "W503", "W504"]
+[tool.uv]
+# Transitive deps (aarecommon -> aarelc_infer) ask for the non-headless
+# `opencv-python`. It unpacks into the same `cv2/` directory as the
+# `opencv-python-headless` we depend on, so whichever installs last wins for the
+# overlapping files and you get a mixed install -- the Python half of one wheel
+# next to the native half of the other. That surfaces as
+# `AttributeError: partially initialized module 'cv2' has no attribute
+# 'gapi_wip_gst_GStreamerPipeline'` on `import cv2`, and it bites in the
+# test-with-beamline-plugins job because installing the plugins re-resolves and
+# flips the install order. A marker that never matches drops the requirement,
+# leaving headless as the only cv2 provider.
+override-dependencies = ["opencv-python; sys_platform == 'nonexistent'"]
+
[tool.uv.sources]
aaredb = { index = "psi"}
@@ -81,5 +99,13 @@ multi_line_output = 3
[tool.ruff]
line-length = 100
+[tool.ruff.lint]
+ignore = [
+ # Timestamps here are local beamline wall-clock and are stored/displayed as
+ # naive ISO strings; adding a tzinfo would change what aaredb and the GUI see.
+ "DTZ005",
+ "DTZ006",
+]
+
[tool.ruff.format]
skip-magic-trailing-comma = true
diff --git a/scripts/camera_stat_thread.py b/scripts/camera_stat_thread.py
index 5f444731..2d22e451 100644
--- a/scripts/camera_stat_thread.py
+++ b/scripts/camera_stat_thread.py
@@ -1,11 +1,14 @@
import json
import threading
import time
-from typing import Any, Dict
+from typing import Any
import cv2
import numpy as np
import zmq
+from aarecommon.config.logger import setup_logger
+
+logger = setup_logger("aareDAQ")
class ImageStatsReceiver:
@@ -33,7 +36,7 @@ class ImageStatsReceiver:
self.connection_attempts = 0
self.last_message_time = None
- def calculate_projections(self, image: np.ndarray) -> Dict[str, Any]:
+ def calculate_projections(self, image: np.ndarray) -> dict[str, Any]:
"""Calculate X and Y projections of the image"""
# Convert to grayscale if color image
if len(image.shape) == 3:
@@ -99,7 +102,7 @@ class ImageStatsReceiver:
"y_coords": list(range(len(y_projection))),
}
- def calculate_radial_integration(self, image: np.ndarray, num_bins: int = 50) -> Dict[str, Any]:
+ def calculate_radial_integration(self, image: np.ndarray, num_bins: int = 50) -> dict[str, Any]:
"""Calculate radial integration of the image"""
# Convert to grayscale if color image
if len(image.shape) == 3:
@@ -153,7 +156,7 @@ class ImageStatsReceiver:
"num_bins": num_bins,
}
- def calculate_image_stats(self, image: np.ndarray) -> Dict[str, Any]:
+ def calculate_image_stats(self, image: np.ndarray) -> dict[str, Any]:
"""Calculate comprehensive statistics for an image"""
image_float = image.astype(np.float64)
@@ -229,8 +232,8 @@ class ImageStatsReceiver:
except zmq.Again:
return None
- except Exception as e:
- print(f"Error processing ZMQ message: {e}")
+ except Exception:
+ logger.exception("Error processing ZMQ message")
return None
def receiver_thread(self):
@@ -250,7 +253,7 @@ class ImageStatsReceiver:
else:
print(f"[{time.strftime('%H:%M:%S')}] Waiting for image data...")
- def print_formatted_stats(self, stats: Dict[str, Any]):
+ def print_formatted_stats(self, stats: dict[str, Any]):
"""Print formatted statistics in a compact terminal format"""
timestamp = time.strftime("%H:%M:%S", time.localtime(stats["timestamp"]))
@@ -295,7 +298,7 @@ class ImageStatsReceiver:
)
# Optional: Print per-channel stats if available
- if any(k.startswith("mean_ch") for k in stats.keys()):
+ if any(k.startswith("mean_ch") for k in stats):
channels = []
i = 0
while f"mean_ch{i}" in stats:
@@ -327,7 +330,7 @@ class ImageStatsReceiver:
}
return None
- def save_radial_profile_to_file(self, filename: str = None):
+ def save_radial_profile_to_file(self, filename: str | None = None):
"""Save the current radial profile to a file"""
if filename is None:
filename = f"radial_profile_{int(time.time())}.txt"
@@ -343,13 +346,13 @@ class ImageStatsReceiver:
f.write(f"# Center: {radial['center']}\n")
f.write("# Radius(pixels)\tMean_Intensity\tStd_Intensity\tPixel_Count\n")
- for i in range(len(radial["r_centers"])):
- f.write(
- f"{radial['r_centers'][i]:.2f}\t"
- f"{radial['radial_profile'][i]:.2f}\t"
- f"{radial['radial_std'][i]:.2f}\t"
- f"{radial['pixel_counts'][i]}\n"
- )
+ f.writelines(
+ f"{radial['r_centers'][i]:.2f}\t"
+ f"{radial['radial_profile'][i]:.2f}\t"
+ f"{radial['radial_std'][i]:.2f}\t"
+ f"{radial['pixel_counts'][i]}\n"
+ for i in range(len(radial["r_centers"]))
+ )
print(f"Radial profile saved to {filename}")
return filename
@@ -392,8 +395,8 @@ def start_image_stats_receiver(zmq_url: str = "tcp://localhost:5555"):
stats_receiver = ImageStatsReceiver(zmq_url)
stats_receiver.start()
print(f"Started image statistics monitoring on {zmq_url}")
- except Exception as e:
- print(f"Failed to start image stats receiver: {e}")
+ except Exception:
+ logger.exception("Failed to start image stats receiver")
stats_receiver = None
@@ -408,8 +411,6 @@ def stop_image_stats_receiver():
def get_latest_image_stats():
"""Get the latest image statistics"""
- global stats_receiver
-
if stats_receiver and stats_receiver.latest_stats:
with stats_receiver.stats_lock:
return stats_receiver.latest_stats.copy()
@@ -418,17 +419,13 @@ def get_latest_image_stats():
def get_radial_profile():
"""Get just the radial profile data"""
- global stats_receiver
-
if stats_receiver:
return stats_receiver.get_radial_profile_summary()
return None
-def save_current_radial_profile(filename: str = None):
+def save_current_radial_profile(filename: str | None = None):
"""Save the current radial profile to a file"""
- global stats_receiver
-
if stats_receiver:
return stats_receiver.save_radial_profile_to_file(filename)
return None
diff --git a/scripts/gui_desginer.py b/scripts/gui_desginer.py
index dacc544d..ba48ca9a 100644
--- a/scripts/gui_desginer.py
+++ b/scripts/gui_desginer.py
@@ -1,19 +1,20 @@
+import math
+import sys
+from typing import ClassVar
+
+from PySide6.QtCore import QPointF, QRectF, Qt
+from PySide6.QtGui import QColor, QFont, QFontMetrics, QLinearGradient, QPainter, QPen
from PySide6.QtWidgets import (
QApplication,
- QWidget,
- QVBoxLayout,
+ QFrame,
QHBoxLayout,
QLabel,
- QFrame,
QPushButton,
QScrollArea,
QStackedWidget,
+ QVBoxLayout,
+ QWidget,
)
-from PySide6.QtCore import Qt, QPointF, QRectF
-from PySide6.QtGui import QPainter, QColor, QPen, QLinearGradient, QFont, QFontMetrics
-import sys
-import math
-
# ---------------------------------------------------------------------------
# Colour palette
@@ -114,7 +115,7 @@ class OrbitWidget(QWidget):
# LED step indicator
# ---------------------------------------------------------------------------
class LEDStages(QWidget):
- STEPS = ["Mount", "Centre", "Raster", "Collect"]
+ STEPS: ClassVar[list[str]] = ["Mount", "Centre", "Raster", "Collect"]
def __init__(self, active_step: int = 1, parent=None):
"""
diff --git a/scripts/pshell_client_old.py b/scripts/pshell_client_old.py
index 50ee1614..22c3116b 100644
--- a/scripts/pshell_client_old.py
+++ b/scripts/pshell_client_old.py
@@ -13,12 +13,12 @@ class PShellClient:
def _get_response(self, response, is_json=True):
if response.status_code != 200:
- raise Exception(response.text)
+ raise RuntimeError(response.text)
return json.loads(response.text) if is_json else response.text
def _get_binary_response(self, response):
if response.status_code != 200:
- raise Exception(response.text)
+ raise RuntimeError(response.text)
return response.raw.read()
def get_version(self):
@@ -292,8 +292,8 @@ class PShellClient:
def print_logs(self):
for log_line in self.get_logs():
- print("%s %s %-20s %-8s %s" % tuple(log_line))
+ print("{} {} {:<20} {:<8} {}".format(*log_line))
def print_devices(self):
for log_line in self.get_devices():
- print("%-16s %-32s %-10s %-32s %s" % tuple(log_line))
+ print("{:<16} {:<32} {:<10} {:<32} {}".format(*log_line))
diff --git a/scripts/redis_dump.py b/scripts/redis_dump.py
index 8db28f8f..84148556 100644
--- a/scripts/redis_dump.py
+++ b/scripts/redis_dump.py
@@ -1,9 +1,10 @@
# python
-import sys
import argparse
-from typing import Any, Dict, Tuple
-import yaml
+import sys
+from typing import Any
+
import redis
+import yaml
def decode_bulk(value: Any):
@@ -19,7 +20,7 @@ def decode_bulk(value: Any):
return value
-def fetch_key(r: redis.Redis, key: bytes) -> Tuple[str, Any]:
+def fetch_key(r: redis.Redis, key: bytes) -> tuple[str, Any]:
decode_bulk(key)
t = r.type(key)
if isinstance(t, bytes):
@@ -66,7 +67,7 @@ def dump_redis_to_yaml(
):
r = redis.Redis(host=host, port=port, db=db, password=password)
- dump: Dict[str, Any] = {"meta": {"host": host, "port": port, "db": db}, "data": {}}
+ dump: dict[str, Any] = {"meta": {"host": host, "port": port, "db": db}, "data": {}}
cursor = 0
while True:
diff --git a/src/aare/beamline_dispatch/default/beamline_dispatch.py b/src/aare/beamline_dispatch/default/beamline_dispatch.py
index 64daae3a..c432da92 100644
--- a/src/aare/beamline_dispatch/default/beamline_dispatch.py
+++ b/src/aare/beamline_dispatch/default/beamline_dispatch.py
@@ -6,7 +6,7 @@ from aare.beamline_dispatch.protocols import AuthDispatch, BeamlineDispatch
class DefaultAuthDispatch(AuthDispatch):
def get_jwt_key(self) -> str:
if (key := os.environ.get("JWT_AAREDAQ_KEY")) is None:
- raise Exception(
+ raise RuntimeError(
"JWT_AAREDAQ_KEY environment variable not set, cannot guarantee safe authentication."
)
return key
diff --git a/src/aare/daq/aaredb.py b/src/aare/daq/aaredb.py
index 2ceeec9a..70f14953 100644
--- a/src/aare/daq/aaredb.py
+++ b/src/aare/daq/aaredb.py
@@ -2,7 +2,6 @@ import datetime
import io
import json
import os
-from typing import List, Optional
import aareDB
import cv2
@@ -76,7 +75,7 @@ class AareWrapper:
self._key_file = configuration.key_file
@log_timing(logger, "AareDB call")
- def set_pucks_beamline(self, input_list: List[PuckLoadedInfo]):
+ def set_pucks_beamline(self, input_list: list[PuckLoadedInfo]):
o = []
for i in input_list:
@@ -98,12 +97,12 @@ class AareWrapper:
try:
s.db_id = self._sample_api.insert_sample(manual_sample).id
- except Exception as e:
- logger.error(f"Error inserting sample: {e}")
+ except Exception:
+ logger.exception("Error inserting sample")
@log_timing(logger, "AareDB call")
def send_sample_event(
- self, sample_id: StrictInt, event_type: SampleEventType, comment: Optional[str] = None
+ self, sample_id: StrictInt, event_type: SampleEventType, comment: str | None = None
) -> None:
if sample_id is None or sample_id < 0:
if sample_id is None:
@@ -118,12 +117,12 @@ class AareWrapper:
sample_id=sample_id,
sample_event_create=SampleEventCreate(event_type=event_type, comment=comment),
)
- except Exception as e:
- logger.error(f"Error sending sample event {event_type!s} to db: {e}")
+ except Exception:
+ logger.exception(f"Error sending sample event {event_type!s} to db")
@log_timing(logger, "AareDB call")
def upload_image(
- self, sample_id: int, filename: str, bgr_image: np.ndarray, message: Optional[str] = None
+ self, sample_id: int, filename: str, bgr_image: np.ndarray, message: str | None = None
):
_, buffer = cv2.imencode(".jpg", bgr_image)
jpeg_bytes = io.BytesIO(buffer)
@@ -145,7 +144,7 @@ class AareWrapper:
logger.debug(f"Response status code: {response.status_code}")
@log_timing(logger, "AareDB call")
- def upload_jpg(self, sample_id: int, filename: str, jpg_image, message: Optional[str] = None):
+ def upload_jpg(self, sample_id: int, filename: str, jpg_image, message: str | None = None):
logger.debug(f"jppg_image of type: {type(jpg_image)}")
url = f"{self._host}/protected_router/sample_runner/{sample_id}/upload-images"
headers = {
@@ -165,7 +164,7 @@ class AareWrapper:
@log_timing(logger, "AareDB call")
def create_rotation_run(
- self, s: Optional[SampleShortInfo], r: RotationScanRequest, d: DAQStatusModel
+ self, s: SampleShortInfo | None, r: RotationScanRequest, d: DAQStatusModel
):
if s is None:
return
@@ -233,12 +232,12 @@ class AareWrapper:
sample_id=s.db_id, experiment_parameters_create=experiment_params_payload
)
# logger.debug("Experiment parameters created:", response)
- except Exception as e:
- logger.error(e)
+ except Exception:
+ logger.exception("Error creating experiment parameters for rotation run")
@log_timing(logger, "AareDB call")
def create_gridscan_run(
- self, s: Optional[SampleShortInfo], r: RasterGridRequest, d: DAQStatusModel
+ self, s: SampleShortInfo | None, r: RasterGridRequest, d: DAQStatusModel
):
if s is None:
return
@@ -296,16 +295,16 @@ class AareWrapper:
)
# logger.info("Experiment parameters created:", response)
except Exception as e:
- logger.debug(e)
+ logger.debug(e, exc_info=True)
@log_timing(logger, "AareDB call")
def ingest_gridscan(
self,
- sample: Optional[SampleShortInfo],
+ sample: SampleShortInfo | None,
raster_result: ScanResult,
raster_request: RasterGridRequest,
geom: SampleGeometryModel,
- com: Optional[CenterOfMassModel],
+ com: CenterOfMassModel | None,
beam_mark_pxl: tuple[float, float],
):
@@ -339,11 +338,11 @@ class AareWrapper:
def format_gridscan_payload(
self,
- sample: Optional[SampleShortInfo],
+ sample: SampleShortInfo | None,
raster_result: ScanResult,
raster_request: RasterGridRequest,
geom: SampleGeometryModel,
- com: Optional[CenterOfMassModel],
+ com: CenterOfMassModel | None,
beam_mark_pxl: tuple[float, float],
) -> RasterPayloadModel | None:
@@ -377,7 +376,9 @@ class AareWrapper:
for img in raster_result.images
]
except Exception as e:
- logger.warning(f"raster score computation failed, sending null score: {e}")
+ logger.warning(
+ f"raster score computation failed, sending null score: {e}", exc_info=True
+ )
score = None
payload = RasterPayloadModel(
@@ -398,12 +399,12 @@ class AareWrapper:
except Exception as e:
logger.error(e)
- raise e
+ raise
@log_timing(logger, "AareDB call")
def ingest_scan(
self,
- sample: Optional[SampleShortInfo],
+ sample: SampleShortInfo | None,
result: ScanResult,
geom: SampleGeometryModel,
beam_mark_pxl: tuple[float, float],
@@ -437,7 +438,7 @@ class AareWrapper:
def format_scan_payload(
self,
- sample: Optional[SampleShortInfo],
+ sample: SampleShortInfo | None,
result: ScanResult,
geom: SampleGeometryModel,
beam_mark_pxl: tuple[float, float],
@@ -455,4 +456,4 @@ class AareWrapper:
except Exception as e:
logger.error(e)
- raise e
+ raise
diff --git a/src/aare/daq/auth.py b/src/aare/daq/auth.py
index 927478b2..f7ca9696 100644
--- a/src/aare/daq/auth.py
+++ b/src/aare/daq/auth.py
@@ -5,7 +5,6 @@ import pwd
import time
import uuid
from datetime import UTC, datetime, timedelta
-from typing import List
import jwt
from aarecommon.errors.exception_handler import (
@@ -38,7 +37,7 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class TokenData(BaseModel):
sub: str # Username
- pgroups: List[str]
+ pgroups: list[str]
session: int
staff: bool = False
@@ -136,6 +135,7 @@ def check_jwt_rw(cfg: BeamlineConfig, data: TokenData) -> None:
cfg.try_extend_active_session(data.session, SESSION_EXPIRE_SECONDS)
except Exception:
# In case something is wrong but you are holder (maybe redis expiry?)
+ logger.debug("Could not extend the active session; claiming it instead", exc_info=True)
cfg.try_set_active_session(data.session, SESSION_EXPIRE_SECONDS)
@@ -159,6 +159,7 @@ def check_jwt_staff(cfg: BeamlineConfig, data: TokenData) -> None:
try:
cfg.try_extend_active_session(data.session, SESSION_EXPIRE_SECONDS)
except Exception:
+ logger.debug("Could not extend the active session; claiming it instead", exc_info=True)
cfg.try_set_active_session(data.session, SESSION_EXPIRE_SECONDS)
diff --git a/src/aare/daq/beamcenterfit.py b/src/aare/daq/beamcenterfit.py
index de0f815c..280a2160 100644
--- a/src/aare/daq/beamcenterfit.py
+++ b/src/aare/daq/beamcenterfit.py
@@ -1,5 +1,5 @@
-import numpy as np
import cv2
+import numpy as np
from scipy.optimize import curve_fit
diff --git a/src/aare/daq/config.py b/src/aare/daq/config.py
index 47d71310..421c1c91 100644
--- a/src/aare/daq/config.py
+++ b/src/aare/daq/config.py
@@ -4,7 +4,6 @@ import json
import time
from dataclasses import asdict, is_dataclass
from datetime import datetime
-from typing import List, Tuple
import numpy as np
import redis
@@ -148,6 +147,7 @@ class BeamlineConfig:
try:
ttl = int(self._client.ttl(self._gui_session_key(session)))
except Exception:
+ logger.debug("Could not read the GUI session TTL", exc_info=True)
return None
if ttl > 0:
@@ -161,7 +161,7 @@ class BeamlineConfig:
try:
return OpenGuiSessionInfo(**json.loads(raw))
except Exception:
- logger.warning(f"Failed to parse GUI session info for session {session}")
+ logger.warning(f"Failed to parse GUI session info for session {session}", exc_info=True)
return None
def touch_gui_session(
@@ -263,7 +263,7 @@ class BeamlineConfig:
holder_session = holder.session if holder is not None else None
sessions: list[OpenGuiSessionInfo] = []
- for session_id in sorted((int(s) for s in session_ids)):
+ for session_id in sorted(int(s) for s in session_ids):
payload = self._read_gui_session(session_id)
if payload is not None:
payload.holds_baton = payload.session == holder_session
@@ -348,7 +348,7 @@ class BeamlineConfig:
if active is None:
self._client.set(f"{self._bl}:active_session", session)
elif active != session:
- raise Exception(
+ raise RuntimeError(
"There is already active session with different id. Try again later."
)
self._client.expire(f"{self._bl}:active_session", expiry_sec)
@@ -358,7 +358,7 @@ class BeamlineConfig:
with redis_lock.Lock(self._client, f"{self._bl}:active_session_lock", expire=10):
active = self.active_session
if active is None:
- raise Exception("There is no active session with given id. Try again later.")
+ raise RuntimeError("There is no active session with given id. Try again later.")
# if active == session:
# self._client.expire(f"{self._bl}:active_session", expiry_sec, gt=True)
@@ -372,7 +372,7 @@ class BeamlineConfig:
if current_ttl is None or current_ttl < 0 or current_ttl < expiry_sec:
self._client.expire(key, expiry_sec)
else:
- raise Exception(
+ raise RuntimeError(
"There is already active session with different id. Try again later."
)
@@ -402,6 +402,7 @@ class BeamlineConfig:
try:
return BatonHolderInfo(**json.loads(tmp))
except Exception:
+ logger.debug("Could not parse the baton holder info", exc_info=True)
return None
@baton_holder.setter
@@ -420,6 +421,7 @@ class BeamlineConfig:
try:
return BatonRequest(**json.loads(tmp))
except Exception:
+ logger.debug("Could not parse the pending baton request", exc_info=True)
return None
def set_pending_baton_request(
@@ -445,6 +447,7 @@ class BeamlineConfig:
try:
return BatonTransferQueue(**json.loads(tmp))
except Exception:
+ logger.debug("Could not parse the queued baton transfer", exc_info=True)
return None
@queued_baton_transfer.setter
@@ -457,12 +460,9 @@ class BeamlineConfig:
def can_transfer_baton_now(self) -> bool:
"""Check if baton can be transferred (beamline not mid-operation)."""
# Can't transfer while beamline is busy
- if self.state_busy:
- return False
# Add automation queue check here when you implement it
- # if self.automation_queue_running:
- # return False
- return True
+ # return not (self.state_busy or self.automation_queue_running)
+ return not self.state_busy
def execute_baton_transfer(
self,
@@ -522,9 +522,7 @@ class BeamlineConfig:
@property
def commissioning_mode(self) -> bool:
tmp = self._client.get(f"{self._bl}:commissioning_mode")
- if tmp is None:
- return False
- return True
+ return tmp is not None
@commissioning_mode.setter
def commissioning_mode(self, commisioning_mode: bool) -> None:
@@ -548,7 +546,7 @@ class BeamlineConfig:
curr_state = self.state
if curr_state != target:
self.state_busy = False
- raise Exception("Beamline is not in a proper state")
+ raise RuntimeError("Beamline is not in a proper state")
def start_moving(
self, target: BeamlineStateEnum, timeout: int | None = None
@@ -635,7 +633,7 @@ class BeamlineConfig:
return float(np.log(lens_factor / (b * target_pixel_in_mm)) / a)
@property
- def beam_center(self) -> Tuple[float, float]:
+ def beam_center(self) -> tuple[float, float]:
tmp_x = self._client.get(f"{self._bl}:beam_center_x")
tmp_y = self._client.get(f"{self._bl}:beam_center_y")
if tmp_x:
@@ -649,7 +647,7 @@ class BeamlineConfig:
return val_x, val_y
@beam_center.setter
- def beam_center(self, data: Tuple[float, float]):
+ def beam_center(self, data: tuple[float, float]):
self._client.set(f"{self._bl}:beam_center_x", data[0])
self._client.set(f"{self._bl}:beam_center_y", data[1])
@@ -721,7 +719,7 @@ class BeamlineConfig:
data_dict = json.loads(tmp)
return SampleShortInfoList(**data_dict)
- def spreadsheet_pgroup(self, pgroups: List[str]) -> SampleShortInfoList:
+ def spreadsheet_pgroup(self, pgroups: list[str]) -> SampleShortInfoList:
sample = self.spreadsheet
sample.s = list(filter(lambda x: x.user in pgroups, sample.s))
return sample
@@ -820,7 +818,7 @@ class BeamlineConfig:
def zoom_settings(self) -> ZoomModel:
mode = self.zoom_mode
if not mode or not isinstance(mode, ZoomModeEnum):
- raise Exception("incorrect zoom settings mode used")
+ raise ValueError("incorrect zoom settings mode used")
tmp = self._client.get(f"{self._bl}:{self.zoom_setting_string(mode)}")
if tmp is None:
return zoom_manager(mode, self._mxb)
@@ -831,7 +829,7 @@ class BeamlineConfig:
def zoom_settings(self, data: ZoomModel):
mode = self.zoom_mode
if not mode or not isinstance(mode, ZoomModeEnum):
- raise Exception("incorrect zoom settings mode used")
+ raise ValueError("incorrect zoom settings mode used")
self._client.set(f"{self._bl}:{self.zoom_setting_string(mode)}", data.model_dump_json())
def save_zoom_camera_setting(
@@ -1013,8 +1011,8 @@ class BeamlineConfig:
try:
data_dict = json.loads(tmp)
return SimpleScanParameters(**data_dict)
- except Exception as e:
- logger.error(f"failed to parse auto_params from redis: {e}; raw={tmp}")
+ except Exception:
+ logger.exception(f"failed to parse auto_params from redis; raw={tmp}")
return None
@auto_params.setter
@@ -1184,7 +1182,7 @@ class BeamlineConfig:
payload = json.loads(str(raw))
return payload if isinstance(payload, dict) else {}
except Exception as e:
- logger.warning(f"Failed to read detector metadata from Redis: {e}")
+ logger.warning(f"Failed to read detector metadata from Redis: {e}", exc_info=True)
return {}
def set_detector_metadata(self, payload: dict) -> dict:
@@ -1255,7 +1253,7 @@ class BeamlineConfig:
return LocalContactConfigModel.model_validate_json(str(raw_value))
except Exception as e:
- logger.warning(f"Failed to read Local Contact config from Redis: {e}")
+ logger.warning(f"Failed to read Local Contact config from Redis: {e}", exc_info=True)
return default
def set_local_contact_config(
diff --git a/src/aare/daq/daq.py b/src/aare/daq/daq.py
index 402b6bd9..bf6acedc 100644
--- a/src/aare/daq/daq.py
+++ b/src/aare/daq/daq.py
@@ -2,10 +2,10 @@ import copy
import json
import secrets
import time
-from datetime import datetime, timezone
+from collections.abc import Callable
+from datetime import UTC, datetime
from math import ceil
from pathlib import Path
-from typing import Callable, List, Optional, Tuple
from aarecommon.config.beamline import cfg_get
from aarecommon.config.logger import setup_logger
@@ -583,7 +583,7 @@ class AareDAQ:
parsed = json.loads(str(raw_value))
return parsed if isinstance(parsed, list) else []
except Exception as e:
- logger.debug(f"Failed to read tell_events from Redis: {e}")
+ logger.debug(f"Failed to read tell_events from Redis: {e}", exc_info=True)
return []
@staticmethod
@@ -609,11 +609,14 @@ class AareDAQ:
return True
if (
- tell_state.last_event_class == "Motion Sync"
- and tell_state.last_event_value == "Sample put on Puck"
+ (
+ tell_state.last_event_class == "Motion Sync"
+ and tell_state.last_event_value == "Sample put on Puck"
+ )
+ and state_ts is not None
+ and state_ts >= started_at
):
- if state_ts is not None and state_ts >= started_at:
- return True
+ return True
for event in reversed(self._get_tell_events_from_redis()):
if event.get("class") == "Motion Sync" and event.get("event") == "Sample put on Puck":
@@ -637,7 +640,7 @@ class AareDAQ:
try:
self._face_detection_progress_cb(payload)
except Exception as e:
- logger.warning(f"Failed to emit face detection progress: {e}")
+ logger.warning(f"Failed to emit face detection progress: {e}", exc_info=True)
def _emit_automation_progress(self, progress: AutomationProgress) -> None:
if self._automation_progress_cb is None:
@@ -645,7 +648,7 @@ class AareDAQ:
try:
self._automation_progress_cb(progress)
except Exception as e:
- logger.warning(f"Failed to emit automation progress: {e}")
+ logger.warning(f"Failed to emit automation progress: {e}", exc_info=True)
def _record_best_effort_step_failure(
self,
@@ -943,10 +946,10 @@ class AareDAQ:
def _handle_operation_error(
self,
operation: DAQOperation,
- sample: Optional[SampleShortInfo],
+ sample: SampleShortInfo | None,
error: Exception,
event_type: SampleEventType = SampleEventType.FAILED,
- additional_comment: Optional[str] = None,
+ additional_comment: str | None = None,
) -> None:
"""
Centralized databse maessage error handling for all operations.
@@ -1006,7 +1009,7 @@ class AareDAQ:
True if successful, False otherwise
"""
previous_sample = None
- mount_started_at = datetime.now(timezone.utc)
+ mount_started_at = datetime.now(UTC)
self._last_mount_error_message = ""
try:
@@ -1018,7 +1021,8 @@ class AareDAQ:
)
except Exception as sync_error:
logger.warning(
- f"Failed to reconcile previous sample from TELL before mount: {sync_error}"
+ f"Failed to reconcile previous sample from TELL before mount: {sync_error}",
+ exc_info=True,
)
if previous_sample is not None and previous_sample.db_id is not None:
@@ -1079,7 +1083,7 @@ class AareDAQ:
except Exception as e:
self._last_mount_error_message = str(e) or "Mount failed"
- logger.error(f"Mount failed: {e}")
+ logger.exception("Mount failed")
previous_sample_unmounted = (
previous_sample is not None
@@ -1151,7 +1155,7 @@ class AareDAQ:
return True
except Exception as e:
- logger.error(f"Loop centering failed: {e}")
+ logger.exception("Loop centering failed")
if result is not None and result.error is not None:
additional_comment = result.comment if result.comment is not None else ""
self._handle_operation_error(
@@ -1169,7 +1173,7 @@ class AareDAQ:
step_size: int = 15,
face_min_ratio: float = 0.3,
report_error: bool = True,
- sample: Optional[SampleShortInfo] = None,
+ sample: SampleShortInfo | None = None,
) -> FaceDetectionResult:
"""
Execute face detection sequence through the face detection service.
@@ -1186,7 +1190,7 @@ class AareDAQ:
sample = self.sample
logger.debug(f"No sample provided, using current sample from DAQ {sample}")
except Exception:
- logger.error("Failed to get current sample")
+ logger.exception("Failed to get current sample")
sample = None
aare = getattr(self, "_aare", None)
@@ -1213,12 +1217,13 @@ class AareDAQ:
return result
except Exception as e:
- logger.error(f"Face detection failed: {e}")
+ logger.exception("Face detection failed")
additional_comment = f"{e}" if e is not None else ""
try:
sample = self.sample
except Exception:
+ logger.debug("Could not read the current sample for face detection", exc_info=True)
sample = None
if report_error:
@@ -1448,7 +1453,7 @@ class AareDAQ:
f.flush()
except Exception as e:
- logger.warning(f"Failed to append smargon trace: {e}")
+ logger.warning(f"Failed to append smargon trace: {e}", exc_info=True)
def _sample_matches_mounted_address(
self, sample: SampleShortInfo | None, mounted_address
@@ -1555,7 +1560,7 @@ class AareDAQ:
"Cannot explicitly move to busy state",
extra={"target": target, "state": self._cfg.state},
)
- raise Exception("Cannot explicitly move to busy state")
+ raise RuntimeError("Cannot explicitly move to busy state")
start = time.perf_counter()
self._cfg.try_set_busy(timeout=300)
@@ -1583,7 +1588,7 @@ class AareDAQ:
# name = clean_filename(name)
return name
- def spreadsheet_params(self) -> tuple[Optional[SimpleScanParameters], str | None]:
+ def spreadsheet_params(self) -> tuple[SimpleScanParameters | None, str | None]:
file_prefix = None
if self.status.sample is None:
@@ -1622,8 +1627,7 @@ class AareDAQ:
new_res = 1 / ((1 / res) + 0.1)
corrected_dtz = self.diffraction_geometry.calc_dtz_mm(new_res)
logger.debug(f"corrected dtz: {corrected_dtz}")
- if corrected_dtz < 108:
- corrected_dtz = 108
+ corrected_dtz = max(corrected_dtz, 108)
params.dtz = round(corrected_dtz)
osc = getattr(aaredb_params, "oscillation", None)
@@ -1655,10 +1659,9 @@ class AareDAQ:
try:
self._devs.aerotech_omega = val
self._cfg.state_busy = False
- except Exception as e:
- logger.error(f"Omega error: {e}")
+ except Exception:
+ logger.exception("Omega move timed out")
self._cfg.state_busy = False
- logger.error("Omega move timed out")
else:
self._cfg.state_busy = False
logger.error("Omega has to be between -2000 and 2000 degrees")
@@ -1801,7 +1804,7 @@ class AareDAQ:
curr_sample = self._cfg.current_sample
if curr_sample is not None and curr_sample.location is not None:
- raise Exception("Sample from TELL is loaded")
+ raise RuntimeError("Sample from TELL is loaded")
self._aare.create_manual_sample(target)
self._cfg.current_sample = target
@@ -1834,8 +1837,8 @@ class AareDAQ:
def tell_toggle_blower(self):
try:
self._devs.tell.toggle_blower()
- except Exception as e:
- logger.error(f"Failed to turn off blower: {e}")
+ except Exception:
+ logger.exception("Failed to turn off blower")
def initialise_smargon(self):
self._cfg.try_set_busy(timeout=360)
@@ -1900,7 +1903,7 @@ class AareDAQ:
logger.debug(f"Failed to change mounted sample: {e}")
raise
- def list_loaded_pucks(self) -> List[PuckLoadedInfo]:
+ def list_loaded_pucks(self) -> list[PuckLoadedInfo]:
return []
def _auto_focus(self, settings: AutofocusSettings, settle_time_s: float = 1.0) -> float:
@@ -2014,14 +2017,13 @@ class AareDAQ:
)
self._devs.smargon_wait(timeout=180)
- return
def _build_fake_rotation_result(self, request: RotationScanRequest) -> CompletedRotationScan:
start_angle = 0.0
try:
start_angle = float(self.omega)
except Exception:
- pass
+ logger.debug("Could not read omega for the fake rotation result", exc_info=True)
return build_fake_rotation_result(request, start_angle=start_angle)
@@ -2157,10 +2159,8 @@ class AareDAQ:
try:
if self._cfg.state_busy and self._cfg.state != BeamlineStateEnum.Maintenance:
self._set_state(BeamlineStateEnum.SampleAlignment)
- except Exception as cleanup_error:
- logger.exception(
- f"Failed to restore SampleAlignment after rotation: {cleanup_error}"
- )
+ except Exception:
+ logger.exception("Failed to restore SampleAlignment after rotation")
finally:
self._cfg.state_busy = False
@@ -2222,7 +2222,6 @@ class AareDAQ:
except Exception:
self._cfg.state_busy = False
raise
- pass
def mark_beam(self, x_pxl: float, y_pxl: float):
self._cfg.set_busy(BeamlineStateEnum.BeamLocation)
@@ -2255,11 +2254,11 @@ class AareDAQ:
return sample_geom
@property
- def beam_center(self) -> Tuple[float, float]:
+ def beam_center(self) -> tuple[float, float]:
return self._cfg.beam_center
@beam_center.setter
- def beam_center(self, val: Tuple[float, float]):
+ def beam_center(self, val: tuple[float, float]):
self._cfg.beam_center = val
@property
@@ -2329,7 +2328,7 @@ class AareDAQ:
self._cfg.state_busy = False
@log_timing(logger, "Auto loop center")
- def auto_loop_center(self, sample: Optional[SampleShortInfo] = None) -> float:
+ def auto_loop_center(self, sample: SampleShortInfo | None = None) -> float:
"""
Automatically center the loop using ML-based detection.
This performs a multi-step sequence including rotation and centering.
@@ -2415,9 +2414,7 @@ class AareDAQ:
default_message=self._default_screenshot_message(sample.db_id),
)
- def send_message_db(
- self, db_id: int, event_type: SampleEventType, comment: Optional[str] = None
- ):
+ def send_message_db(self, db_id: int, event_type: SampleEventType, comment: str | None = None):
self._aare.send_sample_event(db_id, event_type, comment)
@property
@@ -2510,8 +2507,8 @@ class AareDAQ:
try:
params.dtz = self.diffraction_geometry.calc_dtz_mm(res)
logger.debug(f"dtz: {params.dtz}")
- except Exception as e:
- logger.error(f"Failed to calculate dtz for resolution {res}: {e}")
+ except Exception:
+ logger.exception(f"Failed to calculate dtz for resolution {res}")
# Adjust exposure time based on resolution
if res <= 1.5:
@@ -2524,7 +2521,7 @@ class AareDAQ:
return params
def get_collection_params(self, prefer_smart: bool = False) -> tuple[SimpleScanParameters, str]:
- spreadsheet_params, file_prefix = self.spreadsheet_params()
+ spreadsheet_params, _file_prefix = self.spreadsheet_params()
logger.debug(f"spreadsheet_params: {spreadsheet_params}")
smart_params = self._cfg.auto_params
default_params = SimpleScanParameters(exp_time_s=0.04, dtz=110, incr_omega_deg=0.2)
@@ -2544,7 +2541,7 @@ class AareDAQ:
def _end_operation(
self,
start: float,
- operation: Optional[DAQOperation] = DAQOperation.AUTOMATION,
+ operation: DAQOperation | None = DAQOperation.AUTOMATION,
error: bool = False,
) -> float:
"""
@@ -2603,9 +2600,7 @@ class AareDAQ:
self._emit_automation_progress(progress)
formatted_date = datetime.now().strftime("%Y%m%d")
- sample_prefix = "{}/{}/{:02d}/{}".format(
- formatted_date, sample.puck_name, sample.pin, sample.sample_name
- )
+ sample_prefix = f"{formatted_date}/{sample.puck_name}/{sample.pin:02d}/{sample.sample_name}"
try:
self._validate_automation_state(context="automation start")
@@ -2697,9 +2692,7 @@ class AareDAQ:
if raster_params.filename is not None:
logger.info(f"Using filename {raster_params.filename}")
- sample_prefix = "{filename}/{prefix}".format(
- filename=raster_params.filename, prefix=sample.sample_name
- )
+ sample_prefix = f"{raster_params.filename}/{sample.sample_name}"
raster_grid = RasterGridRequest(
exp_time_s=raster_params.exp_time_s,
@@ -2777,9 +2770,7 @@ class AareDAQ:
if params.filename is not None:
logger.info(f"Using filename {params.filename}")
- sample_prefix = "{filename}/{prefix}".format(
- filename=params.filename, prefix=sample.sample_name
- )
+ sample_prefix = f"{params.filename}/{sample.sample_name}"
rotation_request = RotationScanRequest(
start_omega_deg=start_omega,
@@ -2883,7 +2874,7 @@ class AareDAQ:
logger.error(f"Failed to mount sample: {e}")
if e.critical:
self._end_operation(start, operation=DAQOperation.AUTOMATION, error=True)
- raise e
+ raise
else:
pass
@@ -2909,7 +2900,7 @@ class AareDAQ:
event_type=SampleEventType.FAILED,
)
self._end_operation(start, operation=DAQOperation.AUTOMATION, error=True)
- raise Exception(f"Critical Error in automation: {e}") from e
+ raise RuntimeError(f"Critical Error in automation: {e}") from e
self._record_completed_sample_time(progress, time.time() - sample_started_at)
self._mark_progress_finished(progress, True, "Automation complete")
@@ -2937,7 +2928,7 @@ class AareDAQ:
)
if not self._cfg.state_busy:
- raise Exception("Beamline should be busy")
+ raise RuntimeError("Beamline should be busy")
if target == BeamlineStateEnum.Maintenance:
self._cfg.state = BeamlineStateEnum.Maintenance
@@ -3131,8 +3122,8 @@ class AareDAQ:
raise
except Exception as e:
logger.exception(
- f"Exception during state transition : {curr_state} -> {target}."
- f"Changing state to maintenance due to error: {e}",
+ f"Exception during state transition : {curr_state} -> {target}. "
+ "Changing state to maintenance due to error.",
extra={"from_state": curr_state, "to_state": target},
)
self._cfg.state = BeamlineStateEnum.Maintenance
@@ -3175,7 +3166,8 @@ class AareDAQ:
)
except Exception as e:
logger.warning(
- f"Falling back to default diffraction geometry because cached detector metadata is unavailable: {e}"
+ f"Falling back to default diffraction geometry because cached detector metadata is unavailable: {e}",
+ exc_info=True,
)
energy = self._devs.energy_kev
dtz = self._devs.dtz
@@ -3246,8 +3238,8 @@ class AareDAQ:
pss_alarm=pss_alarm,
)
- except Exception as e:
- logger.exception(f"Failed to retrieve beamline status: {e}")
+ except Exception:
+ logger.exception("Failed to retrieve beamline status")
raise
def _safe_sample(self) -> tuple[SampleShortInfo | None, bool, str | None]:
@@ -3259,7 +3251,7 @@ class AareDAQ:
except TellCommunicationError as e:
return self._cfg.current_sample, False, str(e)
except Exception as e:
- logger.warning(f"error in status sample info call: {e}")
+ logger.warning(f"error in status sample info call: {e}", exc_info=True)
# Keep status flowing even if Tell code throws something unexpected
return self._cfg.current_sample, False, f"TELL unavailable: {e}"
@@ -3269,7 +3261,7 @@ class AareDAQ:
try:
_ = self._devs.aerotech.status()
except Exception as e:
- logger.warning(f"Aerotech error in _aerotech_status: {e}")
+ logger.warning(f"Aerotech error in _aerotech_status: {e}", exc_info=True)
aerotech_ok = False
aerotech_err = f"Cannot connect to Aerotech: {e}"
return aerotech_ok, aerotech_err
@@ -3299,7 +3291,7 @@ class AareDAQ:
self._devs.exp_shutter.close()
aerotech_connected = False
except Exception as e:
- logger.warning(f"Unexpected error in _safe_geom: {e}")
+ logger.warning(f"Unexpected error in _safe_geom: {e}", exc_info=True)
smargon_error = f"Safe geometry failed: {e}"
aerotech_error = f"Safe geometry failed: {e}"
aerotech_connected = False
@@ -3324,7 +3316,7 @@ class AareDAQ:
return self.beamline_status
except Exception as e:
# TODO add error message to send to GUI to say problem
- logger.error(f"Failed to retrieve beamline status: {str(e)}")
+ logger.error(f"Failed to retrieve beamline status: {e!s}")
raise
def _safe_tell_state(self) -> TellStateModel | None:
@@ -3345,14 +3337,14 @@ class AareDAQ:
return TellStateModel.model_validate_json(str(raw_value))
except Exception as e:
- logger.warning(f"Failed to read tell_state from Redis: {e}")
+ logger.warning(f"Failed to read tell_state from Redis: {e}", exc_info=True)
return None
def _safe_diffraction_geometry(self) -> DiffractionGeometry:
try:
return self.diffraction_geometry
except Exception as e:
- logger.warning(f"Failed to retrieve diffraction geomtrey: {str(e)}")
+ logger.warning(f"Failed to retrieve diffraction geomtrey: {e!s}", exc_info=True)
# Must satisfy pydantic constraints in DiffractionGeometry
return DiffractionGeometry(
energy_keV=12.4,
diff --git a/src/aare/daq/devices.py b/src/aare/daq/devices.py
index d3ee4055..252d5121 100644
--- a/src/aare/daq/devices.py
+++ b/src/aare/daq/devices.py
@@ -115,7 +115,9 @@ class BeamlineDevices:
try:
self.bec_worker.shutdown_client()
except Exception as e:
- logger.warning(f"Failed to shutdown previous BEC worker cleanly: {e}")
+ logger.warning(
+ f"Failed to shutdown previous BEC worker cleanly: {e}", exc_info=True
+ )
finally:
beamline = MXBeamline.SIMULATED if simulated else self._beamline
logger.info(f"Restarting BEC worker with simulated={simulated}")
diff --git a/src/aare/daq/mlbox.py b/src/aare/daq/mlbox.py
index 7fdb30fc..33621bc3 100644
--- a/src/aare/daq/mlbox.py
+++ b/src/aare/daq/mlbox.py
@@ -1,6 +1,6 @@
import time
+from collections.abc import Iterable
from dataclasses import dataclass, field
-from typing import Iterable, Optional
import cv2
import numpy as np
@@ -92,7 +92,7 @@ class MlBox:
return None
return image
except Exception as e:
- logger.warning(f"Failed to decode prediction bundle image: {e}")
+ logger.warning(f"Failed to decode prediction bundle image: {e}", exc_info=True)
return None
@staticmethod
@@ -106,7 +106,7 @@ class MlBox:
return LatestPredictionModel.model_validate(metadata)
return LatestPredictionModel.model_validate(metadata.model_dump())
except Exception as e:
- logger.warning(f"Failed to validate prediction metadata: {e}")
+ logger.warning(f"Failed to validate prediction metadata: {e}", exc_info=True)
return None
@staticmethod
@@ -126,7 +126,8 @@ class MlBox:
except Exception as e:
last_error = e
logger.warning(
- f"Prediction bundle fetch failed on attempt {attempt}/{self.RETRY_COUNT}: {e}"
+ f"Prediction bundle fetch failed on attempt {attempt}/{self.RETRY_COUNT}: {e}",
+ exc_info=True,
)
if attempt < self.RETRY_COUNT:
time.sleep(self.RETRY_SLEEP_S)
@@ -156,7 +157,9 @@ class MlBox:
if isinstance(raw, (list, tuple)) and len(raw) >= 2:
return float(raw[0]), float(raw[1])
except Exception as e:
- logger.warning(f"Failed to parse target_point from prediction metadata: {e}")
+ logger.warning(
+ f"Failed to parse target_point from prediction metadata: {e}", exc_info=True
+ )
return None
@@ -171,7 +174,9 @@ class MlBox:
if raw_focus is not None:
focus = float(raw_focus)
except Exception as e:
- logger.warning(f"Failed to parse focus_score from prediction metadata: {e}")
+ logger.warning(
+ f"Failed to parse focus_score from prediction metadata: {e}", exc_info=True
+ )
return MLBundleMeta(target_point=target_point, focus=focus)
@@ -308,15 +313,13 @@ class MlBox:
def box_filter_overlap(
self, model: MLBoxModel, pin: MLBoxModel, overlap_parameter: float = 0.5
) -> bool:
- if self._check_overlap(model, pin) >= overlap_parameter:
- return True
- return False
+ return self._check_overlap(model, pin) >= overlap_parameter
def _filter_predictions(
self,
predictions: MLOutputModel,
- overlap_with_pin: Optional[float] = None,
- confidence_min: Optional[float] = None,
+ overlap_with_pin: float | None = None,
+ confidence_min: float | None = None,
):
pin = predictions.get_best_for_class(MLBoxType.PIN)
@@ -330,14 +333,17 @@ class MlBox:
continue
if model.cls == MLBoxType.PIN:
continue
- if overlap_with_pin is not None and pin:
- if self._check_overlap(model, pin) >= overlap_with_pin:
- keys_to_remove.append(key)
+ if (
+ overlap_with_pin is not None
+ and pin
+ and self._check_overlap(model, pin) >= overlap_with_pin
+ ):
+ keys_to_remove.append(key)
for k in keys_to_remove:
predictions.boxes.pop(k, None)
@staticmethod
- def _best_by_class(results) -> Optional[MLOutputModel]:
+ def _best_by_class(results) -> MLOutputModel | None:
out = MLOutputModel()
for pred in results:
try:
@@ -350,7 +356,7 @@ class MlBox:
y2 = float(box.get("y2"))
except Exception as e:
- logger.debug(f"Failed to parse prediction: {e}")
+ logger.debug(f"Failed to parse prediction: {e}", exc_info=True)
continue
base_key = MLOutputModel.get_class_str(cls=cls)
@@ -375,7 +381,7 @@ class MlBox:
@staticmethod
def _all_from_prediction_model(
prediction: LatestPredictionModel | None,
- ) -> Optional[MLOutputModel]:
+ ) -> MLOutputModel | None:
if prediction is None or not getattr(prediction, "boxes", None):
return None
@@ -389,7 +395,7 @@ class MlBox:
x2 = float(det.x2)
y2 = float(det.y2)
except Exception as e:
- logger.debug(f"Failed to parse bundle detection: {e}")
+ logger.debug(f"Failed to parse bundle detection: {e}", exc_info=True)
continue
out.add_box(cls=cls, box_tuple=(x1, y1, x2, y2), conf=conf)
@@ -406,9 +412,9 @@ class MlBox:
@staticmethod
def get_preferred_class_box_with_confidence_threshold(
boxes: MLOutputModel,
- preferred_class: Optional[Iterable[int] | int | MLBoxType] = None,
+ preferred_class: Iterable[int] | int | MLBoxType | None = None,
loop_preference_margin: float = 0.1,
- ) -> Optional[MLBoxModel]:
+ ) -> MLBoxModel | None:
"""
Get best box, preferring loops over pin even if pin has higher confidence,
unless pin's confidence exceeds loops by the margin.
@@ -466,8 +472,8 @@ class MlBox:
@staticmethod
def get_preferred_class_box(
- boxes: MLOutputModel, preferred_class: Optional[Iterable[int] | int | MLBoxType] = None
- ) -> Optional[MLBoxModel]:
+ boxes: MLOutputModel, preferred_class: Iterable[int] | int | MLBoxType | None = None
+ ) -> MLBoxModel | None:
if preferred_class is None:
order = (MLBoxType.CRYSTAL, MLBoxType.LOOP_FACE, MLBoxType.LOOP_ALL, MLBoxType.PIN)
else:
@@ -525,8 +531,9 @@ class MlBox:
def predict_best_no_filter(
self, return_image: bool = False, return_bundle_meta: bool = False
) -> (
- Optional[MLOutputModel]
- | tuple[Optional[MLOutputModel], np.ndarray | None]
+ MLOutputModel
+ | None
+ | tuple[MLOutputModel | None, np.ndarray | None]
| MLBoxPredictionsResult
):
ml_bundle = self._collect_best_bundle()
@@ -553,8 +560,9 @@ class MlBox:
return_image: bool = False,
return_bundle_meta: bool = False,
) -> (
- Optional[MLOutputModel]
- | tuple[Optional[MLOutputModel], np.ndarray | None]
+ MLOutputModel
+ | None
+ | tuple[MLOutputModel | None, np.ndarray | None]
| MLBoxPredictionsResult
):
ml_bundle = self._collect_best_bundle()
@@ -630,7 +638,7 @@ class MlBox:
overlap_with_pin: float | None = None,
confidence_min: float | None = None,
return_image: bool = False,
- ) -> Optional[MLBoxModel] | tuple[Optional[MLBoxModel], np.ndarray | None]:
+ ) -> MLBoxModel | None | tuple[MLBoxModel | None, np.ndarray | None]:
"""
Request bounding boxes for the next N bundle fetches and return the best available box.
"""
@@ -659,7 +667,9 @@ class MlBox:
logger.debug(f"Frame {frame_idx + 1}/{n_frames}: no detection")
except Exception as e:
- logger.warning(f"Frame {frame_idx + 1}/{n_frames}: prediction error: {e}")
+ logger.warning(
+ f"Frame {frame_idx + 1}/{n_frames}: prediction error: {e}", exc_info=True
+ )
continue
if best_box is None:
diff --git a/src/aare/daq/operations/common/ml_bounding_box.py b/src/aare/daq/operations/common/ml_bounding_box.py
index be364a20..77188e2d 100644
--- a/src/aare/daq/operations/common/ml_bounding_box.py
+++ b/src/aare/daq/operations/common/ml_bounding_box.py
@@ -1,7 +1,7 @@
import time
+from collections.abc import Callable
from dataclasses import dataclass
from math import ceil, floor
-from typing import Callable
from aarecommon.config.beamline import cfg_get
from aarecommon.config.logger_events import (
@@ -73,8 +73,8 @@ def scale_auto_raster_grid(
physical_size_y_mm = n_y * grid_size.y
scale = (image_count / max_images) ** 0.5
- scaled_n_x = max(1, int(floor(n_x / scale)))
- scaled_n_y = max(1, int(floor(n_y / scale)))
+ scaled_n_x = max(1, floor(n_x / scale))
+ scaled_n_y = max(1, floor(n_y / scale))
while scaled_n_x * scaled_n_y > max_images:
if scaled_n_x >= scaled_n_y and scaled_n_x > 1:
@@ -201,9 +201,9 @@ def _box_to_raster_request(
frac_x = float(cfg_get("daq.auto_raster.grid_padding_fraction_x", 0.15))
frac_y_top = float(cfg_get("daq.auto_raster.grid_padding_fraction_y", 0.15))
frac_y_bottom = float(cfg_get("daq.auto_raster.grid_padding_fraction_y_bottom", frac_y_top))
- pad_x = max(1, int(ceil(frac_x * n_x)))
- pad_y_top = max(1, int(ceil(frac_y_top * n_y)))
- pad_y_bottom = max(1, int(ceil(frac_y_bottom * n_y)))
+ pad_x = max(1, ceil(frac_x * n_x))
+ pad_y_top = max(1, ceil(frac_y_top * n_y))
+ pad_y_bottom = max(1, ceil(frac_y_bottom * n_y))
x1 = x1 - pad_x * grid_size.x / geom.pixel_in_mm
y1 = y1 - pad_y_top * grid_size.y / geom.pixel_in_mm
n_x = n_x + 2 * pad_x
diff --git a/src/aare/daq/operations/face_detection/service.py b/src/aare/daq/operations/face_detection/service.py
index 8e91d7aa..d75776ed 100644
--- a/src/aare/daq/operations/face_detection/service.py
+++ b/src/aare/daq/operations/face_detection/service.py
@@ -210,7 +210,7 @@ class FaceDetectionService:
return FaceDetectionResult(success=True, payload=payload)
except Exception as e:
- self.logger.error(f"error in face detection sequence {e}")
+ self.logger.exception("error in face detection sequence")
payload = self._emit_empty_result()
return FaceDetectionResult(
success=False, payload=payload, error=e, comment="Face detection sequence failed"
diff --git a/src/aare/daq/operations/face_detection/utils.py b/src/aare/daq/operations/face_detection/utils.py
index fe127170..6aec307f 100644
--- a/src/aare/daq/operations/face_detection/utils.py
+++ b/src/aare/daq/operations/face_detection/utils.py
@@ -3,7 +3,6 @@ import math
import statistics
import time
import warnings
-from typing import Dict, List, Optional, Tuple
import numpy as np
from aarecommon.config.logger import setup_logger
@@ -13,7 +12,7 @@ logger = setup_logger("aareDAQ")
def box_height_from_tuple(box: tuple[float, float, float, float]) -> float:
- x1, y1, x2, y2 = box
+ _x1, y1, _x2, y2 = box
return abs(y2 - y1)
@@ -24,7 +23,7 @@ def box_area_from_tuple(box: tuple[float, float, float, float]) -> float:
def prepare_samples(
boxes_by_angle: dict[int, tuple[float, float, float, float]], area=False
-) -> List[Tuple[float, float]]:
+) -> list[tuple[float, float]]:
# angles in degrees -> (theta_rad, height)
samples = []
for deg, box in boxes_by_angle.items():
@@ -37,7 +36,7 @@ def cos_model(theta_deg: float | np.ndarray, A: float, B: float, phi_rad: float,
return A + B * np.cos(C * np.deg2rad(theta_deg) - phi_rad)
-def mad_filter(samples: List[Tuple[float, float]], k: float = 3.5) -> List[Tuple[float, float]]:
+def mad_filter(samples: list[tuple[float, float]], k: float = 3.5) -> list[tuple[float, float]]:
if not samples:
return samples
ys = [y for _, y in samples]
@@ -54,7 +53,6 @@ def samples_to_json(samples):
}
with open("cos_test.json", "w") as f:
json.dump(output_data, f, indent=2)
- return
def fit_metrics(y_true: np.ndarray, y_pred: np.ndarray) -> tuple[float, float, float]:
@@ -67,7 +65,7 @@ def fit_metrics(y_true: np.ndarray, y_pred: np.ndarray) -> tuple[float, float, f
return rmse, mae, r2
-def fit_cosine(samples: List[Tuple[float, float]]) -> dict:
+def fit_cosine(samples: list[tuple[float, float]]) -> dict:
samples = mad_filter(samples, k=3.5)
if len(samples) < 3:
A = sum(y for _, y in samples) / max(1, len(samples))
@@ -84,6 +82,7 @@ def fit_cosine(samples: List[Tuple[float, float]]) -> dict:
beta, _, _, _ = np.linalg.lstsq(X, ys, rcond=None)
A0, C0, S0 = beta.tolist()
except Exception:
+ logger.debug("Cosine fit seeding failed; falling back to the mean", exc_info=True)
A0, C0, S0 = float(np.mean(ys)), 0.0, 0.0
B0 = float(math.hypot(C0, S0))
@@ -111,7 +110,7 @@ def fit_cosine(samples: List[Tuple[float, float]]) -> dict:
except Exception as e:
# Fallback to initial
- logger.info(f"error in curve fit {e}")
+ logger.info(f"error in curve fit {e}", exc_info=True)
yhat0 = cos_model(degs, A0, max(0.0, B0), phi0, C0)
rmse0, mae0, r2_0 = fit_metrics(ys, yhat0)
return {
@@ -135,11 +134,9 @@ def get_samples_out(boxes):
return samples_out
-def choose_best_fit(
- fits_by_name: Dict[str, Dict],
-) -> Tuple[Optional[float], Optional[Dict], Optional[str]]:
+def choose_best_fit(fits_by_name: dict[str, dict]) -> tuple[float | None, dict | None, str | None]:
- def key(entry: Dict):
+ def key(entry: dict):
params = entry.get("params") or {}
rmse = params.get("rmse")
mae = params.get("mae")
@@ -152,6 +149,7 @@ def choose_best_fit(
logger.info(f"rmse: {rmse}, mae: {mae}, r2: {r2}")
return float(rmse), float(mae), -float(r2)
except Exception:
+ logger.debug("Could not score a candidate fit", exc_info=True)
return None
best_name = None
diff --git a/src/aare/daq/operations/loop_centering/__init__.py b/src/aare/daq/operations/loop_centering/__init__.py
index 2026c242..f6d0c2be 100644
--- a/src/aare/daq/operations/loop_centering/__init__.py
+++ b/src/aare/daq/operations/loop_centering/__init__.py
@@ -1,17 +1,17 @@
+from aare.daq.operations.loop_centering.analyzer import LoopCenteringAnalyzer
from aare.daq.operations.loop_centering.models import (
- LoopCenteringContext,
AngleAnalysis,
AttemptSummary,
+ LoopCenteringContext,
LoopCenteringSettings,
)
from aare.daq.operations.loop_centering.service import LoopCenteringService
-from aare.daq.operations.loop_centering.analyzer import LoopCenteringAnalyzer
__all__ = [
- "LoopCenteringContext",
"AngleAnalysis",
"AttemptSummary",
- "LoopCenteringSettings",
- "LoopCenteringService",
"LoopCenteringAnalyzer",
+ "LoopCenteringContext",
+ "LoopCenteringService",
+ "LoopCenteringSettings",
]
diff --git a/src/aare/daq/operations/loop_centering/service.py b/src/aare/daq/operations/loop_centering/service.py
index e1baeca0..e1f586c8 100644
--- a/src/aare/daq/operations/loop_centering/service.py
+++ b/src/aare/daq/operations/loop_centering/service.py
@@ -1,5 +1,4 @@
import time
-import traceback
from aarecommon.config.logger_events import log_duration
from aarecommon.errors.exception_handler import LoopCenteringFailed
@@ -203,7 +202,5 @@ class LoopCenteringService:
f"Needle: {found_classes_count.get(5, 0)}"
)
- self.logger.error(f"ALC exception: {alc_comment}")
- self.logger.error(traceback.format_exc())
- self.logger.error(f"Error in loop centering: {e}")
+ self.logger.exception(f"ALC exception: {alc_comment}")
return LoopCenteringResult(success=False, comment=alc_comment, error=e)
diff --git a/src/aare/daq/operations/mounting/service.py b/src/aare/daq/operations/mounting/service.py
index 13960895..1f11058c 100644
--- a/src/aare/daq/operations/mounting/service.py
+++ b/src/aare/daq/operations/mounting/service.py
@@ -43,7 +43,9 @@ class MountingService:
self.logger.error(
"Goniometer didn't reach position based on magnet position sensor readout"
)
- raise Exception("Goniometer is not in position based on magnet position sensor readout")
+ raise RuntimeError(
+ "Goniometer is not in position based on magnet position sensor readout"
+ )
def _handle_consecutive_mount_failure(self) -> None:
count = self.ctx.deps.cfg.increment_mount_failure_streak()
@@ -53,8 +55,8 @@ class MountingService:
self.logger.warning(f"Mount failed {count} times in a row; drying")
try:
self.dry(park=False)
- except Exception as e:
- self.logger.exception(f"Failed to dry after mount failure: {e}")
+ except Exception:
+ self.logger.exception("Failed to dry after mount failure")
if count >= STOP_AFTER_FAIL_COUNT:
self.logger.error(f"Mount failed {count} times in a row, stopping automation")
@@ -62,8 +64,8 @@ class MountingService:
try:
self._unmount_current_sample(timeout=60.0)
self.dry(park=True)
- except Exception as e:
- self.logger.exception(f"Failed to clean up after repeated mount failure: {e}")
+ except Exception:
+ self.logger.exception("Failed to clean up after repeated mount failure")
raise MountingFailed(
f"Mount failed {count} times in a row, stopping automation.", critical=True
@@ -235,7 +237,7 @@ class MountingService:
)
except Exception as e:
- self.logger.error(f"Mount failed: {e}")
+ self.logger.exception("Mount failed")
return MountingResult(
success=False,
mounted_sample=None,
diff --git a/src/aare/daq/operations/raster/__init__.py b/src/aare/daq/operations/raster/__init__.py
index 5f87f03c..06414f52 100644
--- a/src/aare/daq/operations/raster/__init__.py
+++ b/src/aare/daq/operations/raster/__init__.py
@@ -1,4 +1,4 @@
-from aare.daq.operations.raster.models import RasterContext, RasterBoundingBoxResult
+from aare.daq.operations.raster.models import RasterBoundingBoxResult, RasterContext
from aare.daq.operations.raster.service import RasterService
__all__ = ["RasterBoundingBoxResult", "RasterContext", "RasterService"]
diff --git a/src/aare/daq/operations/raster/service.py b/src/aare/daq/operations/raster/service.py
index 3d4e7c56..38708811 100644
--- a/src/aare/daq/operations/raster/service.py
+++ b/src/aare/daq/operations/raster/service.py
@@ -121,7 +121,9 @@ class RasterService:
if bits:
comment = f"Raster diffraction ({', '.join(bits)})"
except Exception:
- pass
+ self.logger.debug(
+ "Could not build the raster diffraction preview comment", exc_info=True
+ )
self.ctx.deps.aare.upload_jpg(sample_id, filename, diffraction_image, message=comment)
@@ -325,7 +327,7 @@ class RasterService:
padded_height_mm = (
box_height_pxl * geom.pixel_in_mm * (1.0 + 2.0 * y_padding_fraction_each_side)
)
- n_y = max(1, int(ceil(padded_height_mm / grid_size_mm.y)))
+ n_y = max(1, ceil(padded_height_mm / grid_size_mm.y))
self.logger.info(
"Computed second auto-center raster y size from ML box height",
extra=merge_log_context(
@@ -647,9 +649,9 @@ class RasterService:
request=copy.deepcopy(request), result=scan_result, centre_of_mass=com
)
- except Exception as e:
+ except Exception:
self.logger.exception(
- f"Failed during raster: {e}",
+ "Failed during raster",
extra=merge_log_context(
sample_log_context(self.ctx.sample),
raster_request_log_context(request),
@@ -661,7 +663,7 @@ class RasterService:
def execute_auto_center(self, request: RasterGridRequest) -> CompletedRasterGrid | None:
sample = self.ctx.sample
if sample is None:
- raise Exception("Sample must be mounted to auto center")
+ raise RuntimeError("Sample must be mounted to auto center")
old_prefix = request.file_prefix
geom = self.ctx.sample_geometry
diff --git a/src/aare/daq/server.py b/src/aare/daq/server.py
index 9a305cdf..e0f5f3e7 100644
--- a/src/aare/daq/server.py
+++ b/src/aare/daq/server.py
@@ -4,8 +4,9 @@ import importlib
import json
import os
import time
+from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
-from typing import AsyncGenerator, Optional
+from typing import Any, ClassVar
import uvicorn
from aarecommon.config.beamline import mx_beamline
@@ -84,7 +85,10 @@ _automation_progress_state_lock = asyncio.Lock()
class AareUvicornWorker(UvicornWorker):
# CONFIG_KWARGS merged last into uvicorn Config (uvicorn/workers.py:69) →
# keeps our access-log filter + proxy_headers under gunicorn.
- CONFIG_KWARGS = {"log_config": get_uvicorn_logging_config(), "proxy_headers": False}
+ CONFIG_KWARGS: ClassVar[dict[str, Any]] = {
+ "log_config": get_uvicorn_logging_config(),
+ "proxy_headers": False,
+ }
@asynccontextmanager
@@ -109,16 +113,16 @@ async def lifespan(application: FastAPI):
try:
cfg.reset_automation_progress()
except Exception as e:
- logger.warning(f"Failed to reset automation progress Redis keys: {e}")
+ logger.warning(f"Failed to reset automation progress Redis keys: {e}", exc_info=True)
try:
daq.refresh_detector_metadata_cache()
except Exception as e:
- logger.warning(f"Initial hardware metadata refresh failed: {e}")
+ logger.warning(f"Initial hardware metadata refresh failed: {e}", exc_info=True)
# ── Initial TELL sync ──
try:
daq.sync_current_sample_from_tell(force=True)
except Exception as e:
- logger.warning(f"Initial sample sync from TELL failed: {e}")
+ logger.warning(f"Initial sample sync from TELL failed: {e}", exc_info=True)
# ── Wire callbacks ──
daq.set_face_detection_progress_callback(_push_face_detection_progress)
daq.set_automation_progress_callback(_push_automation_progress)
@@ -184,6 +188,7 @@ def _sample_is_mounted() -> bool:
try:
return daq.sample is not None
except Exception:
+ logger.debug("Could not determine whether a sample is mounted", exc_info=True)
return False
@@ -199,7 +204,7 @@ def _push_face_detection_progress(payload: dict) -> None:
next_seq = int(_face_detection_state.get("seq", 0)) + 1
_face_detection_state = {"seq": next_seq, **payload}
except Exception as e:
- logger.warning(f"Failed to update face detection progress: {e}")
+ logger.warning(f"Failed to update face detection progress: {e}", exc_info=True)
def _get_automation_progress_state() -> dict:
@@ -209,7 +214,7 @@ def _get_automation_progress_state() -> dict:
try:
return cfg.get_automation_progress_state()
except Exception as e:
- logger.warning(f"Failed to read automation progress from Redis: {e}")
+ logger.warning(f"Failed to read automation progress from Redis: {e}", exc_info=True)
return {"seq": 0, "progress": None}
@@ -228,7 +233,9 @@ def _push_automation_progress(progress: AutomationProgress) -> None:
f"current_step={state.get('progress', {}).get('current_step')}"
)
except Exception as e:
- logger.warning(f"Failed to update automation progress: {type(e).__name__}: {e}")
+ logger.warning(
+ f"Failed to update automation progress: {type(e).__name__}: {e}", exc_info=True
+ )
async def face_detection_event_stream() -> AsyncGenerator[str, None]:
@@ -2289,8 +2296,8 @@ async def get_all_pgroups(token: str = Depends(oauth2_scheme)):
names.sort(key=lambda d: int(d[1:]))
_all_pgroups_cache[base_path] = (names, now)
return names
- except Exception as e:
- logger.error(f"Failed to list pgroups: {e}")
+ except Exception:
+ logger.exception("Failed to list pgroups")
return []
@@ -2450,7 +2457,7 @@ async def sse_fluorimeter(token: str = Depends(oauth2_scheme)):
daq.fluorimeter_start(erase=False)
except Exception:
# Ignore if already running or start not needed
- pass
+ logger.debug("Fluorimeter already running or start not needed", exc_info=True)
return StreamingResponse(
fluorimeter_stream(),
media_type="text/event-stream",
@@ -2487,7 +2494,7 @@ async def send_screenshot_db(
async def send_message_db(
db_id: int,
event_type: SampleEventType,
- comment: Optional[str] = None,
+ comment: str | None = None,
token: str = Depends(oauth2_scheme),
):
data = auth.parse_token(token)
diff --git a/src/aare/daq/server_exception_handler.py b/src/aare/daq/server_exception_handler.py
index 4aed5d8e..b78eed76 100644
--- a/src/aare/daq/server_exception_handler.py
+++ b/src/aare/daq/server_exception_handler.py
@@ -173,11 +173,10 @@ def register_exception_handlers(app) -> None:
)
body = _error_body(exc, code_override=code_override)
# TODO: migrate baton to its own error type
- if isinstance(exc, UserRightsException):
- if "do not hold the baton" in exc.message:
- return JSONResponse(
- status_code=status, content=body, headers=getattr(exc, "headers", None)
- )
+ if isinstance(exc, UserRightsException) and "do not hold the baton" in exc.message:
+ return JSONResponse(
+ status_code=status, content=body, headers=getattr(exc, "headers", None)
+ )
logger.warning(
"AareAuthError: %s",
body["message"],
diff --git a/src/aare/daq/spreadsheetupdater.py b/src/aare/daq/spreadsheetupdater.py
index 2c8a86e9..c499ab12 100644
--- a/src/aare/daq/spreadsheetupdater.py
+++ b/src/aare/daq/spreadsheetupdater.py
@@ -3,11 +3,14 @@ import os
import time
from aarecommon.config.beamline import mx_beamline
+from aarecommon.config.logger import setup_logger
from aarecommon.models.models import DewarAddress, SampleShortInfo, SampleShortInfoList
from aareDB.models import PuckWithTellPosition
from aare.daq.config import BeamlineConfig
+logger = setup_logger("aareDAQ")
+
beamline = mx_beamline()
config = BeamlineConfig(bl=beamline)
@@ -105,10 +108,10 @@ def on_message(ws, message):
config._client.delete(ref_key)
print("[REDIS][INFO] Cleared reference tools key:", ref_key)
except Exception:
- pass
+ logger.debug("Could not clear the reference tools key", exc_info=True)
- except Exception as exc:
- print("[WS][ERROR] Failed to parse or convert message:", exc)
+ except Exception:
+ logger.exception("[WS] Failed to parse or convert message")
def on_error(ws, error):
@@ -178,8 +181,8 @@ def main():
print(f"[WS][INFO] Connecting to {WS_URL}...")
ws.run_forever(sslopt=ssl_opt)
- except Exception as e:
- print(f"[MAIN][ERROR] WebSocket connection failed: {e}")
+ except Exception:
+ logger.exception("[MAIN] WebSocket connection failed")
time.sleep(5)
diff --git a/src/aare/daq/tell_state_machine.py b/src/aare/daq/tell_state_machine.py
index 2bbe432d..9de7e8d0 100644
--- a/src/aare/daq/tell_state_machine.py
+++ b/src/aare/daq/tell_state_machine.py
@@ -1,7 +1,7 @@
from __future__ import annotations
import re
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from aarecommon.models.tell import TellActivityEnum, TellPhaseEnum, TellStateModel
@@ -11,7 +11,7 @@ UNMOUNT_STATUS_RE = re.compile(r"^unmount:\s*")
def _utc_now_iso() -> str:
- return datetime.now(timezone.utc).isoformat()
+ return datetime.now(UTC).isoformat()
def initial_tell_state() -> TellStateModel:
@@ -248,11 +248,7 @@ def advance_tell_state(
if event_name == "Motion Sync":
if value == "Sample get from Gonio":
- next_phase = (
- TellPhaseEnum.RETURNING_OLD_SAMPLE
- if state.operation == "mount"
- else TellPhaseEnum.RETURNING_OLD_SAMPLE
- )
+ next_phase = TellPhaseEnum.RETURNING_OLD_SAMPLE
next_operation = "mount" if state.operation == "mount" else "unmount"
return _update_state(
state,
@@ -324,18 +320,21 @@ def advance_tell_state(
mount_error="",
)
- if event_name == "Gripper detection":
- if value in {"No Pin in Gripper", "Pin still in Gripper", "Pin is lost"}:
- return _update_state(
- state,
- activity=TellActivityEnum.ERROR,
- message=value,
- event_name=event_name,
- event_value=value,
- mount_success=False,
- mount_error=value,
- phase=TellPhaseEnum.FAILED,
- )
+ if event_name == "Gripper detection" and value in {
+ "No Pin in Gripper",
+ "Pin still in Gripper",
+ "Pin is lost",
+ }:
+ return _update_state(
+ state,
+ activity=TellActivityEnum.ERROR,
+ message=value,
+ event_name=event_name,
+ event_value=value,
+ mount_success=False,
+ mount_error=value,
+ phase=TellPhaseEnum.FAILED,
+ )
if event_name == "state":
if value == "Ready":
diff --git a/src/aare/daq/tellupdater.py b/src/aare/daq/tellupdater.py
index d459d203..d0ce3c06 100644
--- a/src/aare/daq/tellupdater.py
+++ b/src/aare/daq/tellupdater.py
@@ -4,7 +4,7 @@ import ssl
import threading
import time
from collections import deque
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from typing import Any, cast
import requests
@@ -180,7 +180,7 @@ def set_tell_state_in_redis(state: TellStateModel) -> None:
def record_tell_event(event_name, event_value):
event_record = TellEventRecord(
- timestamp=datetime.now(timezone.utc).isoformat(), class_=event_name, event=event_value
+ timestamp=datetime.now(UTC).isoformat(), class_=event_name, event=event_value
)
latest_tell_events[event_name] = event_value
diff --git a/src/aare/daq/workflows.py b/src/aare/daq/workflows.py
index 0b9a54ca..41cd0c48 100644
--- a/src/aare/daq/workflows.py
+++ b/src/aare/daq/workflows.py
@@ -266,8 +266,8 @@ def common2dh(devs: BeamlineDevices, cfg: BeamlineConfig):
if devs.tell.get_mounted_sample() is not None:
try:
devs.tell.unmount(wait=True)
- except Exception as e:
- print(f"Error for unmounting: {e}")
+ except Exception:
+ logger.exception("Error for unmounting")
devs.tell.dry(wait_cold=-1, wait=False)
diff --git a/src/aare/devices/aerotech.py b/src/aare/devices/aerotech.py
index 5970f5ad..afa95cfa 100644
--- a/src/aare/devices/aerotech.py
+++ b/src/aare/devices/aerotech.py
@@ -1,5 +1,3 @@
-from typing import Optional, Union
-
from aarecommon.config.beamline import cfg_get, mx_beamline
from aarecommon.config.logger import setup_logger
from aarecommon.errors.exception_handler import AerotechCommunicationError
@@ -23,7 +21,7 @@ AEROTECH_HOME = AerotechCoordinate(at_mm=Coordinate(x=0, y=0, z=0), omega_deg=0)
logger = setup_logger("aareDAQ")
-class AerotechController(object):
+class AerotechController:
def __init__(self, bl: MXBeamline):
if bl == MXBeamline.X06DA:
self._simulated = False
@@ -42,7 +40,7 @@ class AerotechController(object):
self._pos = AEROTECH_HOME
self._vel = 0
else:
- raise Exception("unknown beamline")
+ raise ValueError("unknown beamline")
if not self._simulated:
self._client = ApiClient(Configuration(host=self._base))
@@ -184,11 +182,7 @@ class AerotechController(object):
) from e
def rotation_scan(
- self,
- rotation_deg: float | int,
- time_sec: float | int,
- start_pos_deg: float | int,
- run_async: bool = False,
+ self, rotation_deg: float, time_sec: float, start_pos_deg: float, run_async: bool = False
):
payload = RotationRequest(
rotation_deg=rotation_deg,
@@ -212,11 +206,11 @@ class AerotechController(object):
def grid_scan(
self,
grid_elem_count_y: int,
- grid_elem_size_y_um: int | float,
- time_sec: int | float,
- grid_elem_size_x_um: Optional[Union[float, int]] = None,
- grid_elem_count_x: Optional[int] = None,
- run_async: Optional[bool] = False,
+ grid_elem_size_y_um: float,
+ time_sec: float,
+ grid_elem_size_x_um: float | None = None,
+ grid_elem_count_x: int | None = None,
+ run_async: bool | None = False,
):
payload = GridRequest(
grid_elem_count_x=grid_elem_count_x,
@@ -240,9 +234,9 @@ class AerotechController(object):
def screening_scan(
self,
- rotation_deg: float | int,
- wedge_deg: float | int,
- time_sec: float | int,
+ rotation_deg: float,
+ wedge_deg: float,
+ time_sec: float,
steps: int,
run_async: bool = False,
):
diff --git a/src/aare/devices/area_detector.py b/src/aare/devices/area_detector.py
index b5104ed2..74e67076 100644
--- a/src/aare/devices/area_detector.py
+++ b/src/aare/devices/area_detector.py
@@ -3,6 +3,9 @@ from enum import Enum
import epics
import numpy as np
+from aarecommon.config.logger import setup_logger
+
+logger = setup_logger("aareDAQ")
class AutoEnum(Enum):
@@ -21,7 +24,7 @@ class AutoExposureSettings:
exp_max: float = 30000.000
-class epicsAD(object):
+class epicsAD:
def __init__(self, prefix, cam="cam1:", image="image1:"):
self.img = None
self.monitored = False
@@ -61,11 +64,11 @@ class epicsAD(object):
try:
epics.ca.pend_io()
- except Exception as e:
- print(f"EPICS error: {e}")
- pass
+ except Exception:
+ logger.exception("EPICS error")
- def _init_auto_exp(self, settings: AutoExposureSettings = AutoExposureSettings()):
+ def _init_auto_exp(self, settings: AutoExposureSettings | None = None):
+ settings = settings or AutoExposureSettings()
self.acquire.put(0)
self.aoi_start_x.put(settings.aoi_offset_x)
self.aoi_start_y.put(settings.aoi_offset_y)
diff --git a/src/aare/devices/bec_worker.py b/src/aare/devices/bec_worker.py
index f6649bbe..ac652e36 100644
--- a/src/aare/devices/bec_worker.py
+++ b/src/aare/devices/bec_worker.py
@@ -1,6 +1,6 @@
+import sys
import time
from enum import Enum
-from typing import List, Optional
from aarecommon.config.beamline import cfg_get, mx_beamline
from aarecommon.config.logger import setup_logger
@@ -90,9 +90,9 @@ class BECClientWorker:
self._set_scilog_tags()
try:
self._init_beamline_environment()
- except Exception as e:
- logger.error(f"Error initialising BEC devices: {e}")
- exit(1)
+ except Exception:
+ logger.exception("Error initialising BEC devices")
+ sys.exit(1)
logger.debug(f"simulated is {self.simulated}")
def _init_beamline_environment(self):
@@ -112,14 +112,15 @@ class BECClientWorker:
self._zoom = self.dev.scam_zoom
self._ring_current = self.dev.sls_current
except Exception as e:
- logger.error(f"Error initialising zoom and ring_current: {e}")
+ logger.exception("Error initialising zoom and ring_current")
self._zoom = None
self.ring_current = None
- raise Exception(f"Error initialising BEC devices: {e}")
+ raise RuntimeError(f"Error initialising BEC devices: {e}") from e
- def _raise_bec_error(
- self, exc: Exception, *, operation: str, tags: Optional[List[str]] = None
- ) -> None:
+ def _bec_error(
+ self, exc: Exception, *, operation: str, tags: list[str] | None = None
+ ) -> BECCommunicationError:
+ """Report a failed BEC operation and build the error; callers ``raise ... from`` it."""
message = f"BEC operation '{operation}' failed: {type(exc).__name__}: {exc}"
# logger.exception(message)
if tags is None:
@@ -134,8 +135,8 @@ class BECClientWorker:
last_alarm = self.client.show_last_alarm()
message += f"\n\nAlarm: {last_alarm}\n\n"
logger.error(f"last_alarm: {last_alarm}")
- except Exception as e:
- logger.error(f"Couldn't raise BEC alarms: {e}")
+ except Exception:
+ logger.exception("Couldn't raise BEC alarms")
try:
self.scilog_msg(
@@ -144,40 +145,40 @@ class BECClientWorker:
error_message=f"Error during '{operation}': {exc}",
tags=tags,
)
- except Exception as e:
- logger.error(f"Error sending scilog message: {e}")
+ except Exception:
+ logger.exception("Error sending scilog message")
if isinstance(exc, AssertionError):
- raise BECCommunicationError(
+ return BECCommunicationError(
f"BEC internal assertion failed during '{operation}'",
operation=operation,
exception=exc,
- ) from exc
+ )
- raise BECCommunicationError(message, operation=operation, exception=exc) from exc
+ return BECCommunicationError(message, operation=operation, exception=exc)
- def _set_scilog_tags(self, tags: Optional[List[str]] = None):
+ def _set_scilog_tags(self, tags: list[str] | None = None):
try:
if tags:
self.client.messaging.scilog.set_default_tags(tags)
else:
self.client.messaging.scilog.set_default_tags(["AareDAQ"])
except Exception as e:
- logger.error(f"Error setting scilog tags: {e}")
- self._raise_bec_error(e, operation="set scilog tags")
+ logger.exception("Error setting scilog tags")
+ raise self._bec_error(e, operation="set scilog tags") from e
def scilog_msg(
self,
message: str,
error: bool = False,
warning: bool = False,
- error_message: Optional[str] = None,
+ error_message: str | None = None,
attachments=None,
bold: bool = False,
italic: bool = False,
- color: Optional[str] = None,
- additonal_text: Optional[List[str]] = None,
- tags: Optional[List[str]] = None,
+ color: str | None = None,
+ additonal_text: list[str] | None = None,
+ tags: list[str] | None = None,
):
if color and color not in ["red", "green", "yellow", "blue", "pink"]:
logger.warning("specified color not in allowed list,using default")
@@ -185,7 +186,7 @@ class BECClientWorker:
try:
msg = self.client.messaging.scilog.new()
except Exception as e:
- self._raise_bec_error(e, operation="scilog_msg")
+ raise self._bec_error(e, operation="scilog_msg") from e
try:
msg.add_text(message, bold=bold, italic=italic, color=color)
if error:
@@ -193,32 +194,32 @@ class BECClientWorker:
elif warning:
msg.add_text(error_message, bold=True, color="yellow")
except Exception as e:
- logger.error(f"Error adding text: {e}")
+ logger.exception("Error adding text")
msg.add_text(f"Error adding text: {e}")
try:
if attachments:
for attachment in attachments:
msg.add_attachment(attachment)
except Exception as e:
- logger.error(f"Error adding attachment: {e}")
+ logger.exception("Error adding attachment")
msg.add_text(f"Error adding attachment: {e}")
try:
if additonal_text:
for text in additonal_text:
msg.add_text(text)
except Exception as e:
- logger.error(f"Error adding additional text: {e}")
+ logger.exception("Error adding additional text")
msg.add_text(f"Error adding additional text: {e}")
try:
if tags:
msg.add_tags(tags)
except Exception as e:
- logger.error(f"Error setting scilog tags: {e}")
+ logger.exception("Error setting scilog tags")
msg.add_text(f"Error setting scilog tags: {e}")
try:
msg.send()
- except Exception as e:
- logger.error(f"Error sending scilog message: {e}")
+ except Exception:
+ logger.exception("Error sending scilog message")
def run_macro(self, macro_name: str, *args, queue: str = "default", **kwargs):
if self.simulated:
@@ -227,7 +228,7 @@ class BECClientWorker:
try:
return self.client.proc.run_macro(macro_name, *args, queue=queue)
except Exception as e:
- self._raise_bec_error(e, operation=f"run_macro:{macro_name}")
+ raise self._bec_error(e, operation=f"run_macro:{macro_name}") from e
def run_macro_blocked(self, macro_name: str, *args, queue: str = "default", **kwargs):
if self.simulated:
@@ -240,7 +241,7 @@ class BECClientWorker:
print(status)
return status
except Exception as e:
- self._raise_bec_error(e, operation=f"run_macro_blocked:{macro_name}")
+ raise self._bec_error(e, operation=f"run_macro_blocked:{macro_name}") from e
@log_timing(logger, "BEC move_to")
def move_to(self, state: BeamlineState):
@@ -263,7 +264,7 @@ class BECClientWorker:
)
return False
except Exception as e:
- self._raise_bec_error(e, operation=f"planner.move_to:{state.value}")
+ raise self._bec_error(e, operation=f"planner.move_to:{state.value}") from e
def is_state(self, state: BeamlineState):
if self.simulated:
@@ -298,7 +299,7 @@ class BECClientWorker:
return []
return [str(macro) for macro in raw_macros]
except Exception as e:
- self._raise_bec_error(e, operation="list_all_user_macros")
+ raise self._bec_error(e, operation="list_all_user_macros") from e
def _list_all_macros(self):
result = self.client.macros.list_user_macros()
@@ -313,7 +314,7 @@ class BECClientWorker:
try:
return self._load_user_macros()
except Exception as e:
- self._raise_bec_error(e, operation="load_user_macros")
+ raise self._bec_error(e, operation="load_user_macros") from e
def _load_user_macros(self):
result = self.macros.load_all_user_macros()
@@ -345,9 +346,9 @@ class BECClientWorker:
logger.info(f"Reinitialised BEC planner and position devices using method={method}")
return self.list_position_devices()
except Exception as e:
- self._raise_bec_error(
+ raise self._bec_error(
e, operation=f"reinitialise_planner_and_position_devices:{method}"
- )
+ ) from e
def shutdown_client(self):
self.client.shutdown()
@@ -356,7 +357,7 @@ class BECClientWorker:
try:
self.macros.mono_pitch_scan(plot)
except Exception as e:
- self._raise_bec_error(e, operation="mono_pitch_scan", tags=["mono_pitch_scan"])
+ raise self._bec_error(e, operation="mono_pitch_scan", tags=["mono_pitch_scan"]) from e
if self.beamline is MXBeamline.X06DA:
addtional_text = [f"New dcm_pitch position: {self.dev.dcm_pitch.position:5f}"]
else:
@@ -375,18 +376,18 @@ class BECClientWorker:
energy_kev = energy_ev / 1000
return energy_kev
- def change_energy(self, value: float | int, plot: bool = False):
+ def change_energy(self, value: float, plot: bool = False):
current_energy = self.check_current_energy()
logger.info(f"Current energy: {current_energy:.1f} eV")
logger.info(f"Change energy requested: from {current_energy:.1f} to {value:.1f} eV")
try:
self.macros.bl_energy(value, move_gap=False, mono_scan=True, plot=plot)
except Exception as e:
- self._raise_bec_error(
+ raise self._bec_error(
e,
operation=f"Requested energy change from:{current_energy:.1f} to {value} eV",
tags=["energy_change"],
- )
+ ) from e
if abs(value - self.check_current_energy()) > 1:
logger.warning(
@@ -420,7 +421,7 @@ class BECClientWorker:
try:
return self.dev.det_z.position
except Exception as e:
- self._raise_bec_error(e, operation="get_det_z", tags=["det_z"])
+ raise self._bec_error(e, operation="get_det_z", tags=["det_z"]) from e
def det_z(self, value: float, timeout: int | None = None):
"""timeout is None or integer in s"""
@@ -430,13 +431,13 @@ class BECClientWorker:
status.wait(timeout=timeout)
return status
except Exception as e:
- self._raise_bec_error(e, operation=f"scans.mv:det_z:{value}", tags=["det_z"])
+ raise self._bec_error(e, operation=f"scans.mv:det_z:{value}", tags=["det_z"]) from e
def get_det_y(self):
try:
return self.dev.det_y.position
except Exception as e:
- self._raise_bec_error(e, operation="get_det_z", tags=["det_z"])
+ raise self._bec_error(e, operation="get_det_z", tags=["det_z"]) from e
def det_y(self, value: float, timeout: int | None = None):
"""timeout is None or integer in s"""
@@ -446,7 +447,7 @@ class BECClientWorker:
status.wait(timeout=timeout)
return status
except Exception as e:
- self._raise_bec_error(e, operation=f"scans.mv:det_y:{value}", tags=["det_z"])
+ raise self._bec_error(e, operation=f"scans.mv:det_y:{value}", tags=["det_z"]) from e
@property
def backlight_brightness(self) -> BrightnessEnum:
@@ -455,11 +456,11 @@ class BECClientWorker:
try:
return BrightnessEnum(self._backlight_brightness.actual)
except Exception as e:
- self._raise_bec_error(
+ raise self._bec_error(
e,
operation="backlight brightness, could not get backlight brightness",
tags=["backlight"],
- )
+ ) from e
raise
@backlight_brightness.setter
@@ -470,7 +471,9 @@ class BECClientWorker:
try:
self._backlight_brightness.move(value)
except Exception as e:
- self._raise_bec_error(e, operation=f"backlight_brightness:{value}", tags=["backlight"])
+ raise self._bec_error(
+ e, operation=f"backlight_brightness:{value}", tags=["backlight"]
+ ) from e
raise
def get_backlight_pos(self) -> BrightnessEnum:
@@ -489,11 +492,11 @@ class BECClientWorker:
target = BrightnessEnum.ON
self.backlight_brightness = target
except Exception as e:
- self._raise_bec_error(
+ raise self._bec_error(
e,
operation="backlight toggle, could not change backlight on/off ",
tags=["backlight"],
- )
+ ) from e
def save_current_bs_pos(self):
self.macros.save_current_position(self.dev.bs_z, "safe")
@@ -535,13 +538,12 @@ if __name__ == "__main__":
beamline = mx_beamline()
try:
client = BECClientWorker(beamline)
- except Exception as e:
- print(f"Error: {e}")
+ except Exception:
+ logger.exception("Failed to start BEC client")
try:
client.shutdown_client()
- except Exception as e:
- import sys
-
+ except Exception:
+ logger.exception("Failed to shut the BEC client down cleanly")
sys.exit(1)
# print(client.get_det_cov(actual=True))
# print(client.is_state(BeamlineState.ROBOT_SAMPLE_EXCHANGE))
@@ -581,10 +583,9 @@ if __name__ == "__main__":
# print(client.backlight_brightness)
# client.scilog_msg("Testing scilog messages with color = yellow and italic", italic=True,
# color="green", warning=False)
- except Exception as e:
- # client._raise_bec_error(e, operation="send message")
+ except Exception:
client.shutdown_client()
- print(f"Error: {e}")
+ logger.exception("BEC client smoke test failed")
# try:
# det_value = 980
diff --git a/src/aare/devices/enum_pv.py b/src/aare/devices/enum_pv.py
old mode 100755
new mode 100644
index f8219696..fd725e17
--- a/src/aare/devices/enum_pv.py
+++ b/src/aare/devices/enum_pv.py
@@ -1,7 +1,7 @@
from enum import Enum
from typing import Any
-from aare.devices.set_get_pv import SetGetPV, MoveResult
+from aare.devices.set_get_pv import MoveResult, SetGetPV
class EnumPV(SetGetPV):
diff --git a/src/aare/devices/fluorimeter.py b/src/aare/devices/fluorimeter.py
index 13faa3e5..4e6eb823 100644
--- a/src/aare/devices/fluorimeter.py
+++ b/src/aare/devices/fluorimeter.py
@@ -7,7 +7,7 @@ from epics import PV, poll
logger = setup_logger("aareaDAQ")
-class Fluorimeter(object):
+class Fluorimeter:
def __init__(self, beamline: MXBeamline, **kwargs):
BEAMLINE = beamline.value.upper()
@@ -108,8 +108,8 @@ class Fluorimeter(object):
raise ValueError(f"Invalid preset mode: {mode}")
try:
self._preset_mode.put(mode)
- except Exception as e:
- logger.error(f"Error setting preset mode to {mode}: {e}")
+ except Exception:
+ logger.exception(f"Error setting preset mode to {mode}")
def save_file(self, filename: str | None = None, timeout_s: float = 30.0):
if not filename:
@@ -122,8 +122,8 @@ class Fluorimeter(object):
if time.time() > timeout:
raise TimeoutError("Timed out waiting for save to complete")
poll(0.1)
- except Exception as e:
- logger.error(f"Error saving file {filename}: {e}")
+ except Exception:
+ logger.exception(f"Error saving file {filename}")
@property
def real_time(self):
diff --git a/src/aare/devices/jfjoch.py b/src/aare/devices/jfjoch.py
index d4ecf250..a0a7de36 100644
--- a/src/aare/devices/jfjoch.py
+++ b/src/aare/devices/jfjoch.py
@@ -4,6 +4,7 @@ from enum import Enum
import jfjoch_client
from aarecommon.config.beamline import get_jfjoch_url
+from aarecommon.config.logger import setup_logger
from aarecommon.errors.exception_handler import JFJochCommunicationError
from aarecommon.models.beamline import MXBeamline
from aarecommon.models.models import DAQStatusModel, FluorescenceSpectrumOutputModel
@@ -12,6 +13,8 @@ from aarecommon.models.rotation_scan import RotationScanRequest
from jfjoch_client.api.default_api import DefaultApi
from jfjoch_client.api_client import ApiClient
+logger = setup_logger("aareDAQ")
+
class ScanTypeEnum(Enum):
RASTER = "Raster"
@@ -53,32 +56,33 @@ class JFJochWrapper:
return None
- def _raise_jfjoch_error(
+ def _jfjoch_error(
self, message: str, *, error: Exception, operation: str, endpoint: str
- ) -> None:
- raise JFJochCommunicationError(
+ ) -> JFJochCommunicationError:
+ """Build the error for a failed JFJoch call; callers ``raise ... from`` it."""
+ return JFJochCommunicationError(
message,
operation=operation,
endpoint=endpoint,
base_url=self._url,
status_code=self._extract_status_code(error),
- ) from error
+ )
def initialize(self):
try:
self._api.initialize_post()
except Exception as e:
- self._raise_jfjoch_error(
+ raise self._jfjoch_error(
"JFJoch initialize failed", error=e, operation="POST", endpoint="initialize_post"
- )
+ ) from e
def cancel(self):
try:
self._api.cancel_post()
except Exception as e:
- self._raise_jfjoch_error(
+ raise self._jfjoch_error(
"JFJoch cancel failed", error=e, operation="POST", endpoint="cancel_post"
- )
+ ) from e
def is_idle(self) -> bool:
status = self._api.status_get()
@@ -176,23 +180,22 @@ class JFJochWrapper:
)
dataset_settings.xray_fluorescence_spectrum = xrf
- if s.sample.aaredb_params:
- if s.sample.aaredb_params.unitcell:
- unit_cell_db = s.sample.aaredb_params.unitcell
- unit_cell_split = unit_cell_db.replace(",", " ").split()
- unit_cell_floats = [float(x) for x in unit_cell_split]
- unit_cell = jfjoch_client.UnitCell(
- a=unit_cell_floats[0],
- b=unit_cell_floats[1],
- c=unit_cell_floats[2],
- alpha=unit_cell_floats[3],
- beta=unit_cell_floats[4],
- gamma=unit_cell_floats[5],
- )
- dataset_settings.unit_cell = unit_cell
- if s.sample.aaredb_params.spacegroupnumber:
- space_group_number = s.sample.aaredb_params.spacegroupnumber
- dataset_settings.space_group_number = space_group_number
+ if s.sample.aaredb_params and s.sample.aaredb_params.unitcell:
+ unit_cell_db = s.sample.aaredb_params.unitcell
+ unit_cell_split = unit_cell_db.replace(",", " ").split()
+ unit_cell_floats = [float(x) for x in unit_cell_split]
+ unit_cell = jfjoch_client.UnitCell(
+ a=unit_cell_floats[0],
+ b=unit_cell_floats[1],
+ c=unit_cell_floats[2],
+ alpha=unit_cell_floats[3],
+ beta=unit_cell_floats[4],
+ gamma=unit_cell_floats[5],
+ )
+ dataset_settings.unit_cell = unit_cell
+ if s.sample.aaredb_params.spacegroupnumber:
+ space_group_number = s.sample.aaredb_params.spacegroupnumber
+ dataset_settings.space_group_number = space_group_number
return dataset_settings
@@ -208,12 +211,12 @@ class JFJochWrapper:
try:
self._api.start_post(dataset_settings=dataset_settings)
except Exception as e:
- self._raise_jfjoch_error(
+ raise self._jfjoch_error(
f"JFJoch data collection failed to initialize for {scan_type.value} scan with exception: {e}",
error=e,
operation="POST",
endpoint="start_post",
- )
+ ) from e
def measure_rotation(
self,
@@ -230,44 +233,44 @@ class JFJochWrapper:
def measure_raster(self, r: RasterGridRequest, s: DAQStatusModel, async_start: bool = True):
self._start_scan(ScanTypeEnum.RASTER, r, s, async_start=async_start)
- def wait_till_running(self, timeout: int | float = 60):
+ def wait_till_running(self, timeout: float = 60):
if self._simulated:
return None
try:
self._api.wait_until_running_post_with_http_info(timeout=math.ceil(timeout))
return True
except Exception as e:
- self._raise_jfjoch_error(
+ raise self._jfjoch_error(
f"JFJoch wait until running returned an exception: {e}",
error=e,
operation="POST",
endpoint="wait_until_running_post",
- )
+ ) from e
- def wait_till_done(self, timeout: int | float) -> jfjoch_client.models.ScanResult | None:
+ def wait_till_done(self, timeout: float) -> jfjoch_client.models.ScanResult | None:
if self._simulated:
return None
try:
self._api.wait_till_done_post_with_http_info(timeout=math.ceil(timeout))
return self._api.result_scan_get()
except Exception as e:
- self._raise_jfjoch_error(
+ raise self._jfjoch_error(
f"JFJoch wait till done retrieval returned an exception: {e}",
error=e,
operation="POST",
endpoint="wait_till_done_post / result_scan_get",
- )
+ ) from e
def detector(self) -> jfjoch_client.models.DetectorListElement:
try:
detector_list = self._api.config_select_detector_get()
except Exception as e:
- self._raise_jfjoch_error(
+ raise self._jfjoch_error(
f"JFJoch detector configuration retrieval failed: {e}",
error=e,
operation="GET",
endpoint="config_select_detector_get",
- )
+ ) from e
if len(detector_list.detectors) == 0:
raise JFJochCommunicationError(
@@ -281,12 +284,12 @@ class JFJochWrapper:
try:
return detector_list.detectors[detector_list.current_id]
except Exception as e:
- self._raise_jfjoch_error(
+ raise self._jfjoch_error(
"JFJoch returned an invalid selected detector entry",
error=e,
operation="GET",
endpoint="config_select_detector_get",
- )
+ ) from e
def take_pedestal(self):
raise NotImplementedError(
@@ -312,6 +315,7 @@ class JFJochWrapper:
show_beam_center=show_beam_center,
)
except Exception as e:
+ logger.debug("Diffraction image retrieval attempt failed; retrying", exc_info=True)
last_error = e
time.sleep(wait_between_retries_s)
raise last_error
diff --git a/src/aare/devices/mx_lib.py b/src/aare/devices/mx_lib.py
index 09bf2eab..c0807eda 100644
--- a/src/aare/devices/mx_lib.py
+++ b/src/aare/devices/mx_lib.py
@@ -1,9 +1,13 @@
import re
import time
-from typing import Callable, Union, Any
+from collections.abc import Callable
+from typing import Any
+from aarecommon.config.logger import setup_logger
from epics import PV, Motor, poll
+logger = setup_logger("aareDAQ")
+
def wait_for_movement_to_finish(*motors):
"""
@@ -21,12 +25,12 @@ def wait_for_movement_to_finish(*motors):
longest = 0.0
for motor in motors:
time_to_target = motor.readback / motor.slew_speed
- longest = time_to_target if time_to_target > longest else longest
+ longest = max(longest, time_to_target)
timeout = time.time() + 1.5 * longest
done = False
while not done and time.time() < timeout:
- done = all([m.done_moving for m in motors])
+ done = all(m.done_moving for m in motors)
if time.time() > timeout:
print("TIMEOUT waiting for motors to be done moving; current motor positions:")
@@ -102,7 +106,7 @@ def is_epics_type(pv: PV, pv_type: str) -> bool:
def wait_string_condition(
- pv: PV, target: Union[str, re.Pattern], *, timeout: float = 60.0, polling: float = 0.1
+ pv: PV, target: str | re.Pattern, *, timeout: float = 60.0, polling: float = 0.1
):
"""wait until an epics.PV of type string reaches target
:pv: epics.PV
@@ -209,11 +213,12 @@ def wait_motor_position(
raises: TimeoutError if a timeout occurs
"""
if not callable(tester):
- raise RuntimeError("argument 'tester' must be a function")
+ raise TypeError("argument 'tester' must be a function")
try:
move_time = abs(motor.drive - motor.readback) / motor.speed
except Exception:
+ logger.debug("Could not compute the motor move time; using the 1 s default", exc_info=True)
move_time = 1.0 # in case of unusual motor record
tout = move_time + time.time() + timeout
@@ -230,7 +235,7 @@ def wait_motor_position(
def wait_enum_condition(
- pv: PV, value: Union[str, int, re.Pattern], *, timeout: float = 60.0, polling=0.1
+ pv: PV, value: str | int | re.Pattern, *, timeout: float = 60.0, polling=0.1
):
"""wait until an epics.PV enum reaches value
pv: epics.PV
@@ -249,16 +254,16 @@ def wait_enum_condition(
if not (isinstance(pv, PV) and pv.type.lower().endswith("enum")):
raise AttributeError("argument 'pv' must be an epics.PV of type enum")
- if not (isinstance(value, str) or isinstance(value, int) or isinstance(value, re.Pattern)):
- raise AttributeError("argument 'value' must be either an int, str, or re.Pattern")
+ if not (isinstance(value, (str, int, re.Pattern))):
+ raise TypeError("argument 'value' must be either an int, str, or re.Pattern")
if type(value) is int:
- tester = lambda pv: value == pv.get() # noqa: E731
+ tester = lambda pv: value == pv.get()
elif type(value) is str:
- tester = lambda pv: str(value) == pv.get(as_string=True).lower() # noqa: E731
+ tester = lambda pv: str(value) == pv.get(as_string=True).lower()
value = str(value).lower() # it's already a str :-/
elif isinstance(value, re.Pattern):
- tester = lambda pv: value.match(pv.get(as_string=True)) # noqa: E731
+ tester = lambda pv: value.match(pv.get(as_string=True))
else:
raise AttributeError("argument 'value' must be either an int, str, or re.Pattern")
diff --git a/src/aare/devices/set_get_pv.py b/src/aare/devices/set_get_pv.py
index 79fea92d..fa849073 100644
--- a/src/aare/devices/set_get_pv.py
+++ b/src/aare/devices/set_get_pv.py
@@ -1,22 +1,24 @@
from __future__ import annotations
+from collections.abc import Callable, Mapping
from dataclasses import dataclass
-from typing import Any, Mapping, Optional, Union, Callable
+from typing import Any
from epics import PV
+
from aare.devices.mx_lib import pv_wait
-RawValue = Union[str, float, int]
-ResolverValue = Union[
- RawValue,
- tuple[Callable[..., RawValue], tuple[Any, ...]], # (func, args) pattern you already use
-]
+RawValue = str | float | int
+ResolverValue = (
+ RawValue
+ | tuple[Callable[..., RawValue], tuple[Any, ...]] # (func, args) pattern you already use
+)
@dataclass
class MoveResult:
target: RawValue
- name: Optional[str] = None
+ name: str | None = None
class SetGetPV:
diff --git a/src/aare/devices/smargon.py b/src/aare/devices/smargon.py
index e870e277..6fc9abad 100644
--- a/src/aare/devices/smargon.py
+++ b/src/aare/devices/smargon.py
@@ -15,7 +15,7 @@ class SmargonMode(Enum):
ERROR = 99
-class Smargon(object):
+class Smargon:
SMARGON_HOME = SmargonCoordinate(sh_mm=Coordinate(x=0, y=0, z=18), phi_deg=0, chi_deg=0)
AERO_HOME = AerotechCoordinate(x=0, y=0, z=0, omega=0)
@@ -31,7 +31,7 @@ class Smargon(object):
elif bl == MXBeamline.SIMULATED:
self._simulated = True
else:
- raise Exception("unknown beamline")
+ raise ValueError("unknown beamline")
self._pos = self.SMARGON_HOME
self._pos_aero = self.AERO_HOME
@@ -175,13 +175,13 @@ class Smargon(object):
target_string = ""
if coord.sh_mm is not None:
- target_string += "&SHX={:.5f}&SHY={:.5f}&SHZ={:.5f}".format(
- coord.sh_mm.x, coord.sh_mm.y, coord.sh_mm.z
+ target_string += (
+ f"&SHX={coord.sh_mm.x:.5f}&SHY={coord.sh_mm.y:.5f}&SHZ={coord.sh_mm.z:.5f}"
)
if coord.chi_deg is not None:
- target_string += "&CHI={:.5f}".format(coord.chi_deg)
+ target_string += f"&CHI={coord.chi_deg:.5f}"
if coord.phi_deg is not None:
- target_string += "&PHI={:.5f}".format(coord.phi_deg)
+ target_string += f"&PHI={coord.phi_deg:.5f}"
if target_string:
self.gonput(f"targetSCS?{target_string}")
@@ -203,11 +203,11 @@ class Smargon(object):
target_string = ""
if coord.at_mm is not None:
- target_string += "&GMX={:.5f}&GMY={:.5f}&GMZ={:.5f}".format(
- coord.at_mm.x, coord.at_mm.y, coord.at_mm.z
+ target_string += (
+ f"&GMX={coord.at_mm.x:.5f}&GMY={coord.at_mm.y:.5f}&GMZ={coord.at_mm.z:.5f}"
)
if coord.omega_deg is not None:
- target_string += "&GMU={:.5f}".format(coord.omega_deg)
+ target_string += f"&GMU={coord.omega_deg:.5f}"
if target_string:
self.gonput(f"targetAEROTECH?{target_string}")
diff --git a/src/aare/devices/tell_backend.py b/src/aare/devices/tell_backend.py
index 1ce95ff6..8b7f25c1 100644
--- a/src/aare/devices/tell_backend.py
+++ b/src/aare/devices/tell_backend.py
@@ -1,7 +1,8 @@
import json
import re
import time
-from typing import Any, Callable, Protocol
+from collections.abc import Callable
+from typing import Any, Protocol
from urllib.parse import urlparse
import requests
@@ -325,7 +326,7 @@ class SimTellBackend:
}
)
except Exception:
- logger.warning("Failed to load simulated samples info")
+ logger.warning("Failed to load simulated samples info", exc_info=True)
def abort(self) -> None:
self._state = "Ready"
diff --git a/src/aare/devices/tell_client.py b/src/aare/devices/tell_client.py
index cf9ad4d8..baccfd14 100644
--- a/src/aare/devices/tell_client.py
+++ b/src/aare/devices/tell_client.py
@@ -1,8 +1,8 @@
import ast
import json
import re
+from datetime import UTC
from enum import Enum
-from typing import List
from aarecommon.config.logger import setup_logger
from aarecommon.errors.exception_handler import (
@@ -94,7 +94,7 @@ class TellClient:
raise TellConnectionException("Tell reconnecting")
elif state == "Closing":
raise TellConnectionException("Tell is disconnecting")
- raise Exception("Invalid state: " + str(state))
+ raise RuntimeError("Invalid state: " + str(state))
def set_in_mount_position(self, value):
"""tells the robot that the beamlien is safe and to set the in mount position flag allowing mounting
@@ -142,6 +142,7 @@ class TellClient:
if not self.is_remote_mode():
reasons.append("TELL is not in remote mode")
except Exception as e:
+ logger.debug("Failed to check TELL remote mode", exc_info=True)
reasons.append(f"failed to check remote mode: {e}")
if reasons:
@@ -158,13 +159,13 @@ class TellClient:
except Exception as e:
raise TellCommunicationError(
f"Mount can't start: failed to check door status: {e}", operation="mount_precheck"
- )
+ ) from e
if not door_closed:
raise TellCommunicationError(
"Mount can't start: TELL doors are open", operation="mount_precheck"
)
- def set_samples_info(self, info: List[PuckWithTellPosition]):
+ def set_samples_info(self, info: list[PuckWithTellPosition]):
"""sets the samples in the robot dewar based on the given list of PuckWithTellPosition objects
and runs set_sample_info in the background"""
@@ -205,9 +206,8 @@ class TellClient:
result = self.get_result(self._last_cmd_id)
logger.debug(f"getting result for command {self._last_cmd_id}: {result}")
status = result["status"]
- if "completed" != status:
- if "removed" != status:
- raise MountingFailed(f"{msg} {result}")
+ if "completed" != status and "removed" != status:
+ raise MountingFailed(f"{msg} {result}")
return f"{msg} {result}"
def estimate_mounting_time(self, segment) -> int:
@@ -233,6 +233,7 @@ class TellClient:
needs_drying = mount_needs_drying + unmount_needs_drying
return needs_cooling * 30 + needs_drying * 120
except Exception:
+ logger.debug("Could not estimate the mounting time", exc_info=True)
return 0
def mount(
@@ -358,7 +359,7 @@ class TellClient:
except TellCommunicationError:
raise
except Exception as e:
- logger.error(f"Exception occurred: {e}")
+ logger.exception("Exception occurred")
raise TellCommunicationError(
message=f"Error during mount {segment}{puck}-{sample}: {e}", critical=True
)
@@ -452,7 +453,7 @@ class TellClient:
# return eval(status)
return ast.literal_eval(status)
- def get_detected_pucks(self) -> List[PuckLoadedInfo]:
+ def get_detected_pucks(self) -> list[PuckLoadedInfo]:
j = json.loads(self.backend.eval("get_pucks_info()&"))
output = []
@@ -474,6 +475,7 @@ class TellClient:
try:
offset = float(self.backend.eval("get_pin_offset()&"))
except Exception:
+ logger.debug("Could not read the pin offset; assuming 0", exc_info=True)
offset = 0.0
return offset
@@ -482,7 +484,7 @@ class TellClient:
return float(current)
def set_current(self, current: float) -> float:
- self.backend.eval("smart_magnet.set_current({:.1f})&".format(current))
+ self.backend.eval(f"smart_magnet.set_current({current:.1f})&")
current = self.backend.eval("smart_magnet.get_current_rb()&")
return float(current)
@@ -549,7 +551,7 @@ class TellClient:
raise SmartMagnetFaultException
except Exception as e:
logger.error(f"check_smart_magnet_mounted failed: {e}")
- raise e
+ raise
def make_tell_client(bl: MXBeamline) -> TellClient:
@@ -561,7 +563,7 @@ def make_tell_client(bl: MXBeamline) -> TellClient:
if __name__ == "__main__":
- from datetime import datetime, timezone
+ from datetime import datetime
from aarecommon.config.beamline import mx_beamline
@@ -574,8 +576,8 @@ if __name__ == "__main__":
ts = float(tell_client.get_setting("dry_timestamp"))
print("dry timestape: ", ts)
- past = datetime.fromtimestamp(ts, tz=timezone.utc)
- now = datetime.now(timezone.utc)
+ past = datetime.fromtimestamp(ts, tz=UTC)
+ now = datetime.now(UTC)
seconds_ago = int((now - past).total_seconds())
print(seconds_ago)
print("door closer :", tell_client.backend.eval("is_door_closed()&"))
diff --git a/src/aare/devices/workflow_tools.py b/src/aare/devices/workflow_tools.py
old mode 100755
new mode 100644
index 88cb8377..a75eea0b
--- a/src/aare/devices/workflow_tools.py
+++ b/src/aare/devices/workflow_tools.py
@@ -7,10 +7,10 @@ def wait_position(motor, target, tolerance=None, timeout=60.0):
position = motor.readback
if isinstance(position, float) and tolerance is None:
tst = "%f == %s"
- ltst = lambda x, y, z: x == y # noqa: E731
+ ltst = lambda x, y, z: x == y
elif isinstance(position, float) and tolerance is not None:
tst = "abs(%f - %f) < %f"
- ltst = lambda x, y, z: abs(x - y) < z # noqa: E731
+ ltst = lambda x, y, z: abs(x - y) < z
elif isinstance(position, (bytes, str)):
tst = "'%s' == '%s'"
if isinstance(position, bytes):
@@ -37,19 +37,11 @@ def wait_position(motor, target, tolerance=None, timeout=60.0):
n = n + 1
if n > 20:
n = 0
- print(
- "waiting_position test: %s (%s, %s, %s)"
- % (tst, str(motor.readback), str(target), str(tolerance))
- )
+ print(f"waiting_position test: {tst} ({motor.readback!s}, {target!s}, {tolerance!s})")
timeisup = timeout < time.time()
condition = ltst(motor.readback, target, tolerance)
if timeisup:
- msg = "Timeout when waiting for %s to reach %s with tolerance %s. Device was at: %s" % (
- motor,
- str(target),
- str(tolerance),
- str(motor.readback),
- )
+ msg = f"Timeout when waiting for {motor} to reach {target!s} with tolerance {tolerance!s}. Device was at: {motor.readback!s}"
print(msg)
raise RuntimeError(msg)
diff --git a/src/aare/devices/zmq_client.py b/src/aare/devices/zmq_client.py
old mode 100755
new mode 100644
index 2b4db0d9..da8d389b
--- a/src/aare/devices/zmq_client.py
+++ b/src/aare/devices/zmq_client.py
@@ -5,7 +5,6 @@ with fallback to area_detector if ZMQ is unavailable.
"""
import json
-from typing import Optional
import cv2
import numpy as np
@@ -43,12 +42,12 @@ class ZMQCameraClient:
self._simulated = True
self._zmq_url = None
else:
- raise Exception("unknown beamline")
+ raise ValueError("unknown beamline")
self._timeout_ms = timeout_ms
- self._context: Optional[zmq.Context] = None
- self._socket: Optional[zmq.Socket] = None
- self._last_image: Optional[np.ndarray] = None
+ self._context: zmq.Context | None = None
+ self._socket: zmq.Socket | None = None
+ self._last_image: np.ndarray | None = None
self._last_fetch_time: float = 0.0
self._connected = False
@@ -77,11 +76,11 @@ class ZMQCameraClient:
logger.debug(f"ZMQ camera connected to {self._zmq_url}")
return True
except Exception as e:
- logger.warning(f"Failed to connect ZMQ camera to {self._zmq_url}: {e}")
+ logger.warning(f"Failed to connect ZMQ camera to {self._zmq_url}: {e}", exc_info=True)
self._connected = False
return False
- def get_image(self, gray: bool = False) -> Optional[np.ndarray]:
+ def get_image(self, gray: bool = False) -> np.ndarray | None:
"""
Fetch the latest image from the ZMQ stream.
@@ -114,6 +113,7 @@ class ZMQCameraClient:
header = decoded
break
except Exception:
+ logger.debug("Skipping an unparsable ZMQ message part", exc_info=True)
continue
if header and header.get("type") == "uint8":
@@ -138,7 +138,7 @@ class ZMQCameraClient:
logger.debug("ZMQ camera: timeout waiting for frame")
return self._last_image
except Exception as e:
- logger.warning(f"ZMQ camera error: {e}")
+ logger.warning(f"ZMQ camera error: {e}", exc_info=True)
self._connected = False
return self._last_image
@@ -161,6 +161,7 @@ class ZMQCameraClient:
self._socket.setsockopt(zmq.RCVTIMEO, old_timeout)
return False
except Exception:
+ logger.debug("Camera ZMQ socket is not available", exc_info=True)
return False
def close(self):
@@ -169,13 +170,13 @@ class ZMQCameraClient:
try:
self._socket.close()
except Exception:
- pass
+ logger.debug("Error closing the camera ZMQ socket", exc_info=True)
self._socket = None
if self._context:
try:
self._context.term()
except Exception:
- pass
+ logger.debug("Error terminating the camera ZMQ context", exc_info=True)
self._context = None
self._connected = False
diff --git a/src/aare/gui/about.py b/src/aare/gui/about.py
index 0119fa04..8152fdee 100644
--- a/src/aare/gui/about.py
+++ b/src/aare/gui/about.py
@@ -4,16 +4,23 @@ import importlib
import sys
from typing import TYPE_CHECKING
+from aarecommon.config.logger import setup_logger
+
+from aare.gui.constants import LOGGER_NAME
+
+logger = setup_logger(LOGGER_NAME)
+
if TYPE_CHECKING:
from aare.gui.threads.daq_worker import DAQWorker
-def about_text(client: "DAQWorker"):
+def about_text(client: DAQWorker):
from aare.gui import gui
try:
version = importlib.metadata.version("aaredaq")
except Exception:
+ logger.debug("Could not resolve the installed package version", exc_info=True)
version = "Not found - package is not installed into the environment."
server_version, server_file_path = client.server_about_info()
diff --git a/src/aare/gui/auth.py b/src/aare/gui/auth.py
index 235d0cd5..92201ca6 100644
--- a/src/aare/gui/auth.py
+++ b/src/aare/gui/auth.py
@@ -27,6 +27,7 @@ def auth(base_url: str | None, cert_path: str | None) -> str:
capture_output=True,
text=True,
timeout=120.0,
+ check=False,
)
except subprocess.TimeoutExpired as e:
logger.error(f"Token request curl timed out: {e}")
diff --git a/src/aare/gui/gui.py b/src/aare/gui/gui.py
index daaacbad..5cb233c9 100644
--- a/src/aare/gui/gui.py
+++ b/src/aare/gui/gui.py
@@ -1,6 +1,5 @@
import os
import sys
-import traceback
from aarecommon.config.beamline import cfg_get, mx_beamline
from aarecommon.config.logger import setup_logger
@@ -25,8 +24,8 @@ def main():
banner_path = os.path.join(basedir, "graphics/aare_banner.png")
splash_pix = QtGui.QPixmap(banner_path)
splash = LoadingSplashScreen(splash_pix)
- except Exception as e:
- logger.error(f"Failed to load resources for splash screen: {e}")
+ except Exception:
+ logger.exception("Failed to load resources for splash screen")
sys.exit(1)
try:
# define application
@@ -168,12 +167,11 @@ def main():
splash.set_progress(80, "Authentication successful...")
except Exception as e:
splash.finish(None)
- logger.error(f"Cannot connect to AareDAQ server. Exiting. {e}")
- logger.error(traceback.format_exc())
+ logger.exception("Cannot connect to AareDAQ server. Exiting.")
QMessageBox.critical(
None,
"Authentication Error",
- f"Cannot connect to AareDAQ server:\n{str(e)}\n\n Please check the server is running and your network connection.",
+ f"Cannot connect to AareDAQ server:\n{e!s}\n\n Please check the server is running and your network connection.",
)
sys.exit(1)
@@ -196,15 +194,14 @@ def main():
except Exception as e:
splash.finish(None)
- logger.error(f"Error starting GUI: {e}")
- logger.error(f"Traceback: {traceback.format_exc()}")
+ logger.exception("Error starting GUI")
QMessageBox.critical(
None,
"Fatal Error",
f"An error occurred during startup. See console for details."
f"\nPlease check the server is running and your network connection."
- f"\n\n{str(e)}\n\n",
+ f"\n\n{e!s}\n\n",
)
sys.exit(1)
diff --git a/src/aare/gui/log.py b/src/aare/gui/log.py
index 7bb6bb7d..815b007e 100644
--- a/src/aare/gui/log.py
+++ b/src/aare/gui/log.py
@@ -16,5 +16,5 @@ class QtLogHandler(logging.Handler):
try:
msg = self.format(record)
self.emitter.message.emit(msg)
- except Exception:
+ except Exception: # noqa: BLE001 - logging here would recurse into this handler
self.handleError(record)
diff --git a/src/aare/gui/main_window.py b/src/aare/gui/main_window.py
index d6825979..061f11df 100644
--- a/src/aare/gui/main_window.py
+++ b/src/aare/gui/main_window.py
@@ -45,7 +45,7 @@ from aare.gui.panels.fluorescence_panel import FluorescencePanel
from aare.gui.panels.local_contact_panel import LocalContactDialog
# panels
-from aare.gui.panels.LogPanel import LogDock
+from aare.gui.panels.log_panel import LogDock
from aare.gui.panels.loop_centering_panel import LoopCenteringPanel
from aare.gui.panels.manual_sample_panel import ManualSamplePanel
from aare.gui.panels.portrait_mode import PortraitModePanel
@@ -169,8 +169,8 @@ class MainWindow(QMainWindow):
payload = jwt.decode(token_str, options={"verify_signature": False})
self._decoded_token = TokenData(**payload)
- except Exception as e:
- logger.error(f"Failed to decode authentication token: {e}", exc_info=True)
+ except Exception:
+ logger.exception("Failed to decode authentication token")
QMessageBox.critical(
None,
"Authentication Error",
@@ -934,7 +934,7 @@ class MainWindow(QMainWindow):
if hasattr(self, "content_stack") and hasattr(self, "_standard_main_page"):
self.content_stack.setCurrentWidget(self._standard_main_page)
except Exception as e:
- logger.warning(f"Failed to return to main view during shutdown: {e}")
+ logger.warning(f"Failed to return to main view during shutdown: {e}", exc_info=True)
def _restore_samcam_overlay_settings(self) -> None:
settings = QSettings("PSI", "AareGUI")
@@ -1011,7 +1011,7 @@ class MainWindow(QMainWindow):
try:
thread.stop()
except Exception as e:
- logger.warning(f"Failed to stop {attr_name}: {e}")
+ logger.warning(f"Failed to stop {attr_name}: {e}", exc_info=True)
setattr(self, attr_name, None)
@@ -1028,6 +1028,7 @@ class MainWindow(QMainWindow):
try:
activity_name = tell_state.activity.display_name()
except Exception:
+ logger.debug("Could not derive the axis busy text from status", exc_info=True)
activity_name = activity_value.capitalize() if activity_value else "Busy"
return f"TELL {activity_name}".upper()
@@ -1144,7 +1145,7 @@ class MainWindow(QMainWindow):
self.portrait_sample_camera._autoscale = True
self.portrait_sample_camera._scaling()
except Exception:
- pass
+ logger.debug("Could not autoscale the portrait-mode camera", exc_info=True)
# ── Hide all chrome that contributes to window width ────────────────
if self.status_bar is not None:
@@ -1222,7 +1223,7 @@ class MainWindow(QMainWindow):
settings.get("show_overlay_legend", True)
)
except Exception:
- pass
+ logger.debug("Could not restore the camera overlay legend", exc_info=True)
if hasattr(self, "_pre_portrait_geometry") and self._pre_portrait_geometry:
self.restoreGeometry(self._pre_portrait_geometry)
@@ -1742,14 +1743,14 @@ class MainWindow(QMainWindow):
try:
if self.job_list_panel is not None and self.job_list_panel.is_running():
self.job_list_panel.pause_automation()
- except Exception as e:
- logger.error(f"Failed to pause automation after operation failure: {e}")
+ except Exception:
+ logger.exception("Failed to pause automation after operation failure")
try:
show = QMessageBox.critical if critical else QMessageBox.warning
show(self, title, message)
- except Exception as e:
- logger.error(f"Failed to show operation failure popup: {e}")
+ except Exception:
+ logger.exception("Failed to show operation failure popup")
def _hutch_blocks_mount(self) -> str | None:
"""Reason the hutch PSS currently blocks a mount, or None if OK.
@@ -1780,8 +1781,8 @@ class MainWindow(QMainWindow):
logger.warning(f"Manual mount blocked by hutch PSS: {reason}")
try:
QMessageBox.critical(self, "Mounting Failed", reason)
- except Exception as e:
- logger.error(f"Failed to show mount-blocked popup: {e}")
+ except Exception:
+ logger.exception("Failed to show mount-blocked popup")
return
self.daq.mount(sample, reference)
@@ -1792,8 +1793,8 @@ class MainWindow(QMainWindow):
logger.warning(f"Manual unmount blocked by hutch PSS: {reason}")
try:
QMessageBox.critical(self, "Unmounting Failed", reason)
- except Exception as e:
- logger.error(f"Failed to show unmount-blocked popup: {e}")
+ except Exception:
+ logger.exception("Failed to show unmount-blocked popup")
return
self.daq.unmount()
@@ -1837,8 +1838,8 @@ class MainWindow(QMainWindow):
f"Details:\n{message}"
),
)
- except Exception as e:
- logger.error(f"Failed to show manual collection detector popup: {e}")
+ except Exception:
+ logger.exception("Failed to show manual collection detector popup")
@Slot(str)
def _on_automation_critical_failure(self, message: str) -> None:
@@ -1864,8 +1865,8 @@ class MainWindow(QMainWindow):
self.job_list_panel.pause_automation()
if self.daq is not None:
self.daq.send_status_request()
- except Exception as e:
- logger.error(f"Failed to pause automation queue after critical failure: {e}")
+ except Exception:
+ logger.exception("Failed to pause automation queue after critical failure")
# 2. Mark the automation progress widget as finished-with-error so
# _is_automation_active() returns False and idle/close timers behave.
@@ -1903,8 +1904,8 @@ class MainWindow(QMainWindow):
progress.finished = True
progress.success = False
self.automation_progress_panel.set_progress(progress)
- except Exception as e:
- logger.error(f"Failed to update automation progress after critical failure: {e}")
+ except Exception:
+ logger.exception("Failed to update automation progress after critical failure")
self._show_runtime_notification(
title="Automation paused", message=message, level="error", sticky=True
@@ -1954,8 +1955,8 @@ class MainWindow(QMainWindow):
"Please contact your local contact to recover the beamline."
),
)
- except Exception as e:
- logger.error(f"Failed to surface recovery UI after critical failure: {e}")
+ except Exception:
+ logger.exception("Failed to surface recovery UI after critical failure")
def show_beamline_recovery(self) -> None:
if not bool(getattr(self._decoded_token, "staff", False)):
@@ -2035,6 +2036,7 @@ class MainWindow(QMainWindow):
)
break
except Exception:
+ logger.debug("Skipping an unreadable GUI session entry", exc_info=True)
continue
else:
if self._remote_close_deadline_ts is not None:
@@ -2290,7 +2292,7 @@ class MainWindow(QMainWindow):
try:
self._return_to_main_view_for_shutdown()
except Exception as e:
- logger.warning(f"Failed to restore main view before close: {e}")
+ logger.warning(f"Failed to restore main view before close: {e}", exc_info=True)
try:
# TODO put all setting related handling into state_manager
@@ -2300,18 +2302,18 @@ class MainWindow(QMainWindow):
self._save_theme_settings()
except Exception as e:
- logger.warning(f"Failed to save main window state: {e}")
+ logger.warning(f"Failed to save main window state: {e}", exc_info=True)
# End session before closing so the backend removes this GUI from Redis immediately
try:
self.daq.end_session_on_close()
except Exception as e:
- logger.warning(f"Failed to end session on close: {e}")
+ logger.warning(f"Failed to end session on close: {e}", exc_info=True)
try:
self.cleanup()
except Exception as e:
- logger.warning(f"Cleanup during closeEvent failed: {e}")
+ logger.warning(f"Cleanup during closeEvent failed: {e}", exc_info=True)
super().closeEvent(event)
@@ -2322,7 +2324,7 @@ class MainWindow(QMainWindow):
try:
self._return_to_main_view_for_shutdown()
except Exception as e:
- logger.warning(f"Failed to restore main view during cleanup: {e}")
+ logger.warning(f"Failed to restore main view during cleanup: {e}", exc_info=True)
self._cleanup_done = True
@@ -2330,19 +2332,19 @@ class MainWindow(QMainWindow):
if hasattr(self, "_samcam_source_timer") and self._samcam_source_timer is not None:
self._samcam_source_timer.stop()
except Exception as e:
- logger.warning(f"Failed to stop _samcam_source_timer: {e}")
+ logger.warning(f"Failed to stop _samcam_source_timer: {e}", exc_info=True)
try:
if hasattr(self, "_idle_timer") and self._idle_timer is not None:
self._idle_timer.stop()
except Exception as e:
- logger.warning(f"Failed to stop _idle_timer: {e}")
+ logger.warning(f"Failed to stop _idle_timer: {e}", exc_info=True)
try:
if hasattr(self, "_remote_close_timer") and self._remote_close_timer is not None:
self._remote_close_timer.stop()
except Exception as e:
- logger.warning(f"Failed to stop _remote_close_timer: {e}")
+ logger.warning(f"Failed to stop _remote_close_timer: {e}", exc_info=True)
try:
if (
@@ -2351,13 +2353,13 @@ class MainWindow(QMainWindow):
):
self._axis_camera_refresh_timer.stop()
except Exception as e:
- logger.warning(f"Failed to stop _axis_camera_refresh_timer: {e}")
+ logger.warning(f"Failed to stop _axis_camera_refresh_timer: {e}", exc_info=True)
try:
if hasattr(self, "daq") and self.daq is not None:
self.daq.cleanup()
except Exception as e:
- logger.warning(f"Failed to clean up DAQ worker: {e}")
+ logger.warning(f"Failed to clean up DAQ worker: {e}", exc_info=True)
self._stop_axis_camera_threads()
@@ -2371,7 +2373,7 @@ class MainWindow(QMainWindow):
try:
thread.stop()
except Exception as e:
- logger.warning(f"Failed to stop {attr_name}: {e}")
+ logger.warning(f"Failed to stop {attr_name}: {e}", exc_info=True)
setattr(self, attr_name, None)
@@ -2380,14 +2382,14 @@ class MainWindow(QMainWindow):
if hasattr(self, "job_list_panel") and self.job_list_panel is not None:
return bool(self.job_list_panel.is_running())
except Exception:
- pass
+ logger.debug("Could not read the job list panel running state", exc_info=True)
try:
progress = getattr(self.automation_progress_panel, "_progress", None)
if progress is not None and not bool(getattr(progress, "finished", False)):
return True
except Exception:
- pass
+ logger.debug("Could not read the automation progress state", exc_info=True)
return False
@@ -2405,14 +2407,14 @@ class MainWindow(QMainWindow):
try:
self.daq.report_gui_interaction(int(self._decoded_token.session))
except Exception as e:
- logger.debug(f"Failed to report GUI interaction: {e}")
+ logger.debug(f"Failed to report GUI interaction: {e}", exc_info=True)
def _mark_user_interaction(self) -> None:
self._refresh_idle_activity(report_backend=True)
def eventFilter(self, obj, event):
try:
- if event is not None and event.type() in {
+ if event.type() in {
QEvent.Type.MouseButtonPress,
QEvent.Type.MouseButtonRelease,
QEvent.Type.MouseMove,
@@ -2425,7 +2427,7 @@ class MainWindow(QMainWindow):
}:
self._mark_user_interaction()
except Exception as e:
- logger.debug(f"GUI interaction event filter error: {e}")
+ logger.debug(f"GUI interaction event filter error: {e}", exc_info=True)
return super().eventFilter(obj, event)
def _start_remote_close_countdown(
@@ -2454,16 +2456,14 @@ class MainWindow(QMainWindow):
status = self._latest_daq_status
if status is not None and bool(status.busy):
return False
- if self._is_automation_active():
- return False
- return True
+ return not self._is_automation_active()
@Slot()
def _check_remote_close_deadline(self) -> None:
if self._remote_close_deadline_ts is None:
return
- remaining = int(round(self._remote_close_deadline_ts - time.time()))
+ remaining = round(self._remote_close_deadline_ts - time.time())
if remaining > 0:
if self._remote_close_banner_active:
self.alert_banner_secondary.show_message(
diff --git a/src/aare/gui/models/gui_state_manager.py b/src/aare/gui/models/gui_state_manager.py
index 2cdaa90f..07131a6d 100644
--- a/src/aare/gui/models/gui_state_manager.py
+++ b/src/aare/gui/models/gui_state_manager.py
@@ -1,6 +1,12 @@
import json
+
+from aarecommon.config.logger import setup_logger
from PySide6.QtCore import QSettings
+from aare.gui.constants import LOGGER_NAME
+
+logger = setup_logger(LOGGER_NAME)
+
class UIStateManager:
def __init__(self, organization="PSI", app="AareGUI"):
@@ -24,8 +30,8 @@ class UIStateManager:
data = model.to_list()
self.settings.setValue(key, json.dumps(data))
- except Exception as e:
- print(f"Failed to save model {key}: {e}")
+ except Exception:
+ logger.warning(f"Failed to save model {key}", exc_info=True)
def restore_model(self, key: str, model):
try:
@@ -37,8 +43,8 @@ class UIStateManager:
model.from_list(data)
- except Exception as e:
- print(f"Failed to restore model {key}: {e}")
+ except Exception:
+ logger.warning(f"Failed to restore model {key}", exc_info=True)
def save_value(self, key: str, value):
self.settings.setValue(key, value)
diff --git a/src/aare/gui/models/sample_queue_model.py b/src/aare/gui/models/sample_queue_model.py
index 3ceedc1a..f4b19e1a 100644
--- a/src/aare/gui/models/sample_queue_model.py
+++ b/src/aare/gui/models/sample_queue_model.py
@@ -1,7 +1,12 @@
+from aarecommon.config.logger import setup_logger
from aarecommon.models.models import SampleShortInfo, SampleShortInfoList
from PySide6.QtCore import QAbstractTableModel, Qt
from PySide6.QtGui import QBrush, QColor
+from aare.gui.constants import LOGGER_NAME
+
+logger = setup_logger(LOGGER_NAME)
+
def get_entry(sample: SampleShortInfo, column: int, *, show_user: bool = False):
if show_user:
@@ -78,9 +83,7 @@ class SampleQueueSpreadsheet(QAbstractTableModel):
return ["text/plain"]
def canDropMimeData(self, data, action, row, column, parent):
- if data.hasText():
- return True
- return False
+ return bool(data.hasText())
def dropMimeData(self, data, action, row, column, parent):
if not self.canDropMimeData(data, action, row, column, parent):
@@ -107,8 +110,8 @@ class SampleQueueSpreadsheet(QAbstractTableModel):
updated_samples.insert(updated_row, sample)
self.samples = updated_samples
self.endResetModel()
- except Exception as e:
- print(e)
+ except Exception:
+ logger.warning("Failed to handle dropped sample queue rows", exc_info=True)
return False
return True
diff --git a/src/aare/gui/models/user_sample_model.py b/src/aare/gui/models/user_sample_model.py
index 9e410552..14aee66b 100644
--- a/src/aare/gui/models/user_sample_model.py
+++ b/src/aare/gui/models/user_sample_model.py
@@ -1,9 +1,14 @@
import re
+from aarecommon.config.logger import setup_logger
from aarecommon.models.models import SampleShortInfo, SampleShortInfoList
from PySide6.QtCore import QAbstractTableModel, QMimeData, Qt
from PySide6.QtGui import QBrush, QColor
+from aare.gui.constants import LOGGER_NAME
+
+logger = setup_logger(LOGGER_NAME)
+
def get_entry(sample: SampleShortInfo, column: int):
if column == 0:
@@ -182,7 +187,7 @@ class UserSampleSpreadsheet(QAbstractTableModel):
sample_data = SampleShortInfoList(s=[])
- for i in sorted(set(index.row() for index in indexes)):
+ for i in sorted({index.row() for index in indexes}):
sample_data.s.append(self._sorted_samples[i])
mime_data.setText(sample_data.model_dump_json())
@@ -275,6 +280,7 @@ class UserSampleSpreadsheet(QAbstractTableModel):
)
)
except Exception:
+ logger.debug("Falling back to a plain sort for the column values", exc_info=True)
out.sort()
else:
out.sort()
diff --git a/src/aare/gui/panels/automation_panel.py b/src/aare/gui/panels/automation_panel.py
index e5bcf9cf..e9edb4c9 100644
--- a/src/aare/gui/panels/automation_panel.py
+++ b/src/aare/gui/panels/automation_panel.py
@@ -147,8 +147,8 @@ class AutomationProgressWidget(QWidget):
if seconds is None or seconds <= 0:
return "0m 00s"
if seconds < 60:
- return f"0m {int(round(seconds)):02d}s"
- minutes, secs = divmod(int(round(seconds)), 60)
+ return f"0m {round(seconds):02d}s"
+ minutes, secs = divmod(round(seconds), 60)
if minutes < 60:
return f"{minutes}m {secs:02d}s"
hours, minutes = divmod(minutes, 60)
diff --git a/src/aare/gui/panels/axis_video_panel.py b/src/aare/gui/panels/axis_video_panel.py
index bd8153a5..2bc5e5e8 100644
--- a/src/aare/gui/panels/axis_video_panel.py
+++ b/src/aare/gui/panels/axis_video_panel.py
@@ -1,5 +1,5 @@
from PySide6.QtCore import Qt, Signal
-from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton
+from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
from aare.gui.widgets.busy_overlay import BusyOverlayStyle
from aare.gui.widgets.video_image import VideoGraphicsView
diff --git a/src/aare/gui/panels/beamline_recovery_panel.py b/src/aare/gui/panels/beamline_recovery_panel.py
index c83590b5..17933205 100644
--- a/src/aare/gui/panels/beamline_recovery_panel.py
+++ b/src/aare/gui/panels/beamline_recovery_panel.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+from aarecommon.config.logger import setup_logger
from aarecommon.models.models import DAQStatusModel
from PySide6.QtCore import Slot
from PySide6.QtWidgets import (
@@ -14,8 +15,11 @@ from PySide6.QtWidgets import (
QWidget,
)
+from aare.gui.constants import LOGGER_NAME
from aare.gui.threads.daq_worker import DAQWorker
+logger = setup_logger(LOGGER_NAME)
+
class RecoveryPanel(QWidget):
def __init__(self, *, daq: DAQWorker, parent=None):
@@ -161,12 +165,14 @@ class RecoveryPanel(QWidget):
try:
return self._last_status is not None and self._last_status.sample is not None
except Exception:
+ logger.debug("Could not determine whether a sample is mounted", exc_info=True)
return False
def _beamline_appears_busy(self) -> bool:
try:
return self._last_status is not None and bool(self._last_status.busy)
except Exception:
+ logger.debug("Could not determine whether the beamline is busy", exc_info=True)
return False
def _refresh_buttons(self) -> None:
diff --git a/src/aare/gui/panels/developer_help_dialog.py b/src/aare/gui/panels/developer_help_dialog.py
index 905b9c13..ef1fd449 100644
--- a/src/aare/gui/panels/developer_help_dialog.py
+++ b/src/aare/gui/panels/developer_help_dialog.py
@@ -3,9 +3,8 @@ from __future__ import annotations
import json
import logging
import time
-from typing import Dict
-from aarecommon.config.logger import attach_to_logger, find_existing_formatter
+from aarecommon.config.logger import attach_to_logger, find_existing_formatter, setup_logger
from aarecommon.errors.codes import error_code_help
from aarecommon.models.models import OpenGuiSessionInfo
from PySide6.QtCore import Qt, QUrl, Slot
@@ -30,9 +29,12 @@ from PySide6.QtWidgets import (
QWidget,
)
+from aare.gui.constants import LOGGER_NAME
from aare.gui.log import QtLogEmitter, QtLogHandler
from aare.gui.threads.daq_worker import DAQWorker
+logger = setup_logger(LOGGER_NAME)
+
class DeveloperHelpDialog(QDialog):
def __init__(self, *, daq: DAQWorker, is_staff: bool, parent=None):
@@ -40,7 +42,7 @@ class DeveloperHelpDialog(QDialog):
self._daq = daq
self._is_staff = bool(is_staff)
- self._codes: Dict[str, str] = {}
+ self._codes: dict[str, str] = {}
self._last_payload: dict = {}
self._freeze_payload: bool = False
self._always_highlight_last_error: bool = True
@@ -390,6 +392,7 @@ class DeveloperHelpDialog(QDialog):
try:
pretty = json.dumps(p or {}, indent=2, sort_keys=True, default=str)
except Exception:
+ logger.debug("Could not pretty-print the error payload", exc_info=True)
pretty = str(p)
blocks.append(f"#{i}\n{pretty}")
self._payloads_text.setPlainText("\n\n".join(blocks) if blocks else "(none captured yet)")
@@ -596,6 +599,7 @@ class DeveloperHelpDialog(QDialog):
try:
return int(value)
except Exception:
+ logger.debug("Could not read the selected GUI session id", exc_info=True)
return None
@Slot()
diff --git a/src/aare/gui/panels/face_detection_panel.py b/src/aare/gui/panels/face_detection_panel.py
index 2f9e2ef2..46c9e5e2 100644
--- a/src/aare/gui/panels/face_detection_panel.py
+++ b/src/aare/gui/panels/face_detection_panel.py
@@ -70,6 +70,7 @@ class FaceDetectionPanel(QWidget):
self.face_detection_button.setEnabled(False)
self.face_detection.emit(int(self.steps), int(self.step_size))
except Exception as e:
+ logger.warning("Face detection run failed", exc_info=True)
self._manual_run_requested = False
self.status_lbl.setText(f"Error: {e}")
self.face_detection_button.setEnabled(True)
diff --git a/src/aare/gui/panels/fluorescence_panel.py b/src/aare/gui/panels/fluorescence_panel.py
index e1304508..49e2ccb7 100644
--- a/src/aare/gui/panels/fluorescence_panel.py
+++ b/src/aare/gui/panels/fluorescence_panel.py
@@ -112,7 +112,7 @@ class FluorescencePanel(QWidget):
self._update_vline(pt.x())
return False
except Exception as e:
- logger.debug(f"eventFilter error: {e}")
+ logger.debug(f"eventFilter error: {e}", exc_info=True)
return False
def _show_context_menu(self, pos):
@@ -153,10 +153,10 @@ class FluorescencePanel(QWidget):
for x, y in zip(x_vals, y_vals):
writer.writerow([f"{x:.6f}", f"{y:.6f}"])
logger.info(f"Spectrum saved to {path}")
- except Exception as e:
- logger.error(f"Failed to save CSV: {e}")
- except Exception as e:
- logger.error(f"Context menu error: {e}")
+ except Exception:
+ logger.exception("Failed to save CSV")
+ except Exception:
+ logger.exception("Context menu error")
def _update_vline(self, x_val: float):
# Draw vertical line spanning current Y axis range at x_val
@@ -172,7 +172,7 @@ class FluorescencePanel(QWidget):
self._vline.replace(1, x_val, ymax)
self._vline.setVisible(True)
except Exception as e:
- logger.debug(f"vline error: {e}")
+ logger.debug(f"vline error: {e}", exc_info=True)
def _nearest_index(self, x_val: float) -> int:
n = self.series.count()
@@ -213,6 +213,7 @@ class FluorescencePanel(QWidget):
else:
self.avg_dead_label.setText("Average dead time: -")
except Exception:
+ logger.debug("Could not compute the average dead time", exc_info=True)
self.avg_dead_label.setText("Average dead time: -")
# Default peak label
@@ -251,8 +252,8 @@ class FluorescencePanel(QWidget):
ymax = ymin + 1.0
self.axis_x.setRange(xmin, xmax)
self.axis_y.setRange(ymin, ymax)
- except Exception as e:
- logger.error(f"Update plot error: {e}")
+ except Exception:
+ logger.exception("Update plot error")
@Slot(DAQStatusModel)
def update_daq_status(self, s: DAQStatusModel):
diff --git a/src/aare/gui/panels/local_contact_panel.py b/src/aare/gui/panels/local_contact_panel.py
index 52716c54..7b6630c4 100644
--- a/src/aare/gui/panels/local_contact_panel.py
+++ b/src/aare/gui/panels/local_contact_panel.py
@@ -1,6 +1,7 @@
from __future__ import annotations
from collections.abc import Callable
+from typing import ClassVar
from aarecommon.config.logger import setup_logger
from aarecommon.models.models import DAQStatusModel
@@ -44,7 +45,7 @@ class LocalContactPanel(QFrame):
TAB_DETECTOR = "Detector"
TAB_CONFIG = "Config"
- DEVICE_TITLES = {
+ DEVICE_TITLES: ClassVar[dict[str, str]] = {
"bec": "BEC",
"detector": "Detector",
"tell": "TELL",
diff --git a/src/aare/gui/panels/LogPanel.py b/src/aare/gui/panels/log_panel.py
similarity index 98%
rename from src/aare/gui/panels/LogPanel.py
rename to src/aare/gui/panels/log_panel.py
index e2ad35e9..1de7958c 100644
--- a/src/aare/gui/panels/LogPanel.py
+++ b/src/aare/gui/panels/log_panel.py
@@ -169,7 +169,9 @@ class LogDock(QDockWidget):
def __init__(self, title="Log", parent=None):
super().__init__(title, parent)
self.setAllowedAreas(
- Qt.BottomDockWidgetArea | Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea
+ Qt.DockWidgetArea.BottomDockWidgetArea
+ | Qt.DockWidgetArea.RightDockWidgetArea
+ | Qt.DockWidgetArea.LeftDockWidgetArea
)
self.container = QWidget(self)
diff --git a/src/aare/gui/panels/loop_centering_panel.py b/src/aare/gui/panels/loop_centering_panel.py
index d3b4b947..15d9a179 100644
--- a/src/aare/gui/panels/loop_centering_panel.py
+++ b/src/aare/gui/panels/loop_centering_panel.py
@@ -1,4 +1,4 @@
-from PySide6.QtWidgets import QWidget, QGridLayout, QPushButton
+from PySide6.QtWidgets import QGridLayout, QPushButton, QWidget
from aare.gui.widgets.title_label import TitleLabel
diff --git a/src/aare/gui/panels/portrait_mode.py b/src/aare/gui/panels/portrait_mode.py
index f3ef9d4d..08b21114 100644
--- a/src/aare/gui/panels/portrait_mode.py
+++ b/src/aare/gui/panels/portrait_mode.py
@@ -1,5 +1,7 @@
from __future__ import annotations
+from typing import ClassVar
+
from aarecommon.config.logger import setup_logger
from aarecommon.models.automation import AutomationProgress, StepStatus, WorkflowStateKind
from PySide6.QtCore import QPointF, QRectF, Qt, QTimer, Signal, Slot
@@ -39,10 +41,10 @@ ACTIVE_STEP = "#FFFFFF"
# LED step indicator
# ---------------------------------------------------------------------------
class LEDStages(QWidget):
- STEPS = ["Mount", "Centre", "Raster", "Collect"]
+ STEPS: ClassVar[list[str]] = ["Mount", "Centre", "Raster", "Collect"]
# WorkflowStateKind → LED index
- _KIND_TO_INDEX: dict[WorkflowStateKind, int] = {
+ _KIND_TO_INDEX: ClassVar[dict[WorkflowStateKind, int]] = {
WorkflowStateKind.MOUNT: 0,
WorkflowStateKind.LOOP_CENTRE: 1,
WorkflowStateKind.RASTER: 2,
diff --git a/src/aare/gui/panels/prediction_metrics_panel.py b/src/aare/gui/panels/prediction_metrics_panel.py
index 906ab9b9..dce257d6 100644
--- a/src/aare/gui/panels/prediction_metrics_panel.py
+++ b/src/aare/gui/panels/prediction_metrics_panel.py
@@ -13,6 +13,7 @@ import csv
import time
from collections import deque
from dataclasses import dataclass, field
+from typing import ClassVar
from aarecommon.config.logger import setup_logger
from aarecommon.models.models import MLBoxType
@@ -110,8 +111,8 @@ def get_class_color(class_name: str) -> str:
class ConfidenceHistogramWidget(QWidget):
"""Real-time histogram of prediction confidence scores."""
- BINS = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
- BIN_COLORS = ["#d62728", "#ff7f0e", "#ffbb78", "#98df8a", "#2ca02c"]
+ BINS: ClassVar[list[float]] = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
+ BIN_COLORS: ClassVar[list[str]] = ["#d62728", "#ff7f0e", "#ffbb78", "#98df8a", "#2ca02c"]
def __init__(self, parent=None):
super().__init__(parent)
@@ -810,5 +811,5 @@ class PredictionMetricsPanel(QWidget):
logger.info(f"Exported {len(self._history)} frames to {path}")
- except Exception as e:
- logger.error(f"Failed to export prediction metrics: {e}")
+ except Exception:
+ logger.exception("Failed to export prediction metrics")
diff --git a/src/aare/gui/panels/raster_data_collection.py b/src/aare/gui/panels/raster_data_collection.py
index 3250f149..bbe4a16e 100644
--- a/src/aare/gui/panels/raster_data_collection.py
+++ b/src/aare/gui/panels/raster_data_collection.py
@@ -221,7 +221,7 @@ class RasterDataCollectionPanel(ScanSettingsPanel):
def update_total_time_label(self):
mins = int(self._total_time // 60)
- secs = int(round(self._total_time % 60))
+ secs = round(self._total_time % 60)
if secs == 60:
mins += 1
secs = 0
diff --git a/src/aare/gui/panels/reference_tools_panel.py b/src/aare/gui/panels/reference_tools_panel.py
index 8fbf4882..8708c91b 100644
--- a/src/aare/gui/panels/reference_tools_panel.py
+++ b/src/aare/gui/panels/reference_tools_panel.py
@@ -1,5 +1,4 @@
# reference_tools_panel.py
-from typing import Optional
from aarecommon.config.logger import setup_logger
from aarecommon.models.models import DAQStatusModel, SampleShortInfo, SampleShortInfoList
@@ -41,7 +40,7 @@ def get_entry(sample: SampleShortInfo, column: int):
class ReferenceToolsModel(QAbstractTableModel):
def __init__(
self,
- rows: Optional[list[SampleShortInfo]] | None = None,
+ rows: list[SampleShortInfo] | None = None,
parent=None,
current_reference: int | None = None,
):
@@ -65,10 +64,10 @@ class ReferenceToolsModel(QAbstractTableModel):
else:
self._sorted_samples = []
- def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
+ def rowCount(self, parent: QModelIndex | None = None) -> int:
return len(self._sorted_samples)
- def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
+ def columnCount(self, parent: QModelIndex | None = None) -> int:
return len(self.header)
def data(self, index: QModelIndex, role=None):
@@ -147,7 +146,7 @@ class ReferenceToolsModel(QAbstractTableModel):
reverse=(self._sort_order == Qt.SortOrder.DescendingOrder),
)
- def get_item(self, row: int) -> Optional[SampleShortInfo]:
+ def get_item(self, row: int) -> SampleShortInfo | None:
"""Get sample at the given row index."""
if 0 <= row < len(self._sorted_samples):
return self._sorted_samples[row]
@@ -227,7 +226,7 @@ class ReferenceToolsPanel(QFrame):
self.table_view.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.table_view.setSelectionMode(QTableView.SelectionMode.SingleSelection)
- def _selected_item(self) -> Optional[SampleShortInfo]:
+ def _selected_item(self) -> SampleShortInfo | None:
idx = self.table_view.currentIndex()
if not idx.isValid():
return None
@@ -280,4 +279,5 @@ class ReferenceToolsPanel(QFrame):
f"Current sample: {sample.sample_name} ({sample.location.segment}{sample.location.pos}-{sample.pin})"
)
except Exception as e:
+ logger.debug("Could not update the current sample label", exc_info=True)
self.curr_sample_label.setText(f"Confusing information :/ {e}")
diff --git a/src/aare/gui/panels/rotation_data_collection.py b/src/aare/gui/panels/rotation_data_collection.py
index c01538eb..0e8dacf4 100644
--- a/src/aare/gui/panels/rotation_data_collection.py
+++ b/src/aare/gui/panels/rotation_data_collection.py
@@ -244,7 +244,7 @@ class RotationDataCollectionPanel(ScanSettingsPanel):
def update_total_time_label(self):
mins = int(self._total_time // 60)
- secs = int(round(self._total_time % 60))
+ secs = round(self._total_time % 60)
if secs == 60:
mins += 1
secs = 0
diff --git a/src/aare/gui/panels/sample_queue_panel.py b/src/aare/gui/panels/sample_queue_panel.py
index 72dc0c8b..a7de0c68 100644
--- a/src/aare/gui/panels/sample_queue_panel.py
+++ b/src/aare/gui/panels/sample_queue_panel.py
@@ -279,8 +279,8 @@ class SampleQueuePanel(QFrame):
try:
if self.loop_restart_requested():
return
- except Exception as e:
- logger.exception(e)
+ except Exception:
+ logger.exception("Loop restart request failed")
if self.park_and_dry_when_cleared.isChecked():
logger.info("Automation queue empty; parking and drying TELL.")
diff --git a/src/aare/gui/panels/scan_settings_panel.py b/src/aare/gui/panels/scan_settings_panel.py
index fcddb5ae..39285963 100644
--- a/src/aare/gui/panels/scan_settings_panel.py
+++ b/src/aare/gui/panels/scan_settings_panel.py
@@ -218,7 +218,7 @@ class ScanSettingsPanel(QWidget):
def _res_to_dtz(self, res: float) -> float:
dtz = self._diffraction.calc_dtz_mm(res)
- return self.MIN_DTZ if dtz < self.MIN_DTZ else dtz
+ return max(dtz, self.MIN_DTZ)
@Slot(float)
def _on_dtz_value_changed(self, v: float):
diff --git a/src/aare/gui/panels/smargon_trace_panel.py b/src/aare/gui/panels/smargon_trace_panel.py
index e17c6d10..d1b1fb8e 100644
--- a/src/aare/gui/panels/smargon_trace_panel.py
+++ b/src/aare/gui/panels/smargon_trace_panel.py
@@ -5,9 +5,10 @@ from math import sqrt
from pathlib import Path
import numpy as np
+from aarecommon.config.logger import setup_logger
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
-from PySide6.QtCore import QSettings, QTimer, Qt
+from PySide6.QtCore import QSettings, Qt, QTimer
from PySide6.QtWidgets import (
QCheckBox,
QComboBox,
@@ -23,6 +24,10 @@ from PySide6.QtWidgets import (
QWidget,
)
+from aare.gui.constants import LOGGER_NAME
+
+logger = setup_logger(LOGGER_NAME)
+
class SmargonTracePanel(QWidget):
HOME_X_MM = 0.0
@@ -274,6 +279,7 @@ class SmargonTracePanel(QWidget):
)
except Exception as e:
+ logger.warning(f"Failed to load the smargon trace from {csv_path}", exc_info=True)
self._last_rows = []
self._last_distances = []
self._last_lengths = []
diff --git a/src/aare/gui/panels/smart_rotation_panel.py b/src/aare/gui/panels/smart_rotation_panel.py
index 9f850093..1a49a873 100644
--- a/src/aare/gui/panels/smart_rotation_panel.py
+++ b/src/aare/gui/panels/smart_rotation_panel.py
@@ -278,7 +278,7 @@ class SimpleRotationSettingsPanel(QWidget):
def update_total_time_label(self):
mins = int(self.total_time_s // 60)
- secs = int(round(self.total_time_s % 60))
+ secs = round(self.total_time_s % 60)
if secs == 60:
mins += 1
secs = 0
@@ -349,8 +349,7 @@ class SimpleRotationSettingsPanel(QWidget):
self.transmission = 1.0
self.dtz = self._d.diffraction.calc_dtz_mm(d_tar)
- if self.dtz < 108:
- self.dtz = 108
+ self.dtz = max(self.dtz, 108)
self.transmission_label.setText(f"{self.transmission * 100:.1f}")
self.image_time_label.setText(f"{self.image_time_s:.4f}")
@@ -364,7 +363,7 @@ class SimpleRotationSettingsPanel(QWidget):
self.dtz_label.setText(f"{self.dtz:.2f}")
self.parameters = SimpleScanParameters(
- dtz=int(round(self.dtz)),
+ dtz=round(self.dtz),
exp_time_s=self.image_time_s,
start_omega_deg=self.start_angle_enter.value,
incr_omega_deg=image_angle,
diff --git a/src/aare/gui/panels/target_stability_panel.py b/src/aare/gui/panels/target_stability_panel.py
index 7bfdf029..fdbeb00d 100644
--- a/src/aare/gui/panels/target_stability_panel.py
+++ b/src/aare/gui/panels/target_stability_panel.py
@@ -1402,11 +1402,10 @@ class TargetStabilityPanel(QWidget):
@staticmethod
def _coerce_target_point(raw) -> tuple[float, float] | None:
try:
- if isinstance(raw, dict):
- if "x" in raw and "y" in raw:
- return float(raw["x"]), float(raw["y"])
+ if isinstance(raw, dict) and "x" in raw and "y" in raw:
+ return float(raw["x"]), float(raw["y"])
if isinstance(raw, (list, tuple)) and len(raw) >= 2:
return float(raw[0]), float(raw[1])
except Exception as e:
- logger.warning(f"Failed to parse target point {raw}: {e}")
+ logger.warning(f"Failed to parse target point {raw}: {e}", exc_info=True)
return None
diff --git a/src/aare/gui/panels/tell_sample_panel.py b/src/aare/gui/panels/tell_sample_panel.py
index 2669bd75..035c487f 100644
--- a/src/aare/gui/panels/tell_sample_panel.py
+++ b/src/aare/gui/panels/tell_sample_panel.py
@@ -236,6 +236,7 @@ class TellSamplePanel(QFrame):
current_puck=sample.puck_name, current_sample=sample.db_id
)
except Exception as e:
+ logger.debug("Could not build the TELL sample panel text", exc_info=True)
base_text = f"Confusing information :/ {e}"
if tell_details:
diff --git a/src/aare/gui/scan_logic/raster_grid_manager.py b/src/aare/gui/scan_logic/raster_grid_manager.py
index 64b292c1..c6d7d163 100644
--- a/src/aare/gui/scan_logic/raster_grid_manager.py
+++ b/src/aare/gui/scan_logic/raster_grid_manager.py
@@ -1,6 +1,5 @@
import math
from enum import Enum
-from typing import List, Tuple
import numpy as np
from aarecommon.config.beamline import cfg_get, get_jfjoch_url, mx_beamline
@@ -148,13 +147,13 @@ class RasterGridManager(QObject):
),
dtz=cfg_get("daq.data_collection_settings.default_raster_settings.dtz", 200.0),
)
- self._completed_grids: List[CompletedRasterGridElem] = []
+ self._completed_grids: list[CompletedRasterGridElem] = []
# Cache of pre-rendered heatmap bitmaps, keyed by (id(grid_elem), metric).
# Each entry is (QImage, backing ndarray); the ndarray must be kept alive
# because QImage shares its buffer without copying. Rebuilt only when the
# data or metric changes, not on every repaint (sample move / zoom).
- self._heatmap_cache: dict[tuple[int, "RasterGridMetric"], tuple[QImage, np.ndarray]] = {}
+ self._heatmap_cache: dict[tuple[int, RasterGridMetric], tuple[QImage, np.ndarray]] = {}
@property
def active_grid(self) -> RasterGridRequest:
@@ -162,15 +161,21 @@ class RasterGridManager(QObject):
def _is_grid_visible(self, grid: RasterGridRequest):
if (
+ grid.smargon_top_left is None
+ or grid.smargon_top_left.phi_deg is None
+ or grid.smargon_top_left.chi_deg is None
+ or self._geom.smargon.phi_deg is None
+ or self._geom.smargon.chi_deg is None
+ ):
+ raise ValueError(f"Cannot compute visibility for {grid}.")
+ return bool(
grid.visible
and abs(normalize_angle(grid.omega_deg - self._geom.omega_deg)) < 0.2
and abs(grid.smargon_top_left.phi_deg - self._geom.smargon.phi_deg) < 0.2
and abs(grid.smargon_top_left.chi_deg - self._geom.smargon.chi_deg) < 0.2
and grid.n_x > 0
and grid.n_y > 0
- ):
- return True
- return False
+ )
def _grid_pixel_geometry(
self, grid: RasterGridRequest
@@ -210,10 +215,10 @@ class RasterGridManager(QObject):
if visible_rect is None or visible_rect.isEmpty():
return 0, grid.n_x, 0, grid.n_y
- min_x = max(0, int(math.floor((visible_rect.left() - start_x) / cell_w)) - 1)
- max_x = min(grid.n_x, int(math.ceil((visible_rect.right() - start_x) / cell_w)) + 1)
- min_y = max(0, int(math.floor((visible_rect.top() - start_y) / cell_h)) - 1)
- max_y = min(grid.n_y, int(math.ceil((visible_rect.bottom() - start_y) / cell_h)) + 1)
+ min_x = max(0, math.floor((visible_rect.left() - start_x) / cell_w) - 1)
+ max_x = min(grid.n_x, math.ceil((visible_rect.right() - start_x) / cell_w) + 1)
+ min_y = max(0, math.floor((visible_rect.top() - start_y) / cell_h) - 1)
+ max_y = min(grid.n_y, math.ceil((visible_rect.bottom() - start_y) / cell_h) + 1)
return min_x, max_x, min_y, max_y
@@ -300,7 +305,7 @@ class RasterGridManager(QObject):
0, 0, self._active_grid.grid_size_mm.x, self._active_grid.grid_size_mm.y
)
- def get_grid_coord(self, grid: RasterGridRequest, point: QPointF) -> Tuple[int, int]:
+ def get_grid_coord(self, grid: RasterGridRequest, point: QPointF) -> tuple[int, int]:
point_bl = self._geom.picture_to_sample(Coordinate(x=point.x(), y=point.y()))
delta = point_bl - self._geom.smargon_to_beamline(grid.smargon_top_left.sh_mm)
@@ -542,7 +547,7 @@ class RasterGridManager(QObject):
self._heatmap_cache.clear()
def _heatmap_image(
- self, cache_key: tuple, grid: RasterGridRequest, values: List[float] | List[int]
+ self, cache_key: tuple, grid: RasterGridRequest, values: list[float] | list[int]
) -> QImage | None:
"""Return a cached n_x*n_y heatmap bitmap for this grid, building it once
on a cache miss. One pixel per cell; colours baked at full opacity with
@@ -560,7 +565,7 @@ class RasterGridManager(QObject):
return built[0]
def _build_heatmap_image(
- self, n_x: int, n_y: int, values: List[float] | List[int]
+ self, n_x: int, n_y: int, values: list[float] | list[int]
) -> tuple[QImage, np.ndarray] | None:
if n_x <= 0 or n_y <= 0:
return None
@@ -597,7 +602,7 @@ class RasterGridManager(QObject):
self,
painter: QPainter,
elem: CompletedRasterGridElem,
- values: List[float] | List[int],
+ values: list[float] | list[int],
alpha: int,
visible_rect: QRectF | None,
cache_key: tuple,
@@ -648,7 +653,7 @@ class RasterGridManager(QObject):
self,
painter: QPainter,
grid: RasterGridRequest,
- values: List[float] | List[int] | None = None,
+ values: list[float] | list[int] | None = None,
alpha: int = 127,
visible_rect: QRectF | None = None,
):
@@ -657,9 +662,8 @@ class RasterGridManager(QObject):
if alpha < 0 or alpha > 255:
return
- if values is None:
- if self._draw_active_grid_fast(painter, grid, visible_rect):
- return
+ if values is None and self._draw_active_grid_fast(painter, grid, visible_rect):
+ return
geo = self._grid_pixel_geometry(grid)
if geo is None:
@@ -677,9 +681,12 @@ class RasterGridManager(QObject):
):
return
- if values is not None and (cell_w < 3.0 or cell_h < 3.0):
- if self._draw_completed_grid_fast(painter, grid, values, alpha, visible_rect):
- return
+ if (
+ values is not None
+ and (cell_w < 3.0 or cell_h < 3.0)
+ and self._draw_completed_grid_fast(painter, grid, values, alpha, visible_rect)
+ ):
+ return
painter.save()
painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
@@ -759,8 +766,8 @@ class RasterGridManager(QObject):
painter.drawRect(bounds)
min_spacing_px = 4.0
- stride_x = max(1, int(math.ceil(min_spacing_px / max(cell_w, 1e-9))))
- stride_y = max(1, int(math.ceil(min_spacing_px / max(cell_h, 1e-9))))
+ stride_x = max(1, math.ceil(min_spacing_px / max(cell_w, 1e-9)))
+ stride_y = max(1, math.ceil(min_spacing_px / max(cell_h, 1e-9)))
min_ix, max_ix, min_iy, max_iy = self._visible_index_range(
grid, visible_rect, start_x, start_y, cell_w, cell_h
@@ -793,7 +800,7 @@ class RasterGridManager(QObject):
self,
painter: QPainter,
grid: RasterGridRequest,
- values: List[float] | List[int],
+ values: list[float] | list[int],
alpha: int,
visible_rect: QRectF | None,
) -> bool:
@@ -823,8 +830,8 @@ class RasterGridManager(QObject):
)
min_fill_px = 3.0
- stride_x = max(1, int(math.ceil(min_fill_px / max(cell_w, 1e-9))))
- stride_y = max(1, int(math.ceil(min_fill_px / max(cell_h, 1e-9))))
+ stride_x = max(1, math.ceil(min_fill_px / max(cell_w, 1e-9)))
+ stride_y = max(1, math.ceil(min_fill_px / max(cell_h, 1e-9)))
painter.save()
painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
@@ -855,7 +862,7 @@ class RasterGridManager(QObject):
return True
def _block_value(
- self, values: List[float] | List[int], grid_nx: int, x0: int, x1: int, y0: int, y1: int
+ self, values: list[float] | list[int], grid_nx: int, x0: int, x1: int, y0: int, y1: int
) -> float | None:
best = None
for y in range(y0, y1):
@@ -967,5 +974,5 @@ class RasterGridManager(QObject):
)
self.completed_grid_updated.emit()
- def get_completed_grids(self) -> List[CompletedRasterGridElem]:
+ def get_completed_grids(self) -> list[CompletedRasterGridElem]:
return self._completed_grids
diff --git a/src/aare/gui/threads/axis_video_thread.py b/src/aare/gui/threads/axis_video_thread.py
index 26f868ea..a132393a 100644
--- a/src/aare/gui/threads/axis_video_thread.py
+++ b/src/aare/gui/threads/axis_video_thread.py
@@ -1,9 +1,14 @@
import cv2
-import requests
import numpy as np
+import requests
+from aarecommon.config.logger import setup_logger
from PySide6.QtCore import QThread, Signal
from PySide6.QtGui import QImage
+from aare.gui.constants import LOGGER_NAME
+
+logger = setup_logger(LOGGER_NAME)
+
class VideoThread(QThread):
frame_ready = Signal(QImage)
@@ -76,16 +81,17 @@ class VideoThread(QThread):
buffer = buffer[last_boundary:]
except requests.exceptions.RequestException as e:
- self.error_occurred.emit(f"Connection error: {str(e)}")
+ self.error_occurred.emit(f"Connection error: {e!s}")
except Exception as e:
- self.error_occurred.emit(f"Unexpected error: {str(e)}")
+ logger.warning("Axis video stream failed", exc_info=True)
+ self.error_occurred.emit(f"Unexpected error: {e!s}")
finally:
self.running = False
if self.session:
try:
self.session.close()
except Exception:
- pass
+ logger.debug("Error closing the Axis camera session", exc_info=True)
self.session = None
def _process_buffer(self, buffer, boundary):
@@ -119,7 +125,7 @@ class VideoThread(QThread):
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Convert to QImage
- height, width, channel = rgb_frame.shape
+ height, width, _channel = rgb_frame.shape
bytes_per_line = 3 * width
qt_image = QImage(
rgb_frame.data, width, height, bytes_per_line, QImage.Format.Format_RGB888
@@ -131,6 +137,7 @@ class VideoThread(QThread):
except Exception:
# If frame processing fails, use last good frame if available
+ logger.debug("Frame processing failed; reusing the last good frame", exc_info=True)
if self.last_good_frame is not None:
self.frame_ready.emit(self.last_good_frame)
@@ -143,16 +150,16 @@ class VideoThread(QThread):
try:
session.close()
except Exception:
- pass
+ logger.debug("Error closing the Axis camera session", exc_info=True)
finally:
self.session = None
try:
self.quit()
except Exception:
- pass
+ logger.debug("Error while quitting the Axis video thread", exc_info=True)
try:
self.wait(5000)
except Exception:
- pass
+ logger.debug("Error while waiting for the Axis video thread to finish", exc_info=True)
diff --git a/src/aare/gui/threads/camera_thread.py b/src/aare/gui/threads/camera_thread.py
index 310a84dc..e8fd54e9 100644
--- a/src/aare/gui/threads/camera_thread.py
+++ b/src/aare/gui/threads/camera_thread.py
@@ -4,11 +4,16 @@ import time
import cv2
import numpy as np
import zmq
+from aarecommon.config.logger import setup_logger
from aarecommon.math.autofocus import focus_measure_edges
from aarecommon.models.models import DAQStatusModel
from PySide6.QtCore import QThread, Signal, Slot
from PySide6.QtGui import QImage, QPixmap
+from aare.gui.constants import LOGGER_NAME
+
+logger = setup_logger(LOGGER_NAME)
+
class SampleCameraThread(QThread):
# Define a signal to communicate messages from the thread to the main GUI
@@ -97,6 +102,9 @@ class SampleCameraThread(QThread):
header = decoded
break
except Exception:
+ logger.debug(
+ "Skipping an unparsable camera ZMQ message part", exc_info=True
+ )
continue
if header:
@@ -160,6 +168,7 @@ class SampleCameraThread(QThread):
self._set_camera_available(False, "Sample camera feed unavailable")
continue # Check self.running again
except Exception as e:
+ logger.warning("Sample camera feed unavailable", exc_info=True)
self._set_camera_available(False, f"Sample camera feed unavailable: {e}")
self.running = False
diff --git a/src/aare/gui/threads/daq_worker.py b/src/aare/gui/threads/daq_worker.py
index f08d8213..ca0e7765 100644
--- a/src/aare/gui/threads/daq_worker.py
+++ b/src/aare/gui/threads/daq_worker.py
@@ -8,7 +8,7 @@ import time
from collections import deque
from dataclasses import dataclass
from datetime import datetime
-from typing import Literal, cast
+from typing import ClassVar, Literal, cast
from aarecommon.config.logger import setup_logger
from aarecommon.errors.codes import AareErrorCode, AuthErrorCode, export_error_codes
@@ -352,7 +352,7 @@ class DAQWorker(QObject):
self._last_status_request_ts = now
request = QNetworkRequest(QUrl(f"{self._base_url}/status"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.finished.connect(lambda: self.handle_status_response(reply))
@@ -367,7 +367,7 @@ class DAQWorker(QObject):
file = str(file_reply.readAll()) if file_reply.waitForReadyRead(500) else "Not connected"
return version, file
- _SELF_SIGNED_ERRORS = {
+ _SELF_SIGNED_ERRORS: ClassVar[set[QSslError.SslError]] = {
QSslError.SslError.SelfSignedCertificate,
QSslError.SslError.SelfSignedCertificateInChain,
}
@@ -564,7 +564,7 @@ class DAQWorker(QObject):
if aerotech_detail:
err_msg = aerotech_detail
except Exception:
- logger.error(f"Exception reading status response: {e}")
+ logger.exception("Exception reading status response")
if status == 403:
self._last_status_can_read = False
@@ -592,7 +592,7 @@ class DAQWorker(QObject):
self._last_smargon_connected = None
self._last_aerotech_connected = None
- logger.error(f"Exception from status response: {e}")
+ logger.exception("Exception from status response")
@Slot(QNetworkReply)
def handle_spreadsheet_response(self, reply: QNetworkReply):
@@ -601,7 +601,7 @@ class DAQWorker(QObject):
parsed_response = SampleShortInfoList.model_validate_json(response_data)
self.spreadsheet.emit(parsed_response)
except Exception as e:
- logger.error(f"Exception from spreadsheet response: {e}")
+ logger.exception("Exception from spreadsheet response")
self.http_error.emit(str(e))
@Slot(QNetworkReply)
@@ -611,7 +611,7 @@ class DAQWorker(QObject):
parsed_response = SampleShortInfoList.model_validate_json(response_data)
self.reference_tools.emit(parsed_response)
except Exception as e:
- logger.error(f"Exception from reference tools response: {e}")
+ logger.exception("Exception from reference tools response")
self.http_error.emit(str(e))
def handle_req_response(self, reply: QNetworkReply):
@@ -623,11 +623,12 @@ class DAQWorker(QObject):
try:
raw_body = reply.readAll().data().decode("utf-8")
except Exception:
- pass
+ logger.debug("Could not read the raw reply body", exc_info=True)
try:
url = reply.request().url().toString()
except Exception:
+ logger.debug("Could not read the request URL", exc_info=True)
url = ""
net_err = reply.error()
@@ -691,7 +692,7 @@ class DAQWorker(QObject):
self.sample_resync_completed.emit(message)
self.send_status_request()
except Exception as e:
- logger.error(f"Sample resync failed: {e}")
+ logger.exception("Sample resync failed")
self.http_error.emit(str(e))
def _handle_detector_metadata_resync_response(self, reply: QNetworkReply):
@@ -705,7 +706,7 @@ class DAQWorker(QObject):
self.send_status_request()
self.load_local_contact_device_state()
except Exception as e:
- logger.error(f"Hardware metadata resync failed: {e}")
+ logger.exception("Hardware metadata resync failed")
self.http_error.emit(str(e))
def _handle_recovery_action_response(self, reply: QNetworkReply, default_message: str):
@@ -718,7 +719,7 @@ class DAQWorker(QObject):
self.recovery_action_completed.emit(message)
self.send_status_request()
except Exception as e:
- logger.error(f"Recovery action failed: {e}")
+ logger.exception("Recovery action failed")
self.http_error.emit(str(e))
def _emit_placeholder_local_contact_action(self, action_name: str) -> None:
@@ -739,7 +740,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/{url}"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
if str:
request.setRawHeader(b"Content-Type", b"application/json")
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
@@ -757,7 +758,7 @@ class DAQWorker(QObject):
logger.info(f"PUT /{url}: {body}")
return
request = QNetworkRequest(QUrl(f"{self._base_url}/{url}"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
if str:
request.setRawHeader(b"Content-Type", b"application/json")
reply = self._net_manager.put(request, QByteArray(body.encode("utf-8")))
@@ -775,7 +776,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/{url}"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.deleteResource(request)
reply.finished.connect(lambda: self.handle_req_response(reply))
@@ -874,7 +875,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/state/free_beamline"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
body = json.dumps({"confirmation_code": confirmation_code})
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
@@ -889,7 +890,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/access/take_over_beamline"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
body = json.dumps({"confirmation_code": confirmation_code})
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
@@ -904,7 +905,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/recovery/recover_beamline"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
body = json.dumps({"confirmation_code": confirmation_code})
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
@@ -921,7 +922,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/recovery/unmount_sample"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
body = json.dumps({"confirmation_code": confirmation_code})
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
@@ -949,12 +950,12 @@ class DAQWorker(QObject):
arr = json.loads(response_data) if response_data else []
if not isinstance(arr, list):
- raise RuntimeError("Invalid all_pgroups payload")
+ raise TypeError("Invalid all_pgroups payload")
# Ensure list[str]
out = [str(x) for x in arr if isinstance(x, (str, int))]
self.staff_pgroups_loaded.emit(out)
except Exception as e:
- logger.error(f"Exception from all_pgroups response: {e}")
+ logger.exception("Exception from all_pgroups response")
self.http_error.emit(str(e))
@Slot()
@@ -964,7 +965,7 @@ class DAQWorker(QObject):
self.staff_pgroups_loaded.emit([])
return
request = QNetworkRequest(QUrl(f"{self._base_url}/access/all_pgroups"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
reply = self._net_manager.put(request, QByteArray(b""))
reply.finished.connect(lambda: self._handle_all_pgroups_response(reply))
@@ -1017,7 +1018,7 @@ class DAQWorker(QObject):
parsed_response = CompletedRotationScan.model_validate_json(response_data)
self.standard_scan_completed.emit(parsed_response)
except Exception as e:
- logger.error(f"Exception from rotation scan response: {e}")
+ logger.exception("Exception from rotation scan response")
self.http_error.emit(str(e))
finally:
reply.deleteLater()
@@ -1033,7 +1034,7 @@ class DAQWorker(QObject):
self.run_number_incremented.emit()
request = QNetworkRequest(QUrl(f"{self._base_url}/scan/rotation"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
body = r.model_dump_json()
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
@@ -1072,7 +1073,7 @@ class DAQWorker(QObject):
parsed_response = CompletedRasterGrid.model_validate_json(response_data)
self.raster_scan_completed.emit(parsed_response)
except Exception as e:
- logger.error(f"Exception from raster scan response: {e}")
+ logger.exception("Exception from raster scan response")
self.http_error.emit(str(e))
finally:
reply.deleteLater()
@@ -1114,7 +1115,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/scan/raster?auto_center=false"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
body = r.model_dump_json()
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
@@ -1156,7 +1157,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/scan/raster?auto_center=true"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
body = r.model_dump_json()
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
@@ -1169,7 +1170,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/sample/spreadsheet"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.finished.connect(lambda: self.handle_spreadsheet_response(reply))
@@ -1179,7 +1180,7 @@ class DAQWorker(QObject):
logger.info("GET /sample/reference_tools")
return
request = QNetworkRequest(QUrl(f"{self._base_url}/sample/reference_tools"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.finished.connect(lambda: self.handle_reference_tools_response(reply))
@@ -1242,9 +1243,10 @@ class DAQWorker(QObject):
else:
err_msg = raw_body
except Exception:
+ logger.debug("Could not parse the error body as JSON", exc_info=True)
err_msg = raw_body
except Exception:
- pass
+ logger.debug("Could not extract error details from the reply body", exc_info=True)
return status, str(err_msg), body_json
@@ -1278,6 +1280,7 @@ class DAQWorker(QObject):
try:
return code in {item.value for item in AuthErrorCode}
except Exception:
+ logger.debug("Could not classify the reply as an auth error", exc_info=True)
return False
@staticmethod
@@ -1296,6 +1299,7 @@ class DAQWorker(QObject):
try:
status_int = int(status) if status is not None else None
except Exception:
+ logger.debug("Could not read the reply status code", exc_info=True)
status_int = None
code = None
@@ -1314,14 +1318,11 @@ class DAQWorker(QObject):
):
return True
- if (
+ return bool(
"daq state error" in text
or "must be idle to start measurement" in text
or "must be idle" in text
- ):
- return True
-
- return False
+ )
def handle_auto_scan_response(self, reply, sample_id: int):
if reply.error() == QNetworkReply.NetworkError.NoError:
@@ -1377,7 +1378,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/scan/auto"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
body = s.model_dump_json()
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
@@ -1425,7 +1426,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/sample/resync"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
reply = self._net_manager.post(request, QByteArray(b""))
reply.finished.connect(lambda: self._handle_sample_resync_response(reply))
@@ -1437,7 +1438,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/resync/detector_metadata"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
reply = self._net_manager.post(request, QByteArray(b""))
reply.finished.connect(lambda: self._handle_detector_metadata_resync_response(reply))
@@ -1477,6 +1478,7 @@ class DAQWorker(QObject):
try:
url = reply.request().url().toString()
except Exception:
+ logger.debug("Could not read the request URL", exc_info=True)
url = "unknown-url"
return f"{context}\n\nURL: {url}\nHTTP status: {status}\nError: {exc}"
@@ -1499,7 +1501,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/simulation_state"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.finished.connect(lambda: self._handle_local_contact_simulation_state_response(reply))
@@ -1508,9 +1510,10 @@ class DAQWorker(QObject):
response_data = self.handle_response(reply)
payload = json.loads(response_data) if response_data else {}
if not isinstance(payload, dict):
- raise RuntimeError("Invalid local contact simulation state payload")
+ raise TypeError("Invalid local contact simulation state payload")
self.local_contact_simulation_state_loaded.emit(payload)
except Exception as e:
+ logger.warning("Local Contact simulation state request failed", exc_info=True)
message = self._build_local_contact_error_message(
"Error transferring information from DAQ while loading Local Contact simulation state.",
reply,
@@ -1528,7 +1531,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/device_state"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.finished.connect(lambda: self._handle_local_contact_device_state_response(reply))
@@ -1537,9 +1540,10 @@ class DAQWorker(QObject):
response_data = self.handle_response(reply)
payload = json.loads(response_data) if response_data else {}
if not isinstance(payload, dict):
- raise RuntimeError("Invalid local contact device state payload")
+ raise TypeError("Invalid local contact device state payload")
self.local_contact_device_state_loaded.emit(payload)
except Exception as e:
+ logger.warning("Local Contact device state request failed", exc_info=True)
message = self._build_local_contact_error_message(
"Error transferring information from DAQ while loading Local Contact device state.",
reply,
@@ -1557,7 +1561,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/links"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.finished.connect(lambda: self._handle_local_contact_links_response(reply))
@@ -1566,9 +1570,10 @@ class DAQWorker(QObject):
response_data = self.handle_response(reply)
payload = json.loads(response_data) if response_data else {}
if not isinstance(payload, dict):
- raise RuntimeError("Invalid local contact links payload")
+ raise TypeError("Invalid local contact links payload")
self.local_contact_links_loaded.emit(payload)
except Exception as e:
+ logger.warning("Local Contact links request failed", exc_info=True)
message = self._build_local_contact_error_message(
"Error transferring information from DAQ while loading Local Contact links.",
reply,
@@ -1589,7 +1594,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/config"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.finished.connect(lambda: self._handle_local_contact_config_response(reply))
@@ -1598,14 +1603,14 @@ class DAQWorker(QObject):
response_data = self.handle_response(reply)
payload = json.loads(response_data) if response_data else {}
if not isinstance(payload, dict):
- raise RuntimeError("Invalid local contact config payload")
+ raise TypeError("Invalid local contact config payload")
self.local_contact_config_loaded.emit(payload)
except Exception as e:
message = (
"Error transferring information from DAQ while loading Local Contact config.\n\n"
f"{e}"
)
- logger.error(message)
+ logger.exception(message)
self.local_contact_transfer_error.emit(message)
@Slot(dict)
@@ -1615,7 +1620,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/local_contact/config"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
body = QByteArray(json.dumps(payload).encode("utf-8"))
reply = self._net_manager.put(request, body)
@@ -1626,7 +1631,7 @@ class DAQWorker(QObject):
response_data = self.handle_response(reply)
payload = json.loads(response_data) if response_data else {}
if not isinstance(payload, dict):
- raise RuntimeError("Invalid local contact config response")
+ raise TypeError("Invalid local contact config response")
self.local_contact_config_saved.emit(payload)
self.local_contact_config_loaded.emit(payload)
self.status_message.emit("Local Contact config saved.", False)
@@ -1634,7 +1639,7 @@ class DAQWorker(QObject):
message = (
f"Error transferring information from DAQ while saving Local Contact config.\n\n{e}"
)
- logger.error(message)
+ logger.exception(message)
self.local_contact_transfer_error.emit(message)
@Slot(str, bool)
@@ -1657,7 +1662,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/bec/user_macros"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.finished.connect(lambda: self._handle_bec_user_macros_response(reply))
@@ -1666,10 +1671,10 @@ class DAQWorker(QObject):
response_data = self.handle_response(reply)
payload = json.loads(response_data) if response_data else []
if not isinstance(payload, list):
- raise RuntimeError("Invalid BEC user macros payload")
+ raise TypeError("Invalid BEC user macros payload")
self.bec_user_macros_loaded.emit([str(item) for item in payload])
except Exception as e:
- logger.error(f"Failed to list BEC user macros: {e}")
+ logger.exception("Failed to list BEC user macros")
self.http_error.emit(str(e))
@Slot()
@@ -1680,7 +1685,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/bec/devices"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.finished.connect(lambda: self._handle_bec_devices_response(reply))
@@ -1689,10 +1694,10 @@ class DAQWorker(QObject):
response_data = self.handle_response(reply)
payload = json.loads(response_data) if response_data else []
if not isinstance(payload, list):
- raise RuntimeError("Invalid BEC devices payload")
+ raise TypeError("Invalid BEC devices payload")
self.bec_devices_loaded.emit([str(item) for item in payload])
except Exception as e:
- logger.error(f"Failed to list BEC devices: {e}")
+ logger.exception("Failed to list BEC devices")
self.http_error.emit(str(e))
@Slot(str)
@@ -1755,7 +1760,7 @@ class DAQWorker(QObject):
parsed_response = RasterGridRequest.model_validate_json(response_data)
self.raster_generated_by_ml.emit(parsed_response)
except Exception as e:
- logger.error(f"Error in ml box: {e}")
+ logger.exception("Error in ml box")
self.http_error.emit(str(e))
@Slot()
@@ -1768,7 +1773,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/alc/ml_bounding_box"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.post(request, QByteArray(b""))
reply.finished.connect(lambda: self.handle_ml_box_response(reply))
@@ -1783,7 +1788,7 @@ class DAQWorker(QObject):
data = json.loads(payload)
self.face_detection_result.emit(data)
except Exception as e:
- logger.error(f"Error in face detection: {e}")
+ logger.exception("Error in face detection")
self.http_error.emit(str(e))
finally:
reply.deleteLater()
@@ -1797,8 +1802,8 @@ class DAQWorker(QObject):
if payload:
data = json.loads(payload)
self.face_detection_result.emit(data)
- except Exception as e:
- logger.error(f"Face detection stream parse error: {e}")
+ except Exception:
+ logger.exception("Face detection stream parse error")
def _restart_face_detection_stream(self):
reply = self._face_detection_stream_reply
@@ -1809,6 +1814,7 @@ class DAQWorker(QObject):
try:
status = reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute)
except Exception:
+ logger.debug("Could not read the face detection stream status", exc_info=True)
status = None
if status == 403:
@@ -1968,8 +1974,8 @@ class DAQWorker(QObject):
self._automation_progress_buffer += chunk
self._process_automation_progress_buffer()
- except Exception as e:
- logger.error(f"Automation progress stream parse error: {e}")
+ except Exception:
+ logger.exception("Automation progress stream parse error")
def _restart_automation_progress_stream(self):
reply = self._automation_progress_stream_reply
@@ -1981,6 +1987,7 @@ class DAQWorker(QObject):
try:
status = reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute)
except Exception:
+ logger.debug("Could not read the automation progress stream status", exc_info=True)
status = None
if status == 403:
@@ -2006,7 +2013,7 @@ class DAQWorker(QObject):
self._automation_progress_buffer = ""
request = QNetworkRequest(QUrl(f"{self._base_url}/sse/automation_progress"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.readyRead.connect(lambda: self._read_automation_progress_stream(reply))
reply.finished.connect(self._restart_automation_progress_stream)
@@ -2023,7 +2030,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/sse/face_detection"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.readyRead.connect(lambda: self._read_face_detection_stream(reply))
reply.finished.connect(self._restart_face_detection_stream)
@@ -2045,7 +2052,7 @@ class DAQWorker(QObject):
request = QNetworkRequest(
QUrl(f"{self._base_url}/face_detection/run?steps={steps}&step_size={step_size}")
)
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
reply = self._net_manager.post(request, QByteArray(b""))
reply.finished.connect(lambda: self._handle_face_detection_response(reply))
@@ -2071,7 +2078,7 @@ class DAQWorker(QObject):
if emit_status:
# fetch status and bkg in parallel (simple sequential here)
status_req = QNetworkRequest(QUrl(f"{self._base_url}/fluorimeter/status"))
- status_req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ status_req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
status_reply = self._net_manager.get(status_req)
status_reply.finished.connect(
lambda: self._handle_fluorimeter_status_and_emit(data, status_reply)
@@ -2079,7 +2086,7 @@ class DAQWorker(QObject):
else:
self.fluorimeter_update.emit(data, [], -1)
except Exception as e:
- logger.error(f"Fluorimeter data error: {e}")
+ logger.exception("Fluorimeter data error")
self.http_error.emit(str(e))
def _handle_fluorimeter_status_and_emit(self, data, status_reply: QNetworkReply):
@@ -2087,11 +2094,11 @@ class DAQWorker(QObject):
s_payload = self.handle_response(status_reply)
s = int(s_payload) if s_payload not in ("", "null") else -1
b_req = QNetworkRequest(QUrl(f"{self._base_url}/fluorimeter/background"))
- b_req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ b_req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
b_reply = self._net_manager.get(b_req)
b_reply.finished.connect(lambda: self._emit_fluorimeter_with_bkg(data, s, b_reply))
except Exception as e:
- logger.error(f"Fluorimeter status error: {e}")
+ logger.exception("Fluorimeter status error")
self.http_error.emit(str(e))
def _emit_fluorimeter_with_bkg(self, data, s, b_reply: QNetworkReply):
@@ -2102,7 +2109,7 @@ class DAQWorker(QObject):
bkg = json.loads(bkg_json) if bkg_json else []
self.fluorimeter_update.emit(data, bkg, s)
except Exception as e:
- logger.error(f"Fluorimeter background error: {e}")
+ logger.exception("Fluorimeter background error")
self.http_error.emit(str(e))
@Slot()
@@ -2110,7 +2117,7 @@ class DAQWorker(QObject):
if self._base_url is None:
return
req = QNetworkRequest(QUrl(f"{self._base_url}/fluorimeter/spectrum"))
- req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
req.setRawHeader(b"Content-Type", b"application/json")
body = f.model_dump_json()
reply = self._net_manager.post(req, QByteArray(body.encode("utf-8")))
@@ -2122,7 +2129,7 @@ class DAQWorker(QObject):
parsed_response = FluorescenceSpectrumOutputModel.model_validate_json(response_data)
self.fluorimeter_spectrum_update.emit(parsed_response)
except Exception as e:
- logger.error(f"Exception from fluorimeter spectrum: {e}")
+ logger.exception("Exception from fluorimeter spectrum")
self.http_error.emit(str(e))
@Slot()
@@ -2130,7 +2137,7 @@ class DAQWorker(QObject):
if self._base_url is None:
return
req = QNetworkRequest(QUrl(f"{self._base_url}/fluorimeter/data"))
- req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ req.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(req)
reply.finished.connect(lambda: self._handle_fluorimeter_data(reply, emit_status=True))
@@ -2139,7 +2146,7 @@ class DAQWorker(QObject):
if self._base_url is None:
return
request = QNetworkRequest(QUrl(f"{self._base_url}/sse/fluorimeter"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.readyRead.connect(lambda: self._read_fluorimeter_stream(reply))
reply.finished.connect(lambda: reply.deleteLater())
@@ -2156,8 +2163,8 @@ class DAQWorker(QObject):
bkg = obj.get("background") or []
status = obj.get("status", -1)
self.fluorimeter_update.emit(data, bkg, status)
- except Exception as e:
- logger.error(f"SSE parse error: {e}")
+ except Exception:
+ logger.exception("SSE parse error")
@staticmethod
def _flatten_error_codes_payload(obj: dict) -> dict[str, str]:
@@ -2173,7 +2180,7 @@ class DAQWorker(QObject):
if isinstance(v, dict):
group = str(k)
for kk, vv in v.items():
- out[f"{group}.{str(kk)}"] = str(vv)
+ out[f"{group}.{kk!s}"] = str(vv)
else:
out[str(k)] = str(v)
return out
@@ -2191,13 +2198,13 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/meta/error-codes"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.finished.connect(lambda: self._handle_error_codes_response(reply))
def _retry_error_codes_legacy(self) -> None:
request = QNetworkRequest(QUrl(f"{self._base_url}/meta/error-codes/flat"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.finished.connect(lambda: self._handle_error_codes_response(reply))
@@ -2209,6 +2216,7 @@ class DAQWorker(QObject):
try:
url = reply.request().url().toString()
except Exception:
+ logger.debug("Could not read the request URL", exc_info=True)
url = ""
if int(status) == 404 and url.endswith("/meta/error-codes"):
@@ -2219,11 +2227,11 @@ class DAQWorker(QObject):
payload = self.handle_response(reply)
obj = json.loads(payload) if payload else {}
if not isinstance(obj, dict):
- raise RuntimeError("Invalid error-codes payload (expected JSON object)")
+ raise TypeError("Invalid error-codes payload (expected JSON object)")
out = self._flatten_error_codes_payload(obj)
self.error_codes_loaded.emit(out)
except Exception as e:
- logger.error(f"Failed to load error codes: {e}")
+ logger.exception("Failed to load error codes")
self.http_error.emit(str(e))
@Slot(str, str)
@@ -2255,7 +2263,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/sse/baton"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.readyRead.connect(lambda: self._read_baton_stream(reply))
reply.finished.connect(self._restart_baton_stream)
@@ -2299,8 +2307,8 @@ class DAQWorker(QObject):
self._last_baton_status = status
self.baton_status_changed.emit(status)
- except Exception as e:
- logger.error(f"Baton stream parse error: {e}")
+ except Exception:
+ logger.exception("Baton stream parse error")
@Slot()
def request_baton(self):
@@ -2312,7 +2320,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/baton/request"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
reply = self._net_manager.post(request, QByteArray(b""))
reply.finished.connect(lambda: self._handle_baton_request_response(reply))
@@ -2335,7 +2343,7 @@ class DAQWorker(QObject):
elif result.get("error"):
self.status_message.emit(result.get("message", "Request failed"), True)
except Exception as e:
- logger.error(f"Baton request failed: {e}")
+ logger.exception("Baton request failed")
self.http_error.emit(str(e))
@Slot(bool)
@@ -2353,7 +2361,7 @@ class DAQWorker(QObject):
request = QNetworkRequest(
QUrl(f"{self._base_url}/baton/respond?accept={str(accept).lower()}")
)
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
reply = self._net_manager.post(request, QByteArray(b""))
reply.finished.connect(lambda: self._handle_baton_response_result(reply))
@@ -2370,7 +2378,7 @@ class DAQWorker(QObject):
self.start_baton_stream()
self._restart_blocked_sse_streams_if_access_restored()
except Exception as e:
- logger.error(f"Baton response failed: {e}")
+ logger.exception("Baton response failed")
self.http_error.emit(str(e))
@Slot()
@@ -2390,7 +2398,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/baton/check_timeout"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.finished.connect(lambda: self._handle_baton_timeout_response(reply))
@@ -2405,7 +2413,7 @@ class DAQWorker(QObject):
self.start_baton_stream()
self._restart_blocked_sse_streams_if_access_restored()
except Exception as e:
- logger.error(f"Baton timeout check failed: {e}")
+ logger.exception("Baton timeout check failed")
self.http_error.emit(str(e))
def release_baton_on_close(self):
@@ -2434,7 +2442,7 @@ class DAQWorker(QObject):
logger.info("End session requested on GUI close")
except Exception as e:
- logger.warning(f"Error ending session on close: {e}")
+ logger.warning(f"Error ending session on close: {e}", exc_info=True)
def _handle_gui_sessions_response(self, reply: QNetworkReply):
try:
@@ -2443,7 +2451,7 @@ class DAQWorker(QObject):
sessions = [OpenGuiSessionInfo.model_validate(item) for item in payload]
self.gui_sessions_loaded.emit(sessions)
except Exception as e:
- logger.error(f"Failed to load GUI sessions: {e}")
+ logger.exception("Failed to load GUI sessions")
self.http_error.emit(str(e))
@Slot()
@@ -2453,7 +2461,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/admin/gui_sessions"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.get(request)
reply.finished.connect(lambda: self._handle_gui_sessions_response(reply))
@@ -2470,7 +2478,7 @@ class DAQWorker(QObject):
f"{self._base_url}/admin/gui_sessions/{session_id}/request_close?grace_seconds={grace_seconds}"
)
)
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
reply = self._net_manager.post(request, QByteArray(b""))
reply.finished.connect(lambda: self._handle_gui_session_mutation_response(reply))
@@ -2482,7 +2490,7 @@ class DAQWorker(QObject):
return
request = QNetworkRequest(QUrl(f"{self._base_url}/admin/gui_sessions/{session_id}"))
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
reply = self._net_manager.deleteResource(request)
reply.finished.connect(lambda: self._handle_gui_session_mutation_response(reply))
@@ -2496,7 +2504,7 @@ class DAQWorker(QObject):
_ = self.handle_response(reply)
self.load_gui_sessions()
except Exception as e:
- logger.error(f"GUI session mutation failed: {e}")
+ logger.exception("GUI session mutation failed")
self.http_error.emit(str(e))
self.load_gui_sessions()
@@ -2508,7 +2516,7 @@ class DAQWorker(QObject):
request = QNetworkRequest(
QUrl(f"{self._base_url}/admin/gui_sessions/{session_id}/interaction")
)
- request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode("utf-8"))
+ request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
request.setRawHeader(b"Content-Type", b"application/json")
reply = self._net_manager.post(request, QByteArray(b""))
reply.finished.connect(lambda: reply.deleteLater())
@@ -2522,13 +2530,13 @@ class DAQWorker(QObject):
if hasattr(self, "_baton_timeout_timer") and self._baton_timeout_timer is not None:
self._baton_timeout_timer.stop()
except Exception as e:
- logger.warning(f"Failed to stop _baton_timeout_timer: {e}")
+ logger.warning(f"Failed to stop _baton_timeout_timer: {e}", exc_info=True)
try:
if hasattr(self, "_timer") and self._timer is not None:
self._timer.stop()
except Exception as e:
- logger.warning(f"Failed to stop __timer: {e}")
+ logger.warning(f"Failed to stop __timer: {e}", exc_info=True)
for attr_name in (
"_baton_stream_reply",
@@ -2542,12 +2550,12 @@ class DAQWorker(QObject):
try:
reply.abort()
except Exception as e:
- logger.warning(f"Failed to abort {attr_name}: {e}")
+ logger.warning(f"Failed to abort {attr_name}: {e}", exc_info=True)
try:
reply.deleteLater()
except Exception as e:
- logger.warning(f"Failed to delete {attr_name}: {e}")
+ logger.warning(f"Failed to delete {attr_name}: {e}", exc_info=True)
setattr(self, attr_name, None)
diff --git a/src/aare/gui/threads/jfjoch_viewer.py b/src/aare/gui/threads/jfjoch_viewer.py
index 36407b36..102ef3b0 100644
--- a/src/aare/gui/threads/jfjoch_viewer.py
+++ b/src/aare/gui/threads/jfjoch_viewer.py
@@ -1,7 +1,12 @@
from aarecommon.config.beamline import get_jfjoch_url, mx_beamline
+from aarecommon.config.logger import setup_logger
from PySide6.QtCore import QObject, Slot
from PySide6.QtDBus import QDBusConnection, QDBusInterface
+from aare.gui.constants import LOGGER_NAME
+
+logger = setup_logger(LOGGER_NAME)
+
class JFJochDBusClient(QObject):
def __init__(self):
@@ -25,8 +30,8 @@ class JFJochDBusClient(QObject):
else:
print("D-Bus not available")
- except Exception as e:
- print(f"D-Bus not available: {e}.")
+ except Exception:
+ logger.warning("D-Bus not available", exc_info=True)
def _ensure_interface(self):
"""Ensure we have a valid interface, creating one if needed."""
diff --git a/src/aare/gui/threads/prediction_subscriber.py b/src/aare/gui/threads/prediction_subscriber.py
index 2d03e4d5..044fe99f 100644
--- a/src/aare/gui/threads/prediction_subscriber.py
+++ b/src/aare/gui/threads/prediction_subscriber.py
@@ -91,6 +91,7 @@ class PredictionSubscriber(QThread):
decoded = json.loads(part.decode("utf-8"))
return decoded if isinstance(decoded, dict) else None
except Exception:
+ logger.debug("Could not parse the prediction payload as JSON", exc_info=True)
return None
def _decode_rgb_image(self, header: dict, data: bytes) -> np.ndarray | None:
@@ -231,13 +232,13 @@ class PredictionSubscriber(QThread):
except Exception as e:
if self.running:
self._set_camera_available(False, f"Sample camera feed unavailable: {e}")
- logger.exception(f"PredictionSubscriber error: {e}")
+ logger.exception("PredictionSubscriber error")
finally:
try:
if self._sock is not None:
self._sock.close(0)
except Exception:
- pass
+ logger.debug("Error closing the prediction ZMQ socket", exc_info=True)
finally:
self._sock = None
@@ -245,7 +246,7 @@ class PredictionSubscriber(QThread):
if self._ctx is not None:
self._ctx.term()
except Exception:
- pass
+ logger.debug("Error terminating the prediction ZMQ context", exc_info=True)
finally:
self._ctx = None
@@ -257,7 +258,7 @@ class PredictionSubscriber(QThread):
if self._sock is not None:
self._sock.close(0)
except Exception:
- pass
+ logger.debug("Error while stopping the prediction subscriber", exc_info=True)
if not self.wait(1500):
logger.warning("PredictionSubscriber did not stop within timeout")
diff --git a/src/aare/gui/threads/sse_client.py b/src/aare/gui/threads/sse_client.py
index 7264b955..78f3ca8e 100644
--- a/src/aare/gui/threads/sse_client.py
+++ b/src/aare/gui/threads/sse_client.py
@@ -1,6 +1,7 @@
-from typing import Optional, Dict
-from PySide6.QtCore import QObject, Signal, Slot, QUrl, QTimer, QByteArray
-from PySide6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply, QSslError
+from typing import ClassVar
+
+from PySide6.QtCore import QByteArray, QObject, QTimer, QUrl, Signal, Slot
+from PySide6.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest, QSslError
class SSEClient(QObject):
@@ -15,13 +16,13 @@ class SSEClient(QObject):
super().__init__(parent)
self._network_manager = QNetworkAccessManager(self)
self._network_manager.sslErrors.connect(self._handle_ssl_errors)
- self._reply: Optional[QNetworkReply] = None
+ self._reply: QNetworkReply | None = None
self._reconnect_timer = QTimer(self)
self._reconnect_timer.setSingleShot(True)
self._reconnect_timer.timeout.connect(self._attempt_reconnect)
self._url = QUrl()
- self._headers: Dict[str, str] = {}
+ self._headers: dict[str, str] = {}
self._buffer = QByteArray()
self._connected = False
self._reconnect_delay = 1000 # Start with 1 second
@@ -32,7 +33,7 @@ class SSEClient(QObject):
self._current_data = ""
self._current_id = ""
- def connect_to_sse(self, url: str, headers: Optional[Dict[str, str]] = None):
+ def connect_to_sse(self, url: str, headers: dict[str, str] | None = None):
"""Connect to SSE endpoint"""
self._url = QUrl(url)
self._headers = headers or {}
@@ -61,7 +62,7 @@ class SSEClient(QObject):
"""Check if connected to SSE"""
return self._connected and self._reply and self._reply.isOpen()
- _SELF_SIGNED_ERRORS = {
+ _SELF_SIGNED_ERRORS: ClassVar[set[QSslError.SslError]] = {
QSslError.SslError.SelfSignedCertificate,
QSslError.SslError.SelfSignedCertificateInChain,
}
@@ -155,8 +156,7 @@ class SSEClient(QObject):
value = line[colon_index + 1 :]
# Remove leading space from value
- if value.startswith(" "):
- value = value[1:]
+ value = value.removeprefix(" ")
if field == "data":
if self._current_data:
diff --git a/src/aare/gui/tutorials/controls_help_dialog.py b/src/aare/gui/tutorials/controls_help_dialog.py
index 30ef8f59..0883e97a 100644
--- a/src/aare/gui/tutorials/controls_help_dialog.py
+++ b/src/aare/gui/tutorials/controls_help_dialog.py
@@ -1,5 +1,5 @@
from PySide6.QtCore import Qt
-from PySide6.QtWidgets import QDialog, QVBoxLayout, QTextEdit, QDialogButtonBox, QTabWidget, QWidget
+from PySide6.QtWidgets import QDialog, QDialogButtonBox, QTabWidget, QTextEdit, QVBoxLayout, QWidget
class ControlsHelpDialog(QDialog):
diff --git a/src/aare/gui/tutorials/tutorial_manager.py b/src/aare/gui/tutorials/tutorial_manager.py
index 005a4dc5..87745a4d 100644
--- a/src/aare/gui/tutorials/tutorial_manager.py
+++ b/src/aare/gui/tutorials/tutorial_manager.py
@@ -4,11 +4,12 @@ import time
from dataclasses import dataclass
from typing import Any
+from aarecommon.config.logger import setup_logger
from PySide6.QtCore import (
+ Property,
QEasingCurve,
QObject,
QPropertyAnimation,
- Property,
QRect,
Qt,
QTimer,
@@ -17,6 +18,7 @@ from PySide6.QtCore import (
from PySide6.QtGui import QColor, QPainter, QPen
from PySide6.QtWidgets import QLabel, QPushButton, QWidget
+from aare.gui.constants import LOGGER_NAME
from aare.gui.tutorials.tutorial_models import (
StepFlow,
StepStatus,
@@ -36,6 +38,8 @@ from aare.gui.tutorials.tutorial_runtime import (
ensure_step_runtime_state,
)
+logger = setup_logger(LOGGER_NAME)
+
def _now_ms() -> int:
return int(time.time() * 1000)
@@ -169,11 +173,14 @@ class TutorialOverlay(QWidget):
super().keyPressEvent(event)
def mousePressEvent(self, event) -> None:
- if self._waiting_for_click and self.current_rect.isValid():
- if self.current_rect.contains(event.pos()):
- self.highlight_clicked.emit()
- event.accept()
- return
+ if (
+ self._waiting_for_click
+ and self.current_rect.isValid()
+ and self.current_rect.contains(event.pos())
+ ):
+ self.highlight_clicked.emit()
+ event.accept()
+ return
super().mousePressEvent(event)
def paintEvent(self, event) -> None:
@@ -460,8 +467,7 @@ class TutorialManager(QObject):
self.tutorial_stopped.emit(scenario_id)
return
- if new_index < 0:
- new_index = 0
+ new_index = max(new_index, 0)
self.runtime_state.active_step_index = new_index
step = self.current_scenario.steps[new_index]
@@ -535,6 +541,7 @@ class TutorialManager(QObject):
try:
return self.target_resolver.resolve_target(step.target, self.context)
except Exception:
+ logger.debug("Could not resolve the tutorial step target", exc_info=True)
return None
def _resolved_target_rect(self, resolved: ResolvedTutorialTarget | None) -> QRect | None:
@@ -688,9 +695,8 @@ class TutorialManager(QObject):
step.skippable
and self.current_scenario is not None
and self.current_scenario.allow_skip
- ):
- if self.runtime_state is not None:
- self._advance_to_index(self.runtime_state.active_step_index + 1)
- return
+ ) and self.runtime_state is not None:
+ self._advance_to_index(self.runtime_state.active_step_index + 1)
+ return
self.overlay.next_button.setEnabled(True)
diff --git a/src/aare/gui/tutorials/tutorial_models.py b/src/aare/gui/tutorials/tutorial_models.py
index d0ab205c..fe2bae5c 100644
--- a/src/aare/gui/tutorials/tutorial_models.py
+++ b/src/aare/gui/tutorials/tutorial_models.py
@@ -51,7 +51,7 @@ class CompletionKind(str, Enum):
class CompletionRule:
kind: CompletionKind
value: Any = None
- children: list["CompletionRule"] = field(default_factory=list)
+ children: list[CompletionRule] = field(default_factory=list)
description: TutorialTextRef | None = None
@@ -121,7 +121,7 @@ class TutorialScenario:
title: TutorialTextRef
description: TutorialTextRef
mode: TutorialMode
- steps: list["TutorialStepDefinition"]
+ steps: list[TutorialStepDefinition]
version: str = "1.0"
tags: set[str] = field(default_factory=set)
@@ -146,6 +146,6 @@ class TutorialContext:
completed_step_ids: list[str] = field(default_factory=list)
state: dict[str, Any] = field(default_factory=dict)
- event_log: list["TutorialEvent"] = field(default_factory=list)
+ event_log: list[TutorialEvent] = field(default_factory=list)
metadata: dict[str, Any] = field(default_factory=dict)
diff --git a/src/aare/gui/tutorials/tutorial_runtime.py b/src/aare/gui/tutorials/tutorial_runtime.py
index 27d1b996..1d8f1bf1 100644
--- a/src/aare/gui/tutorials/tutorial_runtime.py
+++ b/src/aare/gui/tutorials/tutorial_runtime.py
@@ -3,9 +3,11 @@ from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Protocol, runtime_checkable
+from aarecommon.config.logger import setup_logger
from PySide6.QtCore import QObject, QRect, Signal
from PySide6.QtWidgets import QWidget
+from aare.gui.constants import LOGGER_NAME
from aare.gui.tutorials.tutorial_models import (
CompletionKind,
CompletionRule,
@@ -19,6 +21,8 @@ from aare.gui.tutorials.tutorial_models import (
TutorialTextRef,
)
+logger = setup_logger(LOGGER_NAME)
+
@dataclass(slots=True)
class ResolvedTutorialTarget:
@@ -148,6 +152,7 @@ class DictionaryTextResolver:
try:
return template.format(**text.args)
except Exception:
+ logger.debug("Could not resolve the tutorial text template", exc_info=True)
return template
diff --git a/src/aare/gui/widgets/automation_progress.py b/src/aare/gui/widgets/automation_progress.py
index 80e0663a..4c88baab 100644
--- a/src/aare/gui/widgets/automation_progress.py
+++ b/src/aare/gui/widgets/automation_progress.py
@@ -112,7 +112,7 @@ class CompactAutomationProgressStrip(QFrame):
def _format_duration(seconds: float | None) -> str:
if seconds is None or seconds <= 0:
return "0m 00s"
- total = int(round(seconds))
+ total = round(seconds)
minutes, secs = divmod(total, 60)
if minutes < 60:
return f"{minutes}m {secs:02d}s"
diff --git a/src/aare/gui/widgets/baton_request_dialog.py b/src/aare/gui/widgets/baton_request_dialog.py
index 16043c8b..4ec6ae4d 100644
--- a/src/aare/gui/widgets/baton_request_dialog.py
+++ b/src/aare/gui/widgets/baton_request_dialog.py
@@ -1,14 +1,14 @@
-from PySide6.QtCore import Qt, Signal, QTimer
+from PySide6.QtCore import Qt, QTimer, Signal
+from PySide6.QtGui import QFont
from PySide6.QtWidgets import (
QDialog,
- QVBoxLayout,
+ QFrame,
QHBoxLayout,
QLabel,
- QPushButton,
QProgressBar,
- QFrame,
+ QPushButton,
+ QVBoxLayout,
)
-from PySide6.QtGui import QFont
class BatonRequestDialog(QDialog):
@@ -317,8 +317,7 @@ class BatonPendingDialog(QDialog):
def _tick(self):
self._remaining -= 1
- if self._remaining < 0:
- self._remaining = 0
+ self._remaining = max(self._remaining, 0)
self.progress.setValue(self._remaining)
self.time_label.setText(f"{self._remaining} seconds remaining")
diff --git a/src/aare/gui/widgets/camera_image.py b/src/aare/gui/widgets/camera_image.py
index 7d94d217..5d66ea2f 100644
--- a/src/aare/gui/widgets/camera_image.py
+++ b/src/aare/gui/widgets/camera_image.py
@@ -193,7 +193,7 @@ class SampleCameraImageLabel(QGraphicsView):
self._det_shape = payload.get("shape", None)
self._detections = payload.get("boxes", []) or []
except Exception as e:
- logger.debug(f"Exception in update_detections: {e}")
+ logger.debug(f"Exception in update_detections: {e}", exc_info=True)
self._detections = []
self.update()
@@ -224,7 +224,7 @@ class SampleCameraImageLabel(QGraphicsView):
)
self._last_target_update_ts = now
except Exception as e:
- logger.debug(f"Exception in update_target_point: {e}")
+ logger.debug(f"Exception in update_target_point: {e}", exc_info=True)
self._target_point = None
self._smoothed_target_point = None
self.update()
@@ -405,12 +405,11 @@ class SampleCameraImageLabel(QGraphicsView):
self._state = SampleCameraImageState.RESIZE_RASTER_GRID
else:
self._state = SampleCameraImageState.DRAWING_RASTER_GRID
- elif event.button() == Qt.MouseButton.LeftButton:
- if (
- self._raster_mgr.is_part_of_active_grid(self.start_point)
- and not ctrl_override_move
- ):
- self._state = SampleCameraImageState.MOVING_RASTER_GRID
+ elif event.button() == Qt.MouseButton.LeftButton and (
+ self._raster_mgr.is_part_of_active_grid(self.start_point)
+ and not ctrl_override_move
+ ):
+ self._state = SampleCameraImageState.MOVING_RASTER_GRID
def _update_grid(self):
now = time.monotonic()
@@ -446,13 +445,14 @@ class SampleCameraImageLabel(QGraphicsView):
self.mapToGlobal(event.pos()), f"{mouse_pos.x():.0f}, {mouse_pos.y():.0f} pxl", self
)
- if self._state == SampleCameraImageState.IDLE:
- if event.modifiers() & Qt.KeyboardModifier.ShiftModifier:
- if not self.raster_timer.isActive():
- self.load_image.emit(mouse_pos)
- self.raster_timer.start(self.raster_timer_interval)
- else:
- self._pending_load_pos = mouse_pos
+ if self._state == SampleCameraImageState.IDLE and (
+ event.modifiers() & Qt.KeyboardModifier.ShiftModifier
+ ):
+ if not self.raster_timer.isActive():
+ self.load_image.emit(mouse_pos)
+ self.raster_timer.start(self.raster_timer_interval)
+ else:
+ self._pending_load_pos = mouse_pos
self.end_point = mouse_pos
@@ -613,8 +613,7 @@ class SampleCameraImageLabel(QGraphicsView):
ratio_h = self.viewport().size().height() / self.pixmap_item.boundingRect().height()
ratio = min(ratio_w, ratio_h)
- if ratio < 0.1:
- ratio = 0.1
+ ratio = max(ratio, 0.1)
if ratio >= 1.0:
# Don't enable scaling when gain in ratio is < 5% (to avoid back-and-forth)
@@ -736,7 +735,7 @@ class SampleCameraImageLabel(QGraphicsView):
label = str(det.get("label", "")).lower()
conf = det.get("conf", 0.0)
except Exception as e:
- logger.debug(f"Error in draw detection {det}: {e}")
+ logger.debug(f"Error in draw detection {det}: {e}", exc_info=True)
continue
color = color_map.get(label, QColor("magenta"))
@@ -776,7 +775,7 @@ class SampleCameraImageLabel(QGraphicsView):
if isinstance(raw, (list, tuple)) and len(raw) >= 2:
return float(raw[0]), float(raw[1])
except Exception as e:
- logger.debug(f"Error parsing target point {raw}: {e}")
+ logger.debug(f"Error parsing target point {raw}: {e}", exc_info=True)
return None
def _draw_target_point(self, painter: QPainter):
@@ -799,7 +798,10 @@ class SampleCameraImageLabel(QGraphicsView):
img_h, img_w = int(shape[0]), int(shape[1])
tx, ty = self._smoothed_target_point
except Exception as e:
- logger.debug(f"Error using smoothed target point {self._smoothed_target_point}: {e}")
+ logger.debug(
+ f"Error using smoothed target point {self._smoothed_target_point}: {e}",
+ exc_info=True,
+ )
return
sx = pix.width() / float(img_w)
@@ -1122,11 +1124,14 @@ class SampleCameraImageLabel(QGraphicsView):
mouse_view_pos = self.mapFromGlobal(mouse_global_pos)
mouse_scene_pos = self.mapToScene(mouse_view_pos)
- if event.key() == Qt.Key.Key_Shift:
- if self._state == SampleCameraImageState.IDLE and self._camera_interaction_enabled():
- if not self.raster_timer.isActive():
- self.load_image.emit(mouse_scene_pos)
- self.raster_timer.start(self.raster_timer_interval)
+ if (
+ event.key() == Qt.Key.Key_Shift
+ and self._state == SampleCameraImageState.IDLE
+ and self._camera_interaction_enabled()
+ and not self.raster_timer.isActive()
+ ):
+ self.load_image.emit(mouse_scene_pos)
+ self.raster_timer.start(self.raster_timer_interval)
super().keyPressEvent(event)
diff --git a/src/aare/gui/widgets/clickable_label.py b/src/aare/gui/widgets/clickable_label.py
index 97d34aa8..eb3b2aac 100644
--- a/src/aare/gui/widgets/clickable_label.py
+++ b/src/aare/gui/widgets/clickable_label.py
@@ -1,4 +1,4 @@
-from PySide6.QtCore import Signal, Qt
+from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import QLabel
diff --git a/src/aare/gui/widgets/local_contact_status_widget.py b/src/aare/gui/widgets/local_contact_status_widget.py
index e9bae8db..328e9157 100644
--- a/src/aare/gui/widgets/local_contact_status_widget.py
+++ b/src/aare/gui/widgets/local_contact_status_widget.py
@@ -1,16 +1,21 @@
from __future__ import annotations
from collections.abc import Iterable
+from typing import ClassVar
+from aarecommon.config.logger import setup_logger
from aarecommon.models.models import DAQStatusModel
from PySide6.QtCore import Qt, Slot
from PySide6.QtWidgets import QFrame, QGridLayout, QLabel, QSizePolicy, QVBoxLayout
+from aare.gui.constants import LOGGER_NAME
from aare.gui.widgets.title_label import TitleLabel
+logger = setup_logger(LOGGER_NAME)
+
class LocalContactStatusWidget(QFrame):
- FIELD_TITLES = {
+ FIELD_TITLES: ClassVar[dict[str, str]] = {
"beamline_state": "Beamline state",
"busy": "Busy",
"sample": "Sample",
@@ -202,6 +207,7 @@ class LocalContactStatusWidget(QFrame):
try:
sample_loc = f" ({status.sample.loc_str()})"
except Exception:
+ logger.debug("Could not format the sample location", exc_info=True)
sample_loc = ""
if sample_name:
return f"{self._badge('MOUNTED', tone='info')} {sample_name}{sample_loc}"
@@ -351,7 +357,7 @@ class LocalContactStatusWidget(QFrame):
def _refresh(self) -> None:
if self._last_status is None:
- for _key, (_title, value) in self._row_widgets.items():
+ for _title, value in self._row_widgets.values():
value.setText(self._badge("WAITING", tone="neutral"))
return
diff --git a/src/aare/gui/widgets/message_box.py b/src/aare/gui/widgets/message_box.py
index 80692363..87c38fac 100644
--- a/src/aare/gui/widgets/message_box.py
+++ b/src/aare/gui/widgets/message_box.py
@@ -114,9 +114,9 @@ def timer_box(
# Auto-accept (as if user pressed Yes)
box.done(QMessageBox.StandardButton.Yes)
stop_timer()
- except Exception as e:
+ except Exception:
# Never let errors in condition_func block UI
- logger.error(f"Error in condition_func: {e}")
+ logger.exception("Error in condition_func")
box.accepted.connect(stop_timer)
box.rejected.connect(stop_timer)
diff --git a/src/aare/gui/widgets/number_line_edit.py b/src/aare/gui/widgets/number_line_edit.py
index fd79f088..168d68b4 100644
--- a/src/aare/gui/widgets/number_line_edit.py
+++ b/src/aare/gui/widgets/number_line_edit.py
@@ -1,6 +1,6 @@
-from PySide6.QtCore import Signal, Slot, Qt
+from PySide6.QtCore import Qt, Signal, Slot
from PySide6.QtGui import QDoubleValidator
-from PySide6.QtWidgets import QLineEdit, QWidget, QCheckBox, QHBoxLayout
+from PySide6.QtWidgets import QCheckBox, QHBoxLayout, QLineEdit, QWidget
class NumberLineEdit(QLineEdit):
@@ -23,7 +23,7 @@ class NumberLineEdit(QLineEdit):
self.setValidator(self.validator)
self.setAlignment(Qt.AlignmentFlag.AlignRight)
self.setToolTip(
- "Minimum: {:s}\nMaximum: {:s}".format(self.to_string(min_val), self.to_string(max_val))
+ f"Minimum: {self.to_string(min_val):s}\nMaximum: {self.to_string(max_val):s}"
)
# Connect the textChanged signal to a custom slot to check validity
@@ -75,7 +75,7 @@ class NumberLineEdit(QLineEdit):
def update_limits(self, min_val: float, max_val: float):
self.validator.setRange(min_val, max_val, self.decimal_count)
self.setToolTip(
- "Minimum: {:s}\nMaximum: {:s}".format(self.to_string(min_val), self.to_string(max_val))
+ f"Minimum: {self.to_string(min_val):s}\nMaximum: {self.to_string(max_val):s}"
)
def validate(self, text) -> bool:
diff --git a/src/aare/gui/widgets/pgroup_dialog.py b/src/aare/gui/widgets/pgroup_dialog.py
index 11e9b0ea..6d467abf 100644
--- a/src/aare/gui/widgets/pgroup_dialog.py
+++ b/src/aare/gui/widgets/pgroup_dialog.py
@@ -1,12 +1,12 @@
from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
- QDialog,
- QVBoxLayout,
- QPushButton,
- QLabel,
QComboBox,
QCompleter,
+ QDialog,
+ QLabel,
QMessageBox,
+ QPushButton,
+ QVBoxLayout,
)
diff --git a/src/aare/gui/widgets/splash_screen.py b/src/aare/gui/widgets/splash_screen.py
index 2c7da0d4..91761223 100644
--- a/src/aare/gui/widgets/splash_screen.py
+++ b/src/aare/gui/widgets/splash_screen.py
@@ -1,5 +1,5 @@
from PySide6.QtCore import Qt
-from PySide6.QtWidgets import QSplashScreen, QProgressBar, QApplication
+from PySide6.QtWidgets import QApplication, QProgressBar, QSplashScreen
class LoadingSplashScreen(QSplashScreen):
diff --git a/src/aare/gui/widgets/status_bar.py b/src/aare/gui/widgets/status_bar.py
index 34506549..8a600382 100644
--- a/src/aare/gui/widgets/status_bar.py
+++ b/src/aare/gui/widgets/status_bar.py
@@ -230,8 +230,8 @@ class StatusBar(QStatusBar):
html_content_session = f"""Session: {session_flag}"""
self.session_label.setText(html_content_session)
- except Exception as e:
- logger.error(f"Error updating DAQ status in status bar: {e}")
+ except Exception:
+ logger.exception("Error updating DAQ status in status bar")
@Slot(BatonStatus)
def update_baton_status(self, status: BatonStatus):
@@ -263,8 +263,8 @@ class StatusBar(QStatusBar):
logger.info("Baton request no longer incoming, closing local dialog reference")
try:
self._baton_request_dialog.close()
- except Exception as e:
- logger.error(f"Error closing baton request dialog: {e}")
+ except Exception:
+ logger.exception("Error closing baton request dialog")
self._baton_request_dialog = None
def _show_pgroup_after_baton_grant(self) -> None:
@@ -537,8 +537,7 @@ class StatusBar(QStatusBar):
try:
self.staff_pgroups_loaded.disconnect(_on_loaded)
except Exception as e:
- logger.debug(f"Error disconnecting: {e}")
- pass
+ logger.debug(f"Error disconnecting: {e}", exc_info=True)
self.staff_pgroups_loaded.connect(_on_loaded)
self._list_staff_pgroups()
@@ -586,7 +585,6 @@ class StatusBar(QStatusBar):
def _list_staff_pgroups(self):
self.get_all_pgroups.emit()
- return
def _generate_pgroup_dialogue(self, curr: str | None = None, pgroups: list | None = None):
logger.info(pgroups)
diff --git a/src/aare/gui/widgets/video_image.py b/src/aare/gui/widgets/video_image.py
index bd03c132..78d9134d 100644
--- a/src/aare/gui/widgets/video_image.py
+++ b/src/aare/gui/widgets/video_image.py
@@ -1,6 +1,6 @@
-from PySide6.QtCore import Qt, Slot, QRectF
-from PySide6.QtGui import QPainter, QPixmap, QImage, QFont, QColor, QPen, QFontMetrics
-from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPixmapItem
+from PySide6.QtCore import QRectF, Qt, Slot
+from PySide6.QtGui import QColor, QFont, QFontMetrics, QImage, QPainter, QPen, QPixmap
+from PySide6.QtWidgets import QGraphicsPixmapItem, QGraphicsScene, QGraphicsView
from aare.gui.widgets.busy_overlay import BusyOverlayStyle
diff --git a/tests/conftest.py b/tests/conftest.py
index c73d1cbc..bba854dc 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -50,7 +50,7 @@ def sample_info():
# -------------------------
@pytest.fixture(scope="session")
def server_module():
- import aare.daq.server as server
+ from aare.daq import server
return server
@@ -90,9 +90,9 @@ def client(server_module, mock_backend, auth_token_data):
patch.object(server_module, "mx_beamline", return_value=mock_backend["bl"]),
patch.object(server_module, "BeamlineConfig", return_value=mock_backend["cfg"]),
patch.object(server_module, "AareDAQ", return_value=mock_backend["daq"]),
+ TestClient(server_module.app) as c,
):
- with TestClient(server_module.app) as c:
- yield c
+ yield c
@pytest.fixture
diff --git a/tests/integration/daq/test_daq_server.py b/tests/integration/daq/test_daq_server.py
index c9f26964..0fc50dca 100644
--- a/tests/integration/daq/test_daq_server.py
+++ b/tests/integration/daq/test_daq_server.py
@@ -1,5 +1,6 @@
-import pytest
import os
+
+import pytest
from fastapi.testclient import TestClient
# We need to set the environment variable before importing the app
diff --git a/tests/unit/daq/operations/face_detection/test_face_detection_logic.py b/tests/unit/daq/operations/face_detection/test_face_detection_logic.py
index 4f430cfb..ca58fd43 100644
--- a/tests/unit/daq/operations/face_detection/test_face_detection_logic.py
+++ b/tests/unit/daq/operations/face_detection/test_face_detection_logic.py
@@ -1,17 +1,18 @@
import numpy as np
import pytest
+
from aare.daq.operations.face_detection.utils import (
- box_height_from_tuple,
box_area_from_tuple,
- prepare_samples,
- cos_model,
- mad_filter,
- fit_metrics,
- fit_cosine,
- get_samples_out,
+ box_height_from_tuple,
choose_best_fit,
- get_flat_face,
chose_best_angle,
+ cos_model,
+ fit_cosine,
+ fit_metrics,
+ get_flat_face,
+ get_samples_out,
+ mad_filter,
+ prepare_samples,
)
@@ -101,7 +102,7 @@ def test_choose_best_fit():
"Height": {"angle": 45, "params": {"rmse": 0.1, "mae": 0.1, "r2": 0.95}},
"Area": {"angle": 50, "params": {"rmse": 0.05, "mae": 0.05, "r2": 0.98}},
}
- angle, fit, name = choose_best_fit(fits)
+ angle, _fit, name = choose_best_fit(fits)
assert name == "Area"
assert angle == 50.0
diff --git a/tests/unit/daq/operations/loop_centering/test_loop_centering_service.py b/tests/unit/daq/operations/loop_centering/test_loop_centering_service.py
index 2dc7e809..8059a644 100644
--- a/tests/unit/daq/operations/loop_centering/test_loop_centering_service.py
+++ b/tests/unit/daq/operations/loop_centering/test_loop_centering_service.py
@@ -88,7 +88,7 @@ def test_service_succeeds_when_correction_pass_has_valid_target(monkeypatch, con
return AngleAnalysis(
angle_deg=kwargs["angle"],
classes=[MLBoxType.CRYSTAL.value] if kwargs["angle"] == 0 else [],
- has_valid_target=True if kwargs["angle"] == 0 else False,
+ has_valid_target=kwargs["angle"] == 0,
ignore_only=False,
)
return AngleAnalysis(
diff --git a/tests/unit/daq/test_aaredb.py b/tests/unit/daq/test_aaredb.py
index 311a352f..e3ab9dc2 100644
--- a/tests/unit/daq/test_aaredb.py
+++ b/tests/unit/daq/test_aaredb.py
@@ -130,7 +130,9 @@ def test_create_manual_sample_error(mock_sample, mock_api, mock_bl, sample_info,
wrapper = AareWrapper(bl=mock_bl)
mock_sample.return_value.insert_sample.side_effect = Exception("DB Error")
wrapper.create_manual_sample(sample_info)
- assert "Error inserting sample: DB Error" in caplog.text
+ assert "Error inserting sample" in caplog.text
+ # the cause now arrives via the logged traceback rather than the message
+ assert "DB Error" in caplog.text
@patch("aareDB.ApiClient")
@@ -348,25 +350,25 @@ def test_ingest_gridscan_payload_none(mock_api, mock_bl, sample_info, geom_model
# Lines 351-352 in aaredb.py: if payload is None: return
# This happens if format_gridscan_payload returns None.
with patch.object(AareWrapper, "format_gridscan_payload", return_value=None):
- wrapper.ingest_gridscan(sample_info, None, None, geom_model, None, (0, 0))
+ wrapper.ingest_gridscan(sample_info, None, None, geom_model, None, (0, 0)) # type: ignore # intended
with patch.object(AareWrapper, "format_scan_payload", return_value=None):
- wrapper.ingest_scan(sample_info, None, geom_model, (0, 0))
+ wrapper.ingest_scan(sample_info, None, geom_model, (0, 0)) # type: ignore # intended
@patch("aareDB.ApiClient")
def test_format_gridscan_payload_error(mock_api, mock_bl, sample_info, caplog):
wrapper = AareWrapper(bl=mock_bl)
# Passing None for geom_model should trigger an error in smargon_to_picture
- with pytest.raises(Exception):
- wrapper.format_gridscan_payload(sample_info, None, None, None, None, (0, 0))
+ with pytest.raises(AttributeError):
+ wrapper.format_gridscan_payload(sample_info, None, None, None, None, (0, 0)) # type: ignore # intended
assert "NoneType" in caplog.text
@patch("aareDB.ApiClient")
def test_format_scan_payload_error(mock_api, mock_bl, sample_info, caplog):
wrapper = AareWrapper(bl=mock_bl)
- with pytest.raises(Exception):
+ with pytest.raises(AttributeError):
# Passing None for geom should trigger error when accessing geom.beam_size_mm
- wrapper.format_scan_payload(sample_info, None, None, (0, 0))
+ wrapper.format_scan_payload(sample_info, None, None, (0, 0)) # type: ignore # intended
assert "NoneType" in caplog.text
diff --git a/tests/unit/daq/test_auth.py b/tests/unit/daq/test_auth.py
index c99ad461..d779a677 100644
--- a/tests/unit/daq/test_auth.py
+++ b/tests/unit/daq/test_auth.py
@@ -97,9 +97,11 @@ def test_parse_token():
def test_parse_token_invalid():
- with patch("aare.daq.auth.jwt_key", return_value="test_secret"):
- with pytest.raises(AuthenticationException):
- parse_token("invalid.token.here")
+ with (
+ patch("aare.daq.auth.jwt_key", return_value="test_secret"),
+ pytest.raises(AuthenticationException),
+ ):
+ parse_token("invalid.token.here")
def test_check_jwt_ro(mock_cfg, token_data):
diff --git a/tests/unit/daq/test_autofocus.py b/tests/unit/daq/test_autofocus.py
index 77e398c0..ce108fbd 100644
--- a/tests/unit/daq/test_autofocus.py
+++ b/tests/unit/daq/test_autofocus.py
@@ -1,5 +1,6 @@
-import numpy as np
import cv2
+import numpy as np
+
from aare.daq.autofocus import calculate_focus_measure
diff --git a/tests/unit/daq/test_beamcenterfit.py b/tests/unit/daq/test_beamcenterfit.py
index c290f09e..8155dcb9 100644
--- a/tests/unit/daq/test_beamcenterfit.py
+++ b/tests/unit/daq/test_beamcenterfit.py
@@ -1,6 +1,7 @@
-import pytest
import numpy as np
-from aare.daq.beamcenterfit import beamcenter_fit, Gaussian2Dfit
+import pytest
+
+from aare.daq.beamcenterfit import Gaussian2Dfit, beamcenter_fit
def create_synthetic_beam_image(
@@ -50,7 +51,6 @@ def test_beamcenter_fit_no_converge():
# but we want to test the failure path.
# To truly force non-convergence we might need a more extreme case,
# but return None is better than exit() anyway.
- pass
def test_beamcenter_fit_no_contours():
diff --git a/tests/unit/daq/test_face_detection.py b/tests/unit/daq/test_face_detection.py
index e1075e09..72a55736 100644
--- a/tests/unit/daq/test_face_detection.py
+++ b/tests/unit/daq/test_face_detection.py
@@ -77,20 +77,11 @@ def test_public_face_detection_uses_execute_face_detection(monkeypatch):
daq = object.__new__(AareDAQ)
cfg = types.SimpleNamespace(try_set_busy=lambda timeout=360: None, state_busy=False)
- setattr(daq, "_cfg", cfg)
+ daq._cfg = cfg
- setattr(
- daq,
- "_execute_face_detection",
- lambda **kwargs: FaceDetectionResult(
- success=True,
- payload={
- "running": False,
- "samples": [{"angle": 45}],
- "height_fit": {},
- "area_fit": {},
- },
- ),
+ daq._execute_face_detection = lambda **kwargs: FaceDetectionResult(
+ success=True,
+ payload={"running": False, "samples": [{"angle": 45}], "height_fit": {}, "area_fit": {}},
)
result = daq.face_detection(steps=7, step_size=30)
diff --git a/tests/unit/daq/test_mlbox.py b/tests/unit/daq/test_mlbox.py
index 98ef9a9e..650e8c09 100644
--- a/tests/unit/daq/test_mlbox.py
+++ b/tests/unit/daq/test_mlbox.py
@@ -70,7 +70,7 @@ def test_filter_predictions(mlbox):
mlbox._filter_predictions(out, confidence_min=0.5)
assert len(out.boxes) == 1
- assert list(out.boxes.values())[0].conf == 0.8
+ assert next(iter(out.boxes.values())).conf == 0.8
@patch("cv2.imdecode")
diff --git a/tests/unit/daq/test_mount.py b/tests/unit/daq/test_mount.py
index 808272fd..2abdc2f1 100644
--- a/tests/unit/daq/test_mount.py
+++ b/tests/unit/daq/test_mount.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
from types import SimpleNamespace
from unittest.mock import MagicMock
@@ -61,13 +61,13 @@ def test_was_previous_sample_unmounted_since_prefers_tell_phase_confirmation():
previous_sample = _make_sample(1, "old")
daq = _make_daq(previous_sample)
- started_at = datetime.now(timezone.utc) - timedelta(seconds=5)
+ started_at = datetime.now(UTC) - timedelta(seconds=5)
daq._safe_tell_state = MagicMock(
return_value=TellStateModel(
activity=TellActivityEnum.MOUNTING,
operation="mount",
phase=TellPhaseEnum.PICKING_NEW_SAMPLE,
- last_update_ts=datetime.now(timezone.utc).isoformat(),
+ last_update_ts=datetime.now(UTC).isoformat(),
last_event_class="Motion Sync",
last_event_value="Sample get on Puck",
)
diff --git a/tests/unit/daq/test_server.py b/tests/unit/daq/test_server.py
index 7a535775..c1343cad 100644
--- a/tests/unit/daq/test_server.py
+++ b/tests/unit/daq/test_server.py
@@ -2,7 +2,6 @@ import os
from types import SimpleNamespace
from unittest.mock import patch
-
os.environ["JWT_AAREDAQ_KEY"] = "test_key_for_unit_testing"
diff --git a/tests/unit/daq/test_spreadsheetupdater.py b/tests/unit/daq/test_spreadsheetupdater.py
index 2255bb87..56ce46d9 100644
--- a/tests/unit/daq/test_spreadsheetupdater.py
+++ b/tests/unit/daq/test_spreadsheetupdater.py
@@ -25,9 +25,8 @@ def test_get_ws_headers_success():
def test_get_ws_headers_fail():
- with patch("os.getenv", return_value=None):
- with pytest.raises(ValueError):
- get_ws_headers()
+ with patch("os.getenv", return_value=None), pytest.raises(ValueError):
+ get_ws_headers()
def test_set_spreadsheet_in_redis(mock_config):
diff --git a/tests/unit/daq/test_tellupdater.py b/tests/unit/daq/test_tellupdater.py
index 2fbfc91a..894325c5 100644
--- a/tests/unit/daq/test_tellupdater.py
+++ b/tests/unit/daq/test_tellupdater.py
@@ -180,8 +180,10 @@ def test_on_message(mock_ws_app):
}
]
)
- with patch("aare.daq.tellupdater.ws_update_samples_info") as mock_update:
- with patch("aare.daq.tellupdater.handle_tell_change_event"):
- tellupdater.current_pucks = []
- tellupdater.on_message(None, message)
- mock_update.assert_called_once()
+ with (
+ patch("aare.daq.tellupdater.ws_update_samples_info") as mock_update,
+ patch("aare.daq.tellupdater.handle_tell_change_event"),
+ ):
+ tellupdater.current_pucks = []
+ tellupdater.on_message(None, message)
+ mock_update.assert_called_once()
diff --git a/tests/unit/devices/test_enum_pv.py b/tests/unit/devices/test_enum_pv.py
index 6ae6cd4d..89658a5e 100644
--- a/tests/unit/devices/test_enum_pv.py
+++ b/tests/unit/devices/test_enum_pv.py
@@ -1,6 +1,8 @@
-import pytest
-from unittest.mock import patch
from enum import Enum
+from unittest.mock import patch
+
+import pytest
+
from aare.devices.enum_pv import EnumPV
@@ -29,14 +31,14 @@ def mock_pvs():
def test_enum_pv_init_fail(mock_pvs):
- set_pv, get_pv = mock_pvs
+ set_pv, _get_pv = mock_pvs
set_pv.enum_strs = None
with pytest.raises(RuntimeError):
EnumPV("test", "SET", "GET")
def test_enum_pv_init_success(mock_pvs):
- set_pv, get_pv = mock_pvs
+ set_pv, _get_pv = mock_pvs
set_pv.enum_strs = ("State1", "State2")
epv = EnumPV("test", "SET", "GET")
assert epv.name == "test"
@@ -51,7 +53,7 @@ def test_enum_pv_position(mock_pvs):
def test_enum_pv_resolve_enum(mock_pvs):
- set_pv, get_pv = mock_pvs
+ set_pv, _get_pv = mock_pvs
set_pv.enum_strs = ("State1", "State2")
epv = EnumPV("test", "SET", "GET")
@@ -63,7 +65,7 @@ def test_enum_pv_resolve_enum(mock_pvs):
def test_enum_pv_resolve_int(mock_pvs):
- set_pv, get_pv = mock_pvs
+ set_pv, _get_pv = mock_pvs
set_pv.enum_strs = ("State1", "State2")
epv = EnumPV("test", "SET", "GET")
@@ -75,7 +77,7 @@ def test_enum_pv_resolve_int(mock_pvs):
def test_enum_pv_resolve_str(mock_pvs):
- set_pv, get_pv = mock_pvs
+ set_pv, _get_pv = mock_pvs
set_pv.enum_strs = (" State1 ", "State2")
epv = EnumPV("test", "SET", "GET")
@@ -87,7 +89,7 @@ def test_enum_pv_resolve_str(mock_pvs):
def test_enum_pv_resolve_invalid_type(mock_pvs):
- set_pv, get_pv = mock_pvs
+ set_pv, _get_pv = mock_pvs
set_pv.enum_strs = ("State1", "State2")
epv = EnumPV("test", "SET", "GET")
with pytest.raises(TypeError):
diff --git a/tests/unit/devices/test_mx_lib.py b/tests/unit/devices/test_mx_lib.py
index e7158f1c..4baeb224 100644
--- a/tests/unit/devices/test_mx_lib.py
+++ b/tests/unit/devices/test_mx_lib.py
@@ -1,7 +1,9 @@
-import pytest
from unittest.mock import MagicMock, patch
-from aare.devices.mx_lib import wait_for_movement_to_finish, pv_wait, is_epics_type, clean_filename
-from epics import Motor, PV
+
+import pytest
+from epics import PV, Motor
+
+from aare.devices.mx_lib import clean_filename, is_epics_type, pv_wait, wait_for_movement_to_finish
def test_clean_filename():
@@ -47,9 +49,8 @@ def test_wait_for_movement_to_finish_timeout(mock_poll):
mock_motor.units = "mm"
mock_motor._prefix = "MOT1:"
- with patch("time.time", side_effect=[0, 0, 100, 101]):
- with pytest.raises(TimeoutError):
- wait_for_movement_to_finish(mock_motor)
+ with patch("time.time", side_effect=[0, 0, 100, 101]), pytest.raises(TimeoutError):
+ wait_for_movement_to_finish(mock_motor)
@patch("aare.devices.mx_lib.wait_motor_position")
diff --git a/tests/unit/devices/test_my_motor.py b/tests/unit/devices/test_my_motor.py
index eefd4cad..c6cb1258 100644
--- a/tests/unit/devices/test_my_motor.py
+++ b/tests/unit/devices/test_my_motor.py
@@ -34,7 +34,7 @@ def test_my_motor_init(mock_motor_base):
def test_my_motor_speed(mock_motor_base):
- _, mock_get, mock_put, _ = mock_motor_base
+ _, mock_get, _mock_put, _ = mock_motor_base
m = MyMotor("MTR")
with patch.object(MyMotor, "name", create=True, new_callable=PropertyMock) as mock_name:
mock_name.return_value = "MTR"
@@ -94,7 +94,7 @@ def test_my_motor_units(mock_motor_base):
def test_my_motor_limits(mock_motor_base):
- _, mock_get, mock_put, _ = mock_motor_base
+ _, mock_get, _mock_put, _ = mock_motor_base
m = MyMotor("MTR")
with patch.object(MyMotor, "name", create=True, new_callable=PropertyMock) as mock_name:
mock_name.return_value = "MTR"
diff --git a/tests/unit/devices/test_workflow_tools.py b/tests/unit/devices/test_workflow_tools.py
index d25419a0..c8136951 100644
--- a/tests/unit/devices/test_workflow_tools.py
+++ b/tests/unit/devices/test_workflow_tools.py
@@ -1,4 +1,5 @@
import pytest
+
from aare.devices.workflow_tools import wait_position
diff --git a/tests/unit/gui/test_auth_mock.py b/tests/unit/gui/test_auth_mock.py
index 8685fa43..836ef743 100644
--- a/tests/unit/gui/test_auth_mock.py
+++ b/tests/unit/gui/test_auth_mock.py
@@ -15,7 +15,7 @@ def test_auth_success(mocker):
assert token == "fake_token_abc.123.xyz"
mock_run.assert_called_once()
- args, kwargs = mock_run.call_args
+ args, _kwargs = mock_run.call_args
assert "curl" in args[0]
assert "--cacert" in args[0]
assert "/tmp/test-cert.pem" in args[0]
diff --git a/tests/unit/gui/test_axis_video_thread.py b/tests/unit/gui/test_axis_video_thread.py
index 828fb594..44ec9270 100644
--- a/tests/unit/gui/test_axis_video_thread.py
+++ b/tests/unit/gui/test_axis_video_thread.py
@@ -1,8 +1,10 @@
-import pytest
-import numpy as np
-import cv2
from unittest.mock import MagicMock, patch
+
+import cv2
+import numpy as np
+import pytest
from PySide6.QtGui import QImage
+
from aare.gui.threads.axis_video_thread import VideoThread
diff --git a/tests/unit/gui/test_login.py b/tests/unit/gui/test_login.py
index 0b761a1f..12f2d50c 100644
--- a/tests/unit/gui/test_login.py
+++ b/tests/unit/gui/test_login.py
@@ -1,8 +1,10 @@
+from unittest.mock import MagicMock, patch
+
+import jwt
import pytest
from PySide6.QtCore import Qt
+
from aare.gui.widgets.login import LoginDialog
-from unittest.mock import MagicMock, patch
-import jwt
@pytest.fixture
diff --git a/tests/unit/gui/test_message_box.py b/tests/unit/gui/test_message_box.py
index 8f2464fd..5ed7923f 100644
--- a/tests/unit/gui/test_message_box.py
+++ b/tests/unit/gui/test_message_box.py
@@ -1,13 +1,15 @@
from unittest.mock import MagicMock, patch
-from PySide6.QtWidgets import QMessageBox
-from aare.gui.widgets.message_box import (
- reply_box,
- timer_box,
- ring_current_low_check,
- experiment_hutch_shutter_check,
- ring_current_auto_check,
-)
+
import pytest
+from PySide6.QtWidgets import QMessageBox
+
+from aare.gui.widgets.message_box import (
+ experiment_hutch_shutter_check,
+ reply_box,
+ ring_current_auto_check,
+ ring_current_low_check,
+ timer_box,
+)
def test_reply_box(qtbot):
diff --git a/tests/unit/gui/test_sse_client.py b/tests/unit/gui/test_sse_client.py
index b1e20f1d..866c208d 100644
--- a/tests/unit/gui/test_sse_client.py
+++ b/tests/unit/gui/test_sse_client.py
@@ -1,8 +1,10 @@
+from unittest.mock import MagicMock
+
import pytest
from PySide6.QtCore import QByteArray
from PySide6.QtNetwork import QNetworkReply
+
from aare.gui.threads.sse_client import SSEClient
-from unittest.mock import MagicMock
@pytest.fixture
diff --git a/tests/unit/gui/test_tutorials.py b/tests/unit/gui/test_tutorials.py
index 2955477e..3f049cfb 100644
--- a/tests/unit/gui/test_tutorials.py
+++ b/tests/unit/gui/test_tutorials.py
@@ -1,19 +1,20 @@
import pytest
from PySide6.QtWidgets import QWidget
+
from aare.gui.tutorials.tutorial_manager import TutorialManager
from aare.gui.tutorials.tutorial_models import (
+ StepKind,
+ TargetKind,
+ TutorialMode,
TutorialScenario,
TutorialStepDefinition,
- TutorialMode,
- StepKind,
- TutorialTextRef,
TutorialTarget,
- TargetKind,
+ TutorialTextRef,
)
from aare.gui.tutorials.tutorial_runtime import (
DictionaryTextResolver,
- NoOpActionExecutor,
DictTargetResolver,
+ NoOpActionExecutor,
TutorialEventBus,
)
diff --git a/tests/unit/utils/test_beamline_dispatch.py b/tests/unit/utils/test_beamline_dispatch.py
index fa53ef6f..8feadae6 100644
--- a/tests/unit/utils/test_beamline_dispatch.py
+++ b/tests/unit/utils/test_beamline_dispatch.py
@@ -1,19 +1,16 @@
-import importlib
+import importlib.util
from unittest.mock import patch
import pytest
from aare.beamline_dispatch.beamline_dispatch import get_beamline_dispatch
from aare.beamline_dispatch.simulated import SimulatedDispatch
-from aare.beamline_dispatch.x06da.beamline_dispatch import X06daDispatch
-from aare.beamline_dispatch.x06sa import X06saDispatch
from aare.beamline_dispatch.x10sa import X10saDispatch
def test_dispatch_cannot_be_instantiated_without_env():
- with patch.dict("os.environ", {}, clear=True):
- with pytest.raises(ValueError) as e:
- _ = get_beamline_dispatch()
+ with patch.dict("os.environ", {}, clear=True), pytest.raises(ValueError) as e:
+ _ = get_beamline_dispatch()
assert e.match("Please set the BEAMLINE environment variable")
@@ -25,9 +22,8 @@ def test_simulated_dispatch_can_be_instantiated():
@pytest.mark.skipif(importlib.util.find_spec("pxi_bec") is None, reason="run only for pxi flavour")
def test_pxi_dispatch_can_be_instantiated():
- with patch.dict("os.environ", {"BEAMLINE": "X06SA"}):
- with pytest.raises(TypeError) as e:
- _ = get_beamline_dispatch()
+ with patch.dict("os.environ", {"BEAMLINE": "X06SA"}), pytest.raises(TypeError) as e:
+ _ = get_beamline_dispatch()
assert e.match("Can't instantiate abstract class")
@@ -44,7 +40,6 @@ def test_pxii_dispatch_can_be_instantiated():
importlib.util.find_spec("pxiii_bec") is None, reason="run only for pxiii flavour"
)
def test_pxiii_dispatch_can_be_instantiated():
- with patch.dict("os.environ", {"BEAMLINE": "X06DA"}):
- with pytest.raises(TypeError) as e:
- _ = get_beamline_dispatch()
+ with patch.dict("os.environ", {"BEAMLINE": "X06DA"}), pytest.raises(TypeError) as e:
+ _ = get_beamline_dispatch()
assert e.match("Can't instantiate abstract class")