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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
with:
extra_args: --hook-stage manual --all-files

- name: Install package
run: python -m pip install .[dev]

- name: Run pre-commit
run: pre-commit run --all-files

checks:
name: Check Python ${{ matrix.python-version }} on ${{ matrix.runs-on }}
Expand Down
12 changes: 6 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ repos:
args: ["--fix", "--show-fixes"]
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.15.0"
- repo: local
hooks:
- id: mypy
- id: pyright
name: pyright
entry: pyright
language: system
types: [python]
files: src|tests
args: []
additional_dependencies:
- pytest

- repo: https://github.com/codespell-project/codespell
rev: "v2.4.1"
Expand Down
18 changes: 16 additions & 2 deletions pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 12 additions & 16 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ dev = [
"pre-commit",
"ipython",
"ruff",
"pyright",
]
docs = [
"sphinx>=7.0",
Expand All @@ -72,7 +73,7 @@ write_to = "src/cditools/_version.py"

[tool.uv]
dev-dependencies = [
"cditools[test]",
"cditools[dev]",
]


Expand All @@ -96,21 +97,16 @@ report.exclude_also = [
'if typing.TYPE_CHECKING:',
]

[tool.mypy]
files = ["src", "tests"]
python_version = "3.9"
warn_unused_configs = true
strict = true
enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
warn_unreachable = true
disallow_untyped_defs = false
disallow_incomplete_defs = false
disallow_subclassing_any = false

[[tool.mypy.overrides]]
module = "cditools.*"
disallow_untyped_defs = true
disallow_incomplete_defs = true
[tool.pyright]
include = ["src", "tests"]
pythonVersion = "3.9"
typeCheckingMode = "strict"
reportMissingImports = true
# Required for untyped packages
reportMissingTypeStubs = false
reportUnknownMemberType = false
reportUnknownArgumentType = false
reportUnknownVariableType = false

[tool.ruff]

Expand Down
25 changes: 13 additions & 12 deletions src/cditools/eiger.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from datetime import datetime
from pathlib import Path, PurePath
from typing import Any
from typing import Any, cast

from ophyd import Component as Cpt # type: ignore[import-not-found]
from ophyd import (
Expand All @@ -12,7 +12,6 @@
ProcessPlugin,
ROIPlugin,
StatsPlugin,
StatusBase,
)
from ophyd.areadetector.base import ( # type: ignore[import-not-found]
ADComponent,
Expand All @@ -23,6 +22,7 @@
new_short_uid,
)
from ophyd.areadetector.trigger_mixins import ( # type: ignore[import-not-found]
ADTriggerStatus,
SingleTrigger,
)

Expand Down Expand Up @@ -69,7 +69,7 @@ def master_file_paths(self) -> list[PurePath]:
def sequence_number(self) -> int:
return self.sequence_id_offset + int(self.sequence_id.get())

def stage(self) -> list[object]:
def stage(self) -> list[object]: # type: ignore[reportIncompatibleMethodOverride]
res_uid = new_short_uid()
write_path = Path(f"{datetime.now().strftime(self.write_path_template)}/")
self.file_path.set(write_path.as_posix()).wait(1.0)
Expand All @@ -82,14 +82,14 @@ def stage(self) -> list[object]:
# * ...
self.file_write_name_pattern.set(f"{res_uid}_$id").wait(1.0)

ret: list[object] = super().stage()
ret: list[object] = super().stage() # type: ignore[reportIncompatibleMethodOverride]

# Set the filename for the resource document.
file_prefix = PurePath(self.file_path.get()) / res_uid
self._fn = file_prefix

images_per_file = self.file_write_images_per_file.get()
resource_kwargs = {"images_per_file": images_per_file}
images_per_file: str = self.file_write_images_per_file.get()
resource_kwargs: dict[str, str] = {"images_per_file": images_per_file}

self._generate_resource(resource_kwargs)

Expand Down Expand Up @@ -145,22 +145,23 @@ class EigerBase(EigerDetector):
def stage(self, *args: Any, **kwargs: dict[str, Any]) -> list[object]:
staged_devices: list[object] = super().stage(*args, **kwargs)
self.cam.manual_trigger.set(True).wait(5.0)
file_write_path = self.file_handler.file_path.get()
file_write_path: Path = Path(cast(str, self.file_handler.file_path.get()))
if not Path.exists(file_write_path):
msg = f"Path {file_write_path} does not exist."
raise FileNotFoundError(msg)
return staged_devices

def unstage(self) -> None:
def unstage(self) -> list[object]:
self.cam.manual_trigger.set(False).wait(5.0)
super().unstage()
ret = super().unstage()

if not all(Path.exists(path) for path in self.file_handler.master_file_paths):
if not all(Path(path).exists() for path in self.file_handler.master_file_paths):
msg = f"Paths {self.file_handler.master_file_paths} were not written."
raise FileNotFoundError(msg)
return ret


class EigerSingleTrigger(SingleTrigger, EigerBase):
class EigerSingleTrigger(SingleTrigger, EigerBase): # type: ignore[reportIncompatibleMethodOverride]
"""Eiger detector that uses the single trigger acquisition mode."""

def __init__(self, *args: Any, **kwargs: dict[str, Any]) -> None:
Expand All @@ -170,7 +171,7 @@ def __init__(self, *args: Any, **kwargs: dict[str, Any]) -> None:
self.stage_sigs["file_handler.enable"] = True
self.stage_sigs["file_handler.save_files"] = True

def trigger(self, *args: Any, **kwargs: dict[str, Any]) -> StatusBase:
def trigger(self, *args: Any, **kwargs: dict[str, Any]) -> ADTriggerStatus:
status = super().trigger(*args, **kwargs)
# If the manual trigger is enabled, we need to press the special trigger button
# to actually trigger the detector.
Expand Down
2 changes: 1 addition & 1 deletion src/cditools/simulated/black_hole.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def __init__(self, *args: Any, **kwargs: dict[str, Any]) -> None:
# Overwrite the pvdb with the blackhole, while keeping the explicit pv properties
self.pvdb: dict[str, ChannelData] = self.pvdb
self.old_pvdb = self.pvdb.copy()
self.pvdb = ReallyDefaultDict(self.fabricate_channel)
self.pvdb = ReallyDefaultDict(self.fabricate_channel) # type: ignore[reportIncompatibleMethodOverride]

def fabricate_channel(self, key: str) -> ChannelData:
# If the channel already exists from initialization, return it
Expand Down
5 changes: 3 additions & 2 deletions tests/test_motors.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import os
import time
from collections.abc import Generator
from subprocess import PIPE, Popen

import pytest
Expand Down Expand Up @@ -34,7 +35,7 @@


@pytest.fixture(scope="session")
def black_hole_ioc():
def black_hole_ioc() -> Generator[None, None, None]:
os.environ["EPICS_CA_ADDR_LIST"] = "127.0.0.1"
os.environ["EPICS_CA_AUTO_ADDR_LIST"] = "NO"
p = Popen(["black-hole-ioc", "--interfaces", "127.0.0.1"], stdout=PIPE)
Expand All @@ -60,7 +61,7 @@ def black_hole_ioc():
p.wait()


def test_motors_can_connect(black_hole_ioc):
def test_motors_can_connect(black_hole_ioc: None) -> None:
slt_wb1 = SltWB1(prefix="XF:09IDA-OP:1{Slt:WB1", name="slt_wb1")
slt_wb1.wait_for_connection(timeout=10.0)

Expand Down