diff --git a/.gitea/actions/install/action.yml b/.gitea/actions/install/action.yml new file mode 100644 index 00000000..ec0fdc9f --- /dev/null +++ b/.gitea/actions/install/action.yml @@ -0,0 +1,46 @@ +name: "Install" +description: "Setup system and python environment and install repo" +inputs: + python_version: + required: false + default: "3.12" + description: "Python version to use" + index_username: + required: false + default: "" + description: "Username for the private psi package index" + index_password: + required: false + default: "" + description: "Token/password for the private psi package index" + +runs: + using: "composite" + steps: + - name: Install Libraries + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y \ + libgl1 \ + libegl1 \ + libxkbcommon-x11-0 \ + libdbus-1-3 \ + libxcb-cursor0 \ + libx11-xcb1 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: ${{ inputs.python_version }} + + - name: Install repo + shell: bash + env: + UV_INDEX_PSI_USERNAME: ${{ inputs.index_username }} + UV_INDEX_PSI_PASSWORD: ${{ inputs.index_password }} + run: | + pip install uv + uv venv + source .venv/bin/activate + uv pip install -e .[test] diff --git a/.gitea/workflows/action.yaml b/.gitea/workflows/action.yaml deleted file mode 100644 index c8d115f4..00000000 --- a/.gitea/workflows/action.yaml +++ /dev/null @@ -1,223 +0,0 @@ -name: Build and Publish -on: - push: - tags: - - '*' - branches: - - master - - deploy - - 'feature/**' - - gitea-pages - pull_request: - branches: - - master - - deploy - pull_request_target: - types: [closed] - -env: - DEPLOY_BRANCH: gitea-pages - PREVIEWS_DIR: previews - PYTHONPATH: src - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Set Up Python - uses: actions/setup-python@v4 - with: - python-version: '3.12' - - - name: Cache pip - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-test-${{ hashFiles('**/pyproject.toml') }} - restore-keys: | - ${{ runner.os }}-pip-test- - - - name: Create virtual environment - run: | - python -m venv venv - - - name: Install dependencies - env: - PIP_INDEX_URL: https://${{ secrets.GITEA_USER }}:${{ secrets.GITEA_TOKEN }}@gitea.psi.ch/api/packages/mx/pypi/simple/ - PIP_EXTRA_INDEX_URL: https://pypi.org/simple - run: | - source venv/bin/activate - - python -m pip install --upgrade pip - - pip install pytest pytest-cov pytest-mock pytest-qt pytest-asyncio - - pip install -e . - - - name: Install system dependencies for PySide6 - run: | - sudo apt-get update - sudo apt-get install -y \ - libgl1 \ - libegl1 \ - libxkbcommon-x11-0 \ - libdbus-1-3 \ - libxcb-cursor0 \ - libx11-xcb1 - - - name: Debug Python env - run: | - source venv/bin/activate - which python - which pytest - python -m pip list | grep pytest - - - name: Run tests with pytest - env: - QT_QPA_PLATFORM: offscreen - BEAMLINE: SIMULATED - run: | - source venv/bin/activate - - python -m pytest --cov=aare --cov-report=xml tests/unit - - build: - runs-on: ubuntu-latest - needs: test - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Cache pip - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }} - - - name: Set Up Python - uses: actions/setup-python@v4 - with: - python-version: '3.12' - - - name: Set Up Python Environment - run: | - python3 -m venv venv # Create a virtual environment - source venv/bin/activate - pip install twine build - - - name: Build the package - run: | - source venv/bin/activate - python -m build - - - name: Upload Package to Gitea PyPI - if: startsWith(github.ref, 'refs/tags/') - env: - TWINE_USERNAME: "__token__" # Username for Twine when using token-based auth - TWINE_PASSWORD: ${{ secrets.PIP_REPOSITORY_API_TOKEN }} # Use the secret for authentication - run: | - source venv/bin/activate - twine upload --repository-url https://gitea.psi.ch/api/packages/mx/pypi \ - dist/* - - docs: - name: Build and Deploy Docs - runs-on: ubuntu-latest - needs: [build, test] - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 # needed for pushing branches/tags - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.12' - - - name: Build Sphinx HTML - working-directory: docs - run: | - python3 -m venv venv - source venv/bin/activate - - pip install --upgrade pip - pip install -r requirements.txt - - pip install -e .. --no-deps - - sphinx-apidoc -o modules/ ../src/aare --separate --module-first --force --remove-old - - sphinx-build -b html . _build/html - - - name: Deploy to gitea-pages branch (Gitea) - env: - GIT_USER: "Martin Appleby (Gitea)" - GIT_EMAIL: "martin.appleby@psi.ch" - DEPLOY_TOKEN: ${{ secrets.DOCS_DEPLOY_TOKEN }} - run: | - set -e - git config --global user.name "$GIT_USER" - git config --global user.email "$GIT_EMAIL" - - # Deploy strategy: create/update branch gitea-pages and replace its contents - # with the generated daq/docs/_build/html. This keeps only the documentation - # on the gitea-pages branch. - # Confirm build output exists (relative to repository root) - BUILD_DIR="docs/_build/html" - echo "Checking build output at: $BUILD_DIR" - - if [ ! -d "$BUILD_DIR" ] || [ -z "$(ls -A "$BUILD_DIR")" ]; then - echo "ERROR: $BUILD_DIR does not exist or is empty" - echo "Listing docs/:" - ls -la docs || true - echo "Listing docs/_build:" - ls -la docs/_build || true - exit 1 - fi - - # Work in a temporary clone to avoid touching the runner workspace - TMPDIR=$(mktemp -d) - REPO_URL="https://$DEPLOY_TOKEN@gitea.psi.ch/${{ github.repository }}" - git clone "$REPO_URL" "$TMPDIR" - cd "$TMPDIR" - - # Create or switch to orphan branch gitea-pages - if git rev-parse --verify --quiet gitea-pages >/dev/null; then - git checkout gitea-pages - # Make sure working tree matches branch (hard reset) - git fetch --prune origin gitea-pages || true - git reset --hard origin/gitea-pages || git reset --hard - else - git checkout --orphan gitea-pages - fi - # Remove all files tracked in the branch (leave .git) - git rm -rf . || true - # Also remove untracked files to ensure clean branch state - git clean -fdx || true - - # Copy built HTML into the clone root - echo "Copying built HTML into branch root..." - (cd "${GITHUB_WORKSPACE}/${BUILD_DIR}" && tar -cf - .) | tar -xf - -C "$TMPDIR" - - # Ensure index.html exists in destination as sanity check - if [ ! -f "$TMPDIR/index.html" ] && [ ! -f "$TMPDIR/index.htm" ]; then - # If site root index is not present, try to check subfolder - echo "Warning: no index.html found at branch root after copy. Listing files:" - ls -la "$TMPDIR" || true - fi - - git add --all - if git diff --quiet --cached; then - echo "No changes to deploy" - exit 0 - fi - COMMIT_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" - git commit -m "docs: (${COMMIT_DATE}) | auto-update documentation from ${GITHUB_SHA}" - git push --force-with-lease origin gitea-pages diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 00000000..5353b233 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,51 @@ +name: CI +on: + push: + pull_request: + workflow_dispatch: + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup + uses: ./.gitea/actions/install + with: + python_version: "3.12" + index_username: ${{ secrets.GITEA_USER }} + index_password: ${{ secrets.GITEA_TOKEN }} + + - name: Format + run: | + source .venv/bin/activate + ruff format --check + + 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 + + - name: Setup + uses: ./.gitea/actions/install + with: + python_version: ${{ matrix.python-version }} + index_username: ${{ secrets.GITEA_USER }} + index_password: ${{ secrets.GITEA_TOKEN }} + + - name: Run Pytest with Coverage + env: + QT_QPA_PLATFORM: offscreen + BEAMLINE: SIMULATED + run: | + source .venv/bin/activate + pytest --cov=aare --cov-config=./pyproject.toml --cov-branch --cov-report=xml --no-cov-on-fail ./tests/unit diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml new file mode 100644 index 00000000..a0943835 --- /dev/null +++ b/.gitea/workflows/publish.yml @@ -0,0 +1,77 @@ +name: Build and Publish +on: + push: + branches: + - main + +jobs: + release: + runs-on: ubuntu-latest + concurrency: + group: ${{ github.workflow }}-release-${{ github.ref_name }} + cancel-in-progress: false + outputs: + release_made: ${{ steps.release_step.outputs.release_made }} + permissions: + contents: write + steps: + - name: Setup | Checkout Repository on Release Branch + uses: actions/checkout@v6 + with: + ref: ${{ github.ref_name }} + fetch-depth: 0 + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + + - name: Setup | Force release branch to be at workflow sha + run: | + git reset --hard ${{ github.sha }} + - name: Evaluate | Verify upstream has NOT changed + shell: bash + run: | + set +o pipefail + + UPSTREAM_BRANCH_NAME="$(git status -sb | head -n 1 | cut -d' ' -f2 | grep -E '\.{3}' | cut -d'.' -f4)" + printf '%s\n' "Upstream branch name: $UPSTREAM_BRANCH_NAME" + + set -o pipefail + + if [ -z "$UPSTREAM_BRANCH_NAME" ]; then + printf >&2 '%s\n' "::error::Unable to determine upstream branch name!" + exit 1 + fi + + git fetch "${UPSTREAM_BRANCH_NAME%%/*}" + + if ! UPSTREAM_SHA="$(git rev-parse "$UPSTREAM_BRANCH_NAME")"; then + printf >&2 '%s\n' "::error::Unable to determine upstream branch sha!" + exit 1 + fi + + HEAD_SHA="$(git rev-parse HEAD)" + + if [ "$HEAD_SHA" != "$UPSTREAM_SHA" ]; then + printf >&2 '%s\n' "[HEAD SHA] $HEAD_SHA != $UPSTREAM_SHA [UPSTREAM SHA]" + printf >&2 '%s\n' "::error::Upstream has changed, aborting release..." + exit 1 + fi + + printf '%s\n' "Verified upstream branch has not changed, continuing with release..." + + - name: Semantic Version Release + id: release_step + env: + TWINE_USERNAME: "__token__" + TWINE_PASSWORD: ${{ secrets.MX_GITEA_BOT_TOKEN }} + MX_GITEA_BOT_TOKEN: ${{ secrets.MX_GITEA_BOT_TOKEN }} + run: | + pip install python-semantic-release==9.* wheel build twine + semantic-release --config ./ci/semantic_release.toml version + if [ ! -d dist ]; then echo No release will be made; echo "release_made=false" >> "$GITHUB_OUTPUT"; exit 0; fi + echo "release_made=true" >> "$GITHUB_OUTPUT" + twine upload dist/* --repository-url https://gitea.psi.ch/api/packages/mx/pypi + semantic-release publish diff --git a/CHANGELOG.md b/LEGACY_CHANGELOG.md similarity index 99% rename from CHANGELOG.md rename to LEGACY_CHANGELOG.md index 851af359..893dc60f 100644 --- a/CHANGELOG.md +++ b/LEGACY_CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog + +## Hand-written changelog is now superseded by the generated one in CHANGELOG.md; preserved for posterity. + ## 0.3.2 (unreleased) - **Authentication & Sessions**: - Migrated DAQ server URLs to HTTPS with per-beamline configurable certificate paths (cacert) and mTLS; GUI now validates certificates and handles self-signed certs / SSL errors in `DAQWorker` and `SSEClient`. diff --git a/VERSION b/VERSION deleted file mode 100644 index d15723fb..00000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.3.2 diff --git a/ci/semantic_release.toml b/ci/semantic_release.toml new file mode 100644 index 00000000..ededef83 --- /dev/null +++ b/ci/semantic_release.toml @@ -0,0 +1,35 @@ +[tool.semantic_release] +build_command = "python -m build" +version_toml = [ + "./pyproject.toml:project.version", +] + +[tool.semantic_release.commit_author] +env = "GIT_COMMIT_AUTHOR" +default = "semantic-release " + +[tool.semantic_release.commit_parser_options] +allowed_tags = [ + "build", + "chore", + "ci", + "docs", + "feat", + "fix", + "perf", + "style", + "refactor", + "test", +] +minor_tags = ["feat"] +patch_tags = ["fix", "perf"] +default_bump_level = 0 + +[tool.semantic_release.remote] +type = "gitea" +domain = "gitea.psi.ch" +token = { env = "MX_GITEA_BOT_TOKEN" } + +[tool.semantic_release.publish] +dist_glob_patterns = ["dist/*"] +upload_to_vcs_release = true diff --git a/pyproject.toml b/pyproject.toml index e254d7a0..9b3b0d9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,8 @@ test = [ "pytest-mock==3.14.0", "pytest-qt==4.4.0", "pytest-asyncio==0.25.3", - "pytest-timeout" + "pytest-timeout", + "ruff==0.15.*" ] [lint] diff --git a/scripts/redis_dump.py b/scripts/redis_dump.py index 0d31b97a..8db28f8f 100644 --- a/scripts/redis_dump.py +++ b/scripts/redis_dump.py @@ -20,7 +20,7 @@ def decode_bulk(value: Any): def fetch_key(r: redis.Redis, key: bytes) -> Tuple[str, Any]: - key_s = decode_bulk(key) + decode_bulk(key) t = r.type(key) if isinstance(t, bytes): t = t.decode() diff --git a/src/aare/daq/aaredb.py b/src/aare/daq/aaredb.py index 3cd5f535..b2b57cca 100644 --- a/src/aare/daq/aaredb.py +++ b/src/aare/daq/aaredb.py @@ -229,7 +229,7 @@ class AareWrapper: experiment_params_payload = ExperimentParametersCreate( type="standard", beamline_parameters=beamline_params, sample_id=s.db_id ) - response = self.__sample_api.create_experiment_parameters_for_sample( + self.__sample_api.create_experiment_parameters_for_sample( sample_id=s.db_id, experiment_parameters_create=experiment_params_payload ) # logger.debug("Experiment parameters created:", response) @@ -291,7 +291,7 @@ class AareWrapper: experiment_params_payload = ExperimentParametersCreate( type="standard", beamline_parameters=beamline_params, sample_id=s.db_id ) - response = self.__sample_api.create_experiment_parameters_for_sample( + self.__sample_api.create_experiment_parameters_for_sample( sample_id=s.db_id, experiment_parameters_create=experiment_params_payload ) # logger.info("Experiment parameters created:", response) diff --git a/src/aare/daq/config.py b/src/aare/daq/config.py index 63268f58..0f80f7c8 100644 --- a/src/aare/daq/config.py +++ b/src/aare/daq/config.py @@ -42,11 +42,8 @@ from aarecommon.models.models import ( ) from aare.daq.config_model import LocalContactConfigModel -#TODO WHAT SHOULD THIS BE? This should be in the YAMl file it is beamline specific -ABR_POS_MOUNT = AerotechCoordinate( - at_mm=Coordinate(x=0, y=0, z=0), - omega_deg=0 -) +# TODO WHAT SHOULD THIS BE? This should be in the YAMl file it is beamline specific +ABR_POS_MOUNT = AerotechCoordinate(at_mm=Coordinate(x=0, y=0, z=0), omega_deg=0) # TODO WHAT SHOULD THIS BE? This should be in the YAMl file it is beamline specific @@ -1261,7 +1258,9 @@ class BeamlineConfig: logger.warning(f"Failed to read Local Contact config from Redis: {e}") return default - def set_local_contact_config(self, config: LocalContactConfigModel | dict) -> LocalContactConfigModel: + def set_local_contact_config( + self, config: LocalContactConfigModel | dict + ) -> LocalContactConfigModel: validated = LocalContactConfigModel.model_validate(config) try: redis_key = f"{self.__bl}:local_contact_config" diff --git a/src/aare/daq/daq.py b/src/aare/daq/daq.py index 50e91933..875ddfe5 100644 --- a/src/aare/daq/daq.py +++ b/src/aare/daq/daq.py @@ -1385,8 +1385,9 @@ class AareDAQ: ) return None - - def _execute_rotation_sequence(self, rotation_request: RotationScanRequest) -> CompletedRotationScan | None: + def _execute_rotation_sequence( + self, rotation_request: RotationScanRequest + ) -> CompletedRotationScan | None: """ Execute rotation scan. @@ -1574,19 +1575,21 @@ class AareDAQ: self.last_time = end - start def _expand_macros(self, name: str) -> str: - name = name.replace('{date}', datetime.now().strftime('%Y%m%d')) - name = name.replace('{sample}', self.sample.sample_name) - name = name.replace('{CrystalName}', self.sample.sample_name) - name = name.replace('{puck}', self.sample.puck_name) - name = name.replace('{position}', f"{self.sample.pin:02d}") - name = name.replace('{sample_id}', f"{self.sample.db_id}") - name = name.replace('{beamline}', f"{self._beamline.value.lower()}") - name = name.replace('{prefix}', f"{self.sample.puck_name}/{self.sample.pin:02d}/{self.sample.sample_name}") - #TODO work out why clean_filename is removing slahses - #name = clean_filename(name) + name = name.replace("{date}", datetime.now().strftime("%Y%m%d")) + name = name.replace("{sample}", self.sample.sample_name) + name = name.replace("{CrystalName}", self.sample.sample_name) + name = name.replace("{puck}", self.sample.puck_name) + name = name.replace("{position}", f"{self.sample.pin:02d}") + name = name.replace("{sample_id}", f"{self.sample.db_id}") + name = name.replace("{beamline}", f"{self._beamline.value.lower()}") + name = name.replace( + "{prefix}", f"{self.sample.puck_name}/{self.sample.pin:02d}/{self.sample.sample_name}" + ) + # TODO work out why clean_filename is removing slahses + # name = clean_filename(name) return name - def spreadsheet_params(self) -> tuple[Optional[SimpleScanParameters], str|None]: + def spreadsheet_params(self) -> tuple[Optional[SimpleScanParameters], str | None]: file_prefix = None if self.status.sample is None: @@ -1979,7 +1982,7 @@ class AareDAQ: self.save_screenshot_db(sample_id, screenshot_name) if request.transmission is not None and request.transmission != self.__devs.transmission: - logger.info(f'requesting transmission to move to {request.transmission}') + logger.info(f"requesting transmission to move to {request.transmission}") self.__devs.transmission = request.transmission start_pos = getattr(request, "start", None) @@ -2700,7 +2703,9 @@ 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 = "{filename}/{prefix}".format( + filename=raster_params.filename, prefix=sample.sample_name + ) raster_grid = RasterGridRequest( exp_time_s=raster_params.exp_time_s, @@ -2767,17 +2772,20 @@ class AareDAQ: update_sample = self.sample if ( - update_sample is not None - and update_sample.db_id is not None - and update_sample.db_id == sample.db_id + update_sample is not None + and update_sample.db_id is not None + and update_sample.db_id == sample.db_id ): - logger.info(f"Updating sample info {sample.db_id} old run_number" - f" sample.run_number {sample.run_number} new run_number {update_sample.run_number}") - + logger.info( + f"Updating sample info {sample.db_id} old run_number" + f" sample.run_number {sample.run_number} new run_number {update_sample.run_number}" + ) 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 = "{filename}/{prefix}".format( + filename=params.filename, prefix=sample.sample_name + ) rotation_request = RotationScanRequest( start_omega_deg=start_omega, @@ -3492,7 +3500,9 @@ class AareDAQ: finally: self.__cfg.state_busy = False - def fluorimeter_take_spectrum(self, fm: FluorescenceSpectrumParameterModel) -> FluorescenceSpectrumOutputModel: + def fluorimeter_take_spectrum( + self, fm: FluorescenceSpectrumParameterModel + ) -> FluorescenceSpectrumOutputModel: self.__cfg.try_set_busy(timeout=360) try: diff --git a/src/aare/daq/operations/common/ml_bounding_box.py b/src/aare/daq/operations/common/ml_bounding_box.py index 7ca9be18..be364a20 100644 --- a/src/aare/daq/operations/common/ml_bounding_box.py +++ b/src/aare/daq/operations/common/ml_bounding_box.py @@ -3,7 +3,6 @@ from dataclasses import dataclass from math import ceil, floor from typing import Callable -import cv2 from aarecommon.config.beamline import cfg_get from aarecommon.config.logger_events import ( geom_log_context, @@ -112,7 +111,6 @@ def get_ml_bounding_box( preferred_class=(3, 0), return_image=True, return_bundle_meta=True ) m = prediction_result.box - bundle_image = prediction_result.image log_ml_bundle_meta( logger, diff --git a/src/aare/daq/operations/face_detection/service.py b/src/aare/daq/operations/face_detection/service.py index 880d3abc..8e91d7aa 100644 --- a/src/aare/daq/operations/face_detection/service.py +++ b/src/aare/daq/operations/face_detection/service.py @@ -53,7 +53,6 @@ class FaceDetectionService: beam_y = geom.beam_location_pxl.y beam_x = geom.beam_location_pxl.x - x1 = model.box.top_x y1 = model.box.top_y y2 = model.box.bottom_y diff --git a/src/aare/daq/server.py b/src/aare/daq/server.py index ef2c9717..068304a6 100644 --- a/src/aare/daq/server.py +++ b/src/aare/daq/server.py @@ -1,7 +1,6 @@ import asyncio import hmac import importlib -import io import json import os import random @@ -9,7 +8,6 @@ import time from contextlib import asynccontextmanager from typing import AsyncGenerator, Optional -import cv2 import uvicorn from aarecommon.config.beamline import mx_beamline from aarecommon.config.logger import get_uvicorn_logging_config, setup_logger diff --git a/src/aare/devices/jfjoch.py b/src/aare/devices/jfjoch.py index 5b5cd05d..5b801f67 100644 --- a/src/aare/devices/jfjoch.py +++ b/src/aare/devices/jfjoch.py @@ -256,7 +256,7 @@ class JFJochWrapper: def detector(self) -> jfjoch_client.models.DetectorListElement: try: - l = self.__api.config_select_detector_get() + detector_list = self.__api.config_select_detector_get() except Exception as e: self._raise_jfjoch_error( f"JFJoch detector configuration retrieval failed: {e}", @@ -265,7 +265,7 @@ class JFJochWrapper: endpoint="config_select_detector_get", ) - if len(l.detectors) == 0: + if len(detector_list.detectors) == 0: raise JFJochCommunicationError( "JFJoch returned no configured detectors", operation="GET", @@ -275,7 +275,7 @@ class JFJochWrapper: ) try: - return l.detectors[l.current_id] + return detector_list.detectors[detector_list.current_id] except Exception as e: self._raise_jfjoch_error( "JFJoch returned an invalid selected detector entry", diff --git a/src/aare/devices/smargon.py b/src/aare/devices/smargon.py index 326ed482..5b4f2af8 100644 --- a/src/aare/devices/smargon.py +++ b/src/aare/devices/smargon.py @@ -220,7 +220,6 @@ class Smargon(object): sleep(poll_time) def wait_aerotech(self, timeout=60.0, tol=0.01, poll_time=0.01): - target = self.target timeout = timeout + time() while time() < timeout: if self.target_aerotech.eq(self.readback_aerotech, tol): diff --git a/src/aare/devices/tell_backend.py b/src/aare/devices/tell_backend.py index 5b264df0..1ce95ff6 100644 --- a/src/aare/devices/tell_backend.py +++ b/src/aare/devices/tell_backend.py @@ -120,11 +120,7 @@ class PShellTellBackend: if e.response is not None and e.response.text: msg = e.response.text.strip() raise TellCommunicationError( - msg, - base_url=self._url, - endpoint=endpoint, - operation=operation, - critical=True, + msg, base_url=self._url, endpoint=endpoint, operation=operation, critical=True ) from e def eval(self, expr: str): diff --git a/src/aare/devices/tell_client.py b/src/aare/devices/tell_client.py index f1660ad1..14a108e7 100644 --- a/src/aare/devices/tell_client.py +++ b/src/aare/devices/tell_client.py @@ -359,7 +359,9 @@ class TellClient: raise except Exception as e: logger.error(f"Exception occurred: {e}") - raise TellCommunicationError(message=f"Error during mount {segment}{puck}-{sample}: {e}", critical=True) + raise TellCommunicationError( + message=f"Error during mount {segment}{puck}-{sample}: {e}", critical=True + ) def unmount(self, force=False, wait=False, timeout=360.0): if self.is_busy(): @@ -582,7 +584,7 @@ if __name__ == "__main__": state = tell_client.get_robot_state() manual_mode = tell_client.is_manual_mode() print("state: ", state) - print("is manual mode True: ", manual_mode == True) + print("is manual mode True: ", manual_mode) print(tell_client.backend.eval("is_manual_mode()&")) print(tell_client.is_door_closed()) # print("release safety: ", tell_client.backend.eval('release_safety()&')) diff --git a/src/aare/devices/workflow_tools.py b/src/aare/devices/workflow_tools.py index bf3352ac..88cb8377 100755 --- a/src/aare/devices/workflow_tools.py +++ b/src/aare/devices/workflow_tools.py @@ -14,11 +14,17 @@ def wait_position(motor, target, tolerance=None, timeout=60.0): elif isinstance(position, (bytes, str)): tst = "'%s' == '%s'" if isinstance(position, bytes): - ltst = lambda x, y, z: ( - x.lower() == y.lower().encode() if isinstance(y, str) else x.lower() == y.lower() - ) + + def ltst(x, y, z): + return ( + x.lower() == y.lower().encode() + if isinstance(y, str) + else x.lower() == y.lower() + ) else: - ltst = lambda x, y, z: x.lower() == y.lower() + + def ltst(x, y, z): + return x.lower() == y.lower() else: raise RuntimeError("BUG: could not understand arguments: ") diff --git a/src/aare/gui/gui.py b/src/aare/gui/gui.py index cb6c728e..daaacbad 100644 --- a/src/aare/gui/gui.py +++ b/src/aare/gui/gui.py @@ -1,4 +1,3 @@ -import importlib import os import sys import traceback @@ -20,24 +19,24 @@ logger = setup_logger(LOGGER_NAME) def main(): """Wrapped gui as main function to make tests easier""" - splash = None + try: + basedir = os.path.dirname(__file__) + icon_path = os.path.join(basedir, "graphics/aaregui_logo.svg") + 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}") + sys.exit(1) try: # define application app = QApplication(sys.argv) + app.setWindowIcon(QtGui.QIcon(icon_path)) app.setApplicationName("AareGUI") app.setApplicationVersion("0.3.1") app.setOrganizationName("PSI") app.setOrganizationDomain("psi.ch") - # set icon - basedir = os.path.dirname(__file__) - icon_path = os.path.join(basedir, "graphics/aaregui_logo.svg") - app.setWindowIcon(QtGui.QIcon(icon_path)) - - # show splash screen - banner_path = os.path.join(basedir, "graphics/aare_banner.png") - splash_pix = QtGui.QPixmap(banner_path) - splash = LoadingSplashScreen(splash_pix) splash.show() splash.set_progress(10, "Initializing Application...") @@ -200,16 +199,13 @@ def main(): logger.error(f"Error starting GUI: {e}") logger.error(f"Traceback: {traceback.format_exc()}") - try: - 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", - ) - except: - pass + 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", + ) sys.exit(1) diff --git a/src/aare/gui/main_window.py b/src/aare/gui/main_window.py index 75d31029..ce4eb675 100644 --- a/src/aare/gui/main_window.py +++ b/src/aare/gui/main_window.py @@ -1852,7 +1852,7 @@ class MainWindow(QMainWindow): logger.critical(f"Automation critical failure: {message}") is_detector_failure = self._is_detector_critical_failure(message) - banner_message = ( + ( self._detector_error_banner_text(automation=True, message=message) if is_detector_failure else f"Automation halted: {message}" @@ -2493,6 +2493,6 @@ class MainWindow(QMainWindow): "Idle timeout reached, but GUI remains open because beamline is busy or automation is active." ) return - #TODO deny communication from GUI to DAQ while IDLE for too long rather than kill the GUI + # TODO deny communication from GUI to DAQ while IDLE for too long rather than kill the GUI logger.warning("Closing GUI after inactivity timeout.") self.close() diff --git a/src/aare/gui/models/sample_queue_model.py b/src/aare/gui/models/sample_queue_model.py index 5452a610..47dd35d5 100644 --- a/src/aare/gui/models/sample_queue_model.py +++ b/src/aare/gui/models/sample_queue_model.py @@ -90,9 +90,9 @@ class SampleQueueSpreadsheet(QAbstractTableModel): row = parent.row() try: - l = SampleShortInfoList.model_validate_json(data.text()) + sample_data = SampleShortInfoList.model_validate_json(data.text()) self.beginResetModel() - for sample in l.s: + for sample in sample_data.s: updated_row = row updated_samples = [] for i in range(len(self.samples)): diff --git a/src/aare/gui/models/user_sample_model.py b/src/aare/gui/models/user_sample_model.py index 10e85340..95e8d863 100644 --- a/src/aare/gui/models/user_sample_model.py +++ b/src/aare/gui/models/user_sample_model.py @@ -180,12 +180,12 @@ class UserSampleSpreadsheet(QAbstractTableModel): def mimeData(self, indexes): mime_data = QMimeData() - l = SampleShortInfoList(s=[]) + sample_data = SampleShortInfoList(s=[]) for i in sorted(set(index.row() for index in indexes)): - l.s.append(self.__sorted_samples[i]) + sample_data.s.append(self.__sorted_samples[i]) - mime_data.setText(l.model_dump_json()) + mime_data.setText(sample_data.model_dump_json()) return mime_data def get_id(self, row: int) -> SampleShortInfo: diff --git a/src/aare/gui/panels/face_detection_panel.py b/src/aare/gui/panels/face_detection_panel.py index 46c949f7..2f9e2ef2 100644 --- a/src/aare/gui/panels/face_detection_panel.py +++ b/src/aare/gui/panels/face_detection_panel.py @@ -78,7 +78,7 @@ class FaceDetectionPanel(QWidget): samples = data.get("samples", []) or [] running = bool(data.get("running", False)) angle = data.get("current_angle_deg") - status = data.get("status", "") + data.get("status", "") if running: if self._manual_run_requested: diff --git a/src/aare/gui/panels/file_path_panel.py b/src/aare/gui/panels/file_path_panel.py index 8da7a40c..9cca0e56 100644 --- a/src/aare/gui/panels/file_path_panel.py +++ b/src/aare/gui/panels/file_path_panel.py @@ -86,14 +86,16 @@ class FilePathPanel(QWidget): def _expand_macros(self, base: str, rn: int) -> str: name = f"{base}_{rn:03d}" - name = name.replace('{date}', self.__formatted_date) - name = name.replace('{sample}', self.__sample_name) - name = name.replace('{CrystalName}', self.__sample_name) - name = name.replace('{puck}', self.__puck_name) - name = name.replace('{position}', f"{self.__puck_pos:02d}") - name = name.replace('{sample_id}', f"{self.__sample_id}") - name = name.replace('{beamline}', f"{self.__beamline}") - name = name.replace('{prefix}', f"{self.__puck_name}/{self.__puck_pos:02d}/{self.__sample_name}") + name = name.replace("{date}", self.__formatted_date) + name = name.replace("{sample}", self.__sample_name) + name = name.replace("{CrystalName}", self.__sample_name) + name = name.replace("{puck}", self.__puck_name) + name = name.replace("{position}", f"{self.__puck_pos:02d}") + name = name.replace("{sample_id}", f"{self.__sample_id}") + name = name.replace("{beamline}", f"{self.__beamline}") + name = name.replace( + "{prefix}", f"{self.__puck_name}/{self.__puck_pos:02d}/{self.__sample_name}" + ) return name def _effective_dataset_base(self, base_no_run: str) -> str: diff --git a/src/aare/gui/panels/local_contact_panel.py b/src/aare/gui/panels/local_contact_panel.py index 4e957f57..52716c54 100644 --- a/src/aare/gui/panels/local_contact_panel.py +++ b/src/aare/gui/panels/local_contact_panel.py @@ -22,7 +22,7 @@ from PySide6.QtWidgets import ( QTabWidget, QTextEdit, QVBoxLayout, - QWidget, + QWidget, ) from aare.gui.constants import LOGGER_NAME @@ -333,11 +333,22 @@ class LocalContactPanel(QFrame): tools_layout.addWidget(tools) tools_layout.addWidget( - self._make_button("Load BEC user macros", self._daq.bec_load_user_macros, "Loading BEC user macros.")) + self._make_button( + "Load BEC user macros", self._daq.bec_load_user_macros, "Loading BEC user macros." + ) + ) tools_layout.addWidget( - self._make_button("Show BEC user macros", self._daq.bec_list_all_user_macros, "Listing BEC user macros.")) + self._make_button( + "Show BEC user macros", + self._daq.bec_list_all_user_macros, + "Listing BEC user macros.", + ) + ) tools_layout.addWidget( - self._make_button("Show BEC position devices", self._daq.bec_list_all_devices, "Listing BEC devices.")) + self._make_button( + "Show BEC position devices", self._daq.bec_list_all_devices, "Listing BEC devices." + ) + ) tools_layout.addWidget( self._make_button( "Reinitialise BEC planner/devices", diff --git a/src/aare/gui/panels/raster_data_collection.py b/src/aare/gui/panels/raster_data_collection.py index 59663c3e..75dfca2b 100644 --- a/src/aare/gui/panels/raster_data_collection.py +++ b/src/aare/gui/panels/raster_data_collection.py @@ -31,15 +31,14 @@ class RasterDataCollectionPanel(ScanSettingsPanel): raster_alpha_changed = Signal(int) def __init__( - self, - raster_mgr: RasterGridManager, - diffraction: DiffractionGeometry, - parent=None, + self, raster_mgr: RasterGridManager, diffraction: DiffractionGeometry, parent=None ): - super().__init__(diffraction=diffraction, - default_dtz=raster_mgr.active_grid.dtz, - default_transmission=raster_mgr.active_grid.transmission, - parent=parent) + super().__init__( + diffraction=diffraction, + default_dtz=raster_mgr.active_grid.dtz, + default_transmission=raster_mgr.active_grid.transmission, + parent=parent, + ) self._previous_sample_was_none_raster = True diff --git a/src/aare/gui/panels/rotation_data_collection.py b/src/aare/gui/panels/rotation_data_collection.py index 7e050210..fb8e455d 100644 --- a/src/aare/gui/panels/rotation_data_collection.py +++ b/src/aare/gui/panels/rotation_data_collection.py @@ -39,8 +39,13 @@ class RotationDataCollectionPanel(ScanSettingsPanel): super().__init__( parent=parent, diffraction=diffraction, - default_dtz=cfg_get("daq.data_collection_settings.default_rotation_settings.dtz", default_dtz), - default_transmission=cfg_get("daq.data_collection_settings.default_rotation_settings.transmission", default_transmission), + default_dtz=cfg_get( + "daq.data_collection_settings.default_rotation_settings.dtz", default_dtz + ), + default_transmission=cfg_get( + "daq.data_collection_settings.default_rotation_settings.transmission", + default_transmission, + ), ) self.__beamline_state = None @@ -71,8 +76,12 @@ class RotationDataCollectionPanel(ScanSettingsPanel): self._layout.addWidget(QLabel("°", parent=self), 5, 4) self._layout.addWidget(QLabel("Image time", parent=self), 6, 0) - default_screening_exp_time = cfg_get("daq.data_collection_settings.default_screening_settings.exp_time_s", 0.1) - self.screening_image_time_enter = NumberLineEdit(0.0005, 10.0, default_screening_exp_time, decimals=4, parent=self) + default_screening_exp_time = cfg_get( + "daq.data_collection_settings.default_screening_settings.exp_time_s", 0.1 + ) + self.screening_image_time_enter = NumberLineEdit( + 0.0005, 10.0, default_screening_exp_time, decimals=4, parent=self + ) self._layout.addWidget(self.screening_image_time_enter, 6, 1, 1, 3) self._layout.addWidget(QLabel("s", parent=self), 6, 4) @@ -96,22 +105,32 @@ class RotationDataCollectionPanel(ScanSettingsPanel): ) self._layout.addWidget(QLabel("Total angle", parent=self), 10, 0) - default_steps = cfg_get("daq.data_collection_settings.default_rotation_settings.steps", 1800) - default_increment_omega = cfg_get("daq.data_collection_settings.default_rotation_settings.increment_omega_deg", 0.2) - default_total_angle = default_steps*default_increment_omega - self.total_angle = DbOverrideLineEdit(0, 9999.0, default=default_total_angle, decimals=3, parent=self) + default_steps = cfg_get( + "daq.data_collection_settings.default_rotation_settings.steps", 1800 + ) + default_increment_omega = cfg_get( + "daq.data_collection_settings.default_rotation_settings.increment_omega_deg", 0.2 + ) + default_total_angle = default_steps * default_increment_omega + self.total_angle = DbOverrideLineEdit( + 0, 9999.0, default=default_total_angle, decimals=3, parent=self + ) self._layout.addWidget(self.total_angle, 10, 1, 1, 3) self._layout.addWidget(QLabel("°", parent=self), 10, 4) self._register_override_field(self.total_angle) self._layout.addWidget(QLabel("Image angle", parent=self), 11, 0) - self.image_angle = DbOverrideLineEdit(0, 10.0, default=default_increment_omega, decimals=3, parent=self) + self.image_angle = DbOverrideLineEdit( + 0, 10.0, default=default_increment_omega, decimals=3, parent=self + ) self._layout.addWidget(self.image_angle, 11, 1, 1, 3) self._layout.addWidget(QLabel("°", parent=self), 11, 4) self._register_override_field(self.image_angle) # TODO add protection on X10SA to prevent too short exposure time/ too high detector rep rate self._layout.addWidget(QLabel("Image time", parent=self), 12, 0) - default_image_exp_time = cfg_get("daq.data_collection_settings.default_rotation_settings.exp_time_s", 0.01) + default_image_exp_time = cfg_get( + "daq.data_collection_settings.default_rotation_settings.exp_time_s", 0.01 + ) self.image_time_enter = DbOverrideLineEdit( 0.0005, 10.0, default=default_image_exp_time, decimals=4, parent=self ) @@ -145,7 +164,7 @@ class RotationDataCollectionPanel(ScanSettingsPanel): self.measurement_button.clicked.connect(self.run_measurement) self._layout.addWidget(self.measurement_button, 16, 0, 1, 6) self._reset_to_defaults() - + @Slot() def run_screening(self): if self.__beamline_state != BeamlineStateEnum.SampleAlignment: diff --git a/src/aare/gui/panels/scan_settings_panel.py b/src/aare/gui/panels/scan_settings_panel.py index 418c3a8a..4352bf7c 100644 --- a/src/aare/gui/panels/scan_settings_panel.py +++ b/src/aare/gui/panels/scan_settings_panel.py @@ -196,7 +196,7 @@ class ScanSettingsPanel(QWidget): else: if not self._previous_sample_was_none: logger.info("Sample cleared, resetting parameters to defaults") - #self._reset_to_defaults() + # self._reset_to_defaults() self._previous_sample_was_none = True # Mark that sample is now None self._sample = None diff --git a/src/aare/gui/panels/target_stability_panel.py b/src/aare/gui/panels/target_stability_panel.py index 3a67a6c1..7bfdf029 100644 --- a/src/aare/gui/panels/target_stability_panel.py +++ b/src/aare/gui/panels/target_stability_panel.py @@ -55,7 +55,7 @@ class InteractiveChartView(QChartView): def mousePressEvent(self, event: QMouseEvent) -> None: if event.button() == Qt.MouseButton.LeftButton: click_pos = event.position() - chart_pos = self.chart().mapToValue(click_pos.toPoint()) + self.chart().mapToValue(click_pos.toPoint()) if self._panel._try_select_series_at_point(click_pos.toPoint()): event.accept() diff --git a/src/aare/gui/scan_logic/raster_grid_manager.py b/src/aare/gui/scan_logic/raster_grid_manager.py index 9eaa02b3..676346ce 100644 --- a/src/aare/gui/scan_logic/raster_grid_manager.py +++ b/src/aare/gui/scan_logic/raster_grid_manager.py @@ -140,8 +140,12 @@ class RasterGridManager(QObject): smargon_top_left=self.__geom.smargon, grid_size_mm=Coordinate(x=0.8 * self.__beam_size_mm.x, y=0.8 * self.__beam_size_mm.y), omega_deg=self.__geom.omega_deg, - exp_time_s=cfg_get("daq.data_collection_settings.default_raster_settings.exp_time_s", 0.01), - transmission=cfg_get("daq.data_collection_settings.default_raster_settings.transmission", 1.0), + exp_time_s=cfg_get( + "daq.data_collection_settings.default_raster_settings.exp_time_s", 0.01 + ), + transmission=cfg_get( + "daq.data_collection_settings.default_raster_settings.transmission", 1.0 + ), dtz=cfg_get("daq.data_collection_settings.default_raster_settings.dtz", 200.0), ) self.__completed_grids: List[CompletedRasterGridElem] = [] diff --git a/src/aare/gui/scan_logic/rotation_scan_manager.py b/src/aare/gui/scan_logic/rotation_scan_manager.py index 927479e6..ec17b8e1 100644 --- a/src/aare/gui/scan_logic/rotation_scan_manager.py +++ b/src/aare/gui/scan_logic/rotation_scan_manager.py @@ -3,7 +3,6 @@ from aarecommon.models.rotation_scan import CompletedRotationScan from PySide6.QtCore import QObject, Signal, Slot - class RotationScanManager(QObject): file_ready = Signal(str, int) diff --git a/src/aare/gui/threads/jfjoch_viewer.py b/src/aare/gui/threads/jfjoch_viewer.py index 250ff853..f6b63ec6 100644 --- a/src/aare/gui/threads/jfjoch_viewer.py +++ b/src/aare/gui/threads/jfjoch_viewer.py @@ -64,7 +64,7 @@ class JFJochDBusClient(QObject): path = "/sls/mx/data/" + name + "_master.h5" print(f"Load dataset: {path} image: {number}") if self._ensure_interface(): - reply = self.__interface.call("LoadFile", path, number, 1) + self.__interface.call("LoadFile", path, number, 1) @Slot() def load_online(self): @@ -73,4 +73,4 @@ class JFJochDBusClient(QObject): return if self._ensure_interface(): - reply = self.__interface.call("LoadFile", self.__detector_url, -1, 1) + self.__interface.call("LoadFile", self.__detector_url, -1, 1) diff --git a/src/aare/gui/tutorials/tutorial_manager.py b/src/aare/gui/tutorials/tutorial_manager.py index 099019d2..005a4dc5 100644 --- a/src/aare/gui/tutorials/tutorial_manager.py +++ b/src/aare/gui/tutorials/tutorial_manager.py @@ -554,13 +554,13 @@ class TutorialManager(QObject): return None - def _run_setup_actions(self, step: TutorialStep) -> None: + def _run_setup_actions(self, step: TutorialStepDefinition) -> None: if self.context is None: return for action in step.setup_actions: self.action_executor.execute_action(action, self.context) - def _run_cleanup_actions(self, step: TutorialStep) -> None: + def _run_cleanup_actions(self, step: TutorialStepDefinition) -> None: if self.context is None: return for action in step.cleanup_actions: diff --git a/src/aare/gui/tutorials/tutorial_runtime.py b/src/aare/gui/tutorials/tutorial_runtime.py index 5ddfd145..27d1b996 100644 --- a/src/aare/gui/tutorials/tutorial_runtime.py +++ b/src/aare/gui/tutorials/tutorial_runtime.py @@ -14,6 +14,7 @@ from aare.gui.tutorials.tutorial_models import ( TutorialContext, TutorialEvent, TutorialScenario, + TutorialStepDefinition, TutorialTarget, TutorialTextRef, ) @@ -188,7 +189,11 @@ class CompletionEvaluator: self._event_bus = event_bus def is_step_complete( - self, step: TutorialStep, context: TutorialContext, *, target_clicked: bool = False + self, + step: TutorialStepDefinition, + context: TutorialContext, + *, + target_clicked: bool = False, ) -> bool: if step.completion is None: return True diff --git a/tests/unit/daq/operations/test_ml_raster_plan.py b/tests/unit/daq/operations/test_ml_raster_plan.py index a3102978..fc3f22d2 100644 --- a/tests/unit/daq/operations/test_ml_raster_plan.py +++ b/tests/unit/daq/operations/test_ml_raster_plan.py @@ -151,7 +151,8 @@ def test_grid_padding_fraction_scales(monkeypatch): def test_crystal_union_extends_grid_only_when_enabled(monkeypatch): # crystal extends well beyond the loop_face box on +x - mlbox = lambda: _fake_mlbox(loop_face=(150, 150, 300, 300), crystals=[(350, 150, 520, 300)]) + def mlbox(): + return _fake_mlbox(loop_face=(150, 150, 300, 300), crystals=[(350, 150, 520, 300)]) def cfg(enabled): return lambda k, d=None: ( diff --git a/tests/unit/daq/test_auth.py b/tests/unit/daq/test_auth.py index 4fbe4eb4..85d2c856 100644 --- a/tests/unit/daq/test_auth.py +++ b/tests/unit/daq/test_auth.py @@ -23,12 +23,7 @@ with patch.dict("os.environ", {"JWT_AAREDAQ_KEY": "test_secret"}): respond_to_baton_request, ) from aarecommon.errors.exception_handler import AuthenticationException, UserRightsException -from aarecommon.models.auth import ( - BatonHolderInfo, - BatonRequest, - BatonRequestStatus, - BatonStatus, -) +from aarecommon.models.auth import BatonHolderInfo, BatonRequest, BatonRequestStatus, BatonStatus from aarecommon.models.models import SessionsStateEnum diff --git a/tests/unit/daq/test_beamcenterfit.py b/tests/unit/daq/test_beamcenterfit.py index b42cedda..c290f09e 100644 --- a/tests/unit/daq/test_beamcenterfit.py +++ b/tests/unit/daq/test_beamcenterfit.py @@ -45,7 +45,7 @@ def test_beamcenter_fit_no_converge(): # beamcenter_fit might still find some contour if there's enough noise, # but curve_fit might fail to converge # If it doesn't converge, it returns None now. - result = beamcenter_fit(image) + beamcenter_fit(image) # It might actually return a result if it finds a random blob, # but we want to test the failure path. # To truly force non-convergence we might need a more extreme case, diff --git a/tests/unit/daq/test_server.py b/tests/unit/daq/test_server.py index bda12315..7a535775 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 -import numpy as np os.environ["JWT_AAREDAQ_KEY"] = "test_key_for_unit_testing" diff --git a/tests/unit/devices/test_experimental_hutch_shutter.py b/tests/unit/devices/test_experimental_hutch_shutter.py index 5e4335d3..024e9272 100644 --- a/tests/unit/devices/test_experimental_hutch_shutter.py +++ b/tests/unit/devices/test_experimental_hutch_shutter.py @@ -7,7 +7,7 @@ from aare.devices.experimental_hutch_shutter import ExperimentalHutchShutter @patch("aare.devices.experimental_hutch_shutter.PV") def test_shutter_init(mock_pv): - shutter = ExperimentalHutchShutter(MXBeamline.X06DA) + ExperimentalHutchShutter(MXBeamline.X06DA) assert mock_pv.call_count == 3 # Check if PV names are correct args = [call.args[0] for call in mock_pv.call_args_list] diff --git a/tests/unit/devices/test_filter_transmission.py b/tests/unit/devices/test_filter_transmission.py index e5d8eb9b..b7afb504 100644 --- a/tests/unit/devices/test_filter_transmission.py +++ b/tests/unit/devices/test_filter_transmission.py @@ -8,7 +8,7 @@ from aare.devices.filter_transmission import FilterTransmission @patch("aare.devices.filter_transmission.PV") def test_filter_init(mock_pv): - filters = FilterTransmission(MXBeamline.X06DA) + FilterTransmission(MXBeamline.X06DA) assert mock_pv.call_count == 3 diff --git a/tests/unit/gui/test_message_box.py b/tests/unit/gui/test_message_box.py index 74806197..8f2464fd 100644 --- a/tests/unit/gui/test_message_box.py +++ b/tests/unit/gui/test_message_box.py @@ -31,6 +31,7 @@ def test_timer_box_auto_accept(qtbot): assert box.result() == QMessageBox.StandardButton.Yes + @pytest.mark.timeout(10) def test_ring_current_low_check_ok(qtbot): # Should return True immediately if current is high enough