Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2c400ac
Run pydantic migrator on murfey and fix issues that arise
stephen-riggs Jan 4, 2024
ab06896
No more procrunner
stephen-riggs Jan 4, 2024
32495bc
Optionals got lost in rebase
stephen-riggs Aug 30, 2024
bd28c7b
This change seems wrong
stephen-riggs Aug 30, 2024
9f31673
Merge main, with pyproject changes
stephen-riggs Oct 8, 2024
685b511
Pydantic deprecation warning bits
stephen-riggs Oct 9, 2024
4eb3cb7
sqlalchemy2 change to metadata
stephen-riggs Oct 11, 2024
d940ee3
More pydantic 2 model dumps
stephen-riggs Oct 14, 2024
ef33445
Pydantic doesn't like things starting model_
stephen-riggs Oct 14, 2024
72533df
Require zocalo>=1
stephen-riggs Oct 15, 2024
ce1728f
merge in main
stephen-riggs Oct 31, 2024
e0b0411
Unnecessary movement of components
stephen-riggs Oct 31, 2024
7dfa6ae
Catch up main commits
stephen-riggs Feb 10, 2025
846f7b5
Ran pydantic update tool on clem files
stephen-riggs Feb 10, 2025
ed8aa3c
Merge in main
stephen-riggs Jun 16, 2025
613244a
Apply 1 to 2 conversion with possible path to str problems
stephen-riggs Jun 16, 2025
585099b
Merged recent changes from 'main' branch
tieneupin Jun 25, 2025
61e2192
Added 'pydantic-settings' to list of dependencies
tieneupin Jun 25, 2025
417f766
Added 'field_validator' functions for the 'calibrations' and 'softwar…
tieneupin Jun 25, 2025
883b761
Pydantic v2 doesn't convert floats into ints, so added a 'field_valid…
tieneupin Jun 25, 2025
d0554ae
Replaced 'model_search_directory' with 'picking_model_search_directory'
tieneupin Jun 25, 2025
e419ba3
Changed 'atlas_pixel_size' from int to float in 'DCGroupParameters' m…
tieneupin Jun 26, 2025
1e73985
Log traceback when encountering exceptions in Contexts
tieneupin Jun 26, 2025
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: 4 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ classifiers = [
]
dependencies = [
"defusedxml", # For safely parsing XML files
"pydantic<2", # Locked to <2 by zocalo
"pydantic>=2",
"requests",
"rich",
"werkzeug",
Expand All @@ -41,13 +41,12 @@ cicd = [
"pytest-cov", # Used by Azure Pipelines for PyTest coverage reports
]
client = [
"procrunner",
"textual==0.42.0",
"websocket-client",
"xmltodict",
]
developer = [
"bump-my-version<0.11.0", # Version control
"bump-my-version", # Version control
"ipykernel", # Enable interactive coding with VS Code and Jupyter Notebook
"pre-commit", # Formatting, linting, type checking, etc.
"pytest", # Test code functionality
Expand All @@ -58,7 +57,7 @@ server = [
"backports.entry_points_selectable",
"cryptography",
"fastapi[standard]",
"ispyb", # Responsible for setting requirements for SQLAlchemy and mysql-connector-python; v10.0.0: sqlalchemy <2, mysql-connector-python >=8.0.32
"ispyb>=10.2.4", # Responsible for setting requirements for SQLAlchemy and mysql-connector-python;
"jinja2",
"mrcfile",
"numpy",
Expand All @@ -71,7 +70,7 @@ server = [
"sqlmodel",
"stomp-py<=8.1.0", # 8.1.1 (released 2024-04-06) doesn't work with our project
"uvicorn[standard]",
"zocalo",
"zocalo>=1",
]
[project.urls]
Bug-Tracker = "https://github.com/DiamondLightSource/python-murfey/issues"
Expand Down
4 changes: 2 additions & 2 deletions src/murfey/cli/transfer.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from __future__ import annotations

import argparse
import subprocess
from pathlib import Path
from urllib.parse import urlparse

import procrunner
import requests
from rich.console import Console
from rich.prompt import Confirm
Expand Down Expand Up @@ -78,6 +78,6 @@ def run():
cmd.extend(list(Path(args.source or ".").glob("*")))
cmd.append(f"{murfey_url.hostname}::{args.destination}")

result = procrunner.run(cmd)
result = subprocess.run(cmd)
if result.returncode:
console.print(f"[red]rsync failed returning code {result.returncode}")
50 changes: 25 additions & 25 deletions src/murfey/client/instance_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
from typing import Callable, Dict, List, NamedTuple, Optional, Set
from urllib.parse import ParseResult

from pydantic import BaseModel, validator
from pydantic import BaseModel, ConfigDict, field_validator
from pydantic_core.core_schema import ValidationInfo

from murfey.client.watchdir import DirWatcher

Expand Down Expand Up @@ -41,20 +42,20 @@ class MurfeyInstanceEnvironment(BaseModel):
destination_registry: Dict[str, str] = {}
watchers: Dict[Path, DirWatcher] = {}
demo: bool = False
data_collection_group_ids: Dict[str, int] = {}
data_collection_ids: Dict[str, int] = {}
processing_job_ids: Dict[str, Dict[str, int]] = {}
autoproc_program_ids: Dict[str, Dict[str, int]] = {}
id_tag_registry: Dict[str, List[str]] = {
"data_collection_group": [],
"data_collection": [],
"processing_job": [],
"auto_proc_program": [],
}
listeners: Dict[str, Set[Callable]] = {}
data_collection_group_ids: Dict[str, int] = {}
data_collection_ids: Dict[str, int] = {}
processing_job_ids: Dict[str, Dict[str, int]] = {}
autoproc_program_ids: Dict[str, Dict[str, int]] = {}
data_collection_parameters: dict = {}
movies: Dict[Path, MovieTracker] = {}
motion_corrected_movies: Dict[Path, List[str]] = {}
listeners: Dict[str, Set[Callable]] = {}
movie_tilt_pair: Dict[Path, str] = {}
tilt_angles: Dict[str, List[List[str]]] = {}
movie_counters: Dict[str, itertools.count] = {}
Expand All @@ -65,42 +66,41 @@ class MurfeyInstanceEnvironment(BaseModel):
murfey_session: Optional[int] = None
samples: Dict[Path, SampleInfo] = {}

class Config:
validate_assignment: bool = True
arbitrary_types_allowed: bool = True
model_config = ConfigDict(arbitrary_types_allowed=True)

@validator("data_collection_group_ids")
def dcg_callback(cls, v, values):
@field_validator("data_collection_group_ids")
def dcg_callback(cls, v, info: ValidationInfo):
with global_env_lock:
for l in values.get("listeners", {}).get("data_collection_group_ids", []):
for l in info.data.get("listeners", {}).get(
"data_collection_group_ids", []
):
for k in v.keys():
if k not in values["id_tag_registry"]["data_collection"]:
if k not in info.data["id_tag_registry"]["data_collection"]:
l(k)
return v

@validator("data_collection_ids")
def dc_callback(cls, v, values):
@field_validator("data_collection_ids")
def dc_callback(cls, v, info: ValidationInfo):
with global_env_lock:
for l in values.get("listeners", {}).get("data_collection_ids", []):
for l in info.data.get("listeners", {}).get("data_collection_ids", []):
for k in v.keys():
if k not in values["id_tag_registry"]["processing_job"]:
if k not in info.data["id_tag_registry"]["processing_job"]:
l(k)
return v

@validator("processing_job_ids")
def job_callback(cls, v, values):
@field_validator("processing_job_ids")
def job_callback(cls, v, info: ValidationInfo):
with global_env_lock:
for l in values.get("listeners", {}).get("processing_job_ids", []):
for l in info.data.get("listeners", {}).get("processing_job_ids", []):
for k in v.keys():
if k not in values["id_tag_registry"]["auto_proc_program"]:
if k not in info.data["id_tag_registry"]["auto_proc_program"]:
l(k, v[k]["ispyb-relion"])
return v

@validator("autoproc_program_ids")
def app_callback(cls, v, values):
# logger.info(f"autoproc program ids validator: {v}")
@field_validator("autoproc_program_ids")
def app_callback(cls, v, info: ValidationInfo):
with global_env_lock:
for l in values.get("listeners", {}).get("autoproc_program_ids", []):
for l in info.data.get("listeners", {}).get("autoproc_program_ids", []):
for k in v.keys():
if v[k].get("em-tomo-preprocess"):
l(k, v[k]["em-tomo-preprocess"])
Expand Down
33 changes: 16 additions & 17 deletions src/murfey/client/rsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
from typing import Callable, List, NamedTuple
from urllib.parse import ParseResult

import procrunner

from murfey.client.tui.status_bar import StatusBar
from murfey.util import Observer

Expand Down Expand Up @@ -433,28 +431,29 @@ def parse_stderr(line: str):
result: subprocess.CompletedProcess | None = None
success = True
if rsync_stdin:
result = procrunner.run(
result = subprocess.run(
rsync_cmd,
callback_stdout=parse_stdout,
callback_stderr=parse_stderr,
working_directory=str(self._basepath),
stdin=rsync_stdin,
print_stdout=False,
print_stderr=False,
cwd=str(self._basepath),
capture_output=True,
input=rsync_stdin,
)
success = result.returncode == 0 if result else False
for stdout_line in result.stdout.decode("utf8", "replace").split("\n"):
parse_stdout(stdout_line)
for stderr_line in result.stderr.decode("utf8", "replace").split("\n"):
parse_stderr(stderr_line)
success = result.returncode == 0

if rsync_stdin_remove:
rsync_cmd.insert(-2, "--remove-source-files")
result = procrunner.run(
result = subprocess.run(
rsync_cmd,
callback_stdout=parse_stdout,
callback_stderr=parse_stderr,
working_directory=str(self._basepath),
stdin=rsync_stdin_remove,
print_stdout=False,
print_stderr=False,
cwd=str(self._basepath),
input=rsync_stdin_remove,
)
for stdout_line in result.stdout.decode("utf8", "replace").split("\n"):
parse_stdout(stdout_line)
for stderr_line in result.stderr.decode("utf8", "replace").split("\n"):
parse_stderr(stderr_line)

if success:
success = result.returncode == 0 if result else False
Expand Down
4 changes: 2 additions & 2 deletions src/murfey/client/tui/app.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from __future__ import annotations

import logging
import subprocess
from datetime import datetime
from functools import partial
from pathlib import Path
from queue import Queue
from typing import Awaitable, Callable, Dict, List, OrderedDict, TypeVar
from urllib.parse import urlparse

import procrunner
import requests
from textual.app import App
from textual.reactive import reactive
Expand Down Expand Up @@ -203,7 +203,7 @@ def _start_rsyncer(
if self._environment:
self._environment.default_destinations[source] = destination
if self._environment.gain_ref and visit_path:
gain_rsync = procrunner.run(
gain_rsync = subprocess.run(
[
"rsync",
str(self._environment.gain_ref),
Expand Down
6 changes: 3 additions & 3 deletions src/murfey/client/tui/screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# import contextlib
import logging
import subprocess
from datetime import datetime
from functools import partial
from pathlib import Path
Expand All @@ -17,7 +18,6 @@
TypeVar,
)

import procrunner
import requests
from pydantic import BaseModel, ValidationError
from rich.box import SQUARE
Expand Down Expand Up @@ -199,7 +199,7 @@ def validate_form(form: dict, model: BaseModel) -> bool:
try:
convert = lambda x: None if x == "None" else x
validated = model(**{k: convert(v) for k, v in form.items()})
log.info(validated.dict())
log.info(validated.model_dump())
return True
except (AttributeError, ValidationError) as e:
log.warning(f"Form validation failed: {str(e)}")
Expand Down Expand Up @@ -846,7 +846,7 @@ def on_button_pressed(self, event):
if self.app._environment.demo:
log.info(f"Would perform {' '.join(cmd)}")
else:
gain_rsync = procrunner.run(cmd)
gain_rsync = subprocess.run(cmd)
if gain_rsync.returncode:
log.warning(
f"Gain reference file {self._dir_tree._gain_reference} was not successfully transferred to {visit_path}/processing"
Expand Down
6 changes: 3 additions & 3 deletions src/murfey/instrument_server/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def start_multigrid_watcher(
demo=True,
do_transfer=True,
processing_enabled=not watcher_spec.skip_existing_processing,
_machine_config=watcher_spec.configuration.dict(),
_machine_config=watcher_spec.configuration.model_dump(),
token=tokens.get(session_id, "token"),
data_collection_parameters=data_collection_parameters.get(label, {}),
)
Expand All @@ -156,7 +156,7 @@ def start_multigrid_watcher(
(watcher_spec.source / d).mkdir(exist_ok=True)
watchers[session_id] = MultigridDirWatcher(
watcher_spec.source,
watcher_spec.configuration.dict(),
watcher_spec.configuration.model_dump(),
skip_existing_processing=watcher_spec.skip_existing_processing,
)
watchers[session_id].subscribe(controllers[session_id]._start_rsyncer_multigrid)
Expand Down Expand Up @@ -221,7 +221,7 @@ def register_processing_parameters(
session_id: MurfeySessionID, proc_param_block: ProcessingParameterBlock
):
data_collection_parameters[proc_param_block.label] = {}
for k, v in proc_param_block.params.dict().items():
for k, v in proc_param_block.params.model_dump().items():
data_collection_parameters[proc_param_block.label][k] = v
return {"success": True}

Expand Down
6 changes: 4 additions & 2 deletions src/murfey/server/api/spa.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ def _cryolo_model_path(visit: str, instrument_name: str) -> Path:
machine_config = get_machine_config(instrument_name=instrument_name)[
instrument_name
]
if machine_config.model_search_directory:
if machine_config.picking_model_search_directory:
visit_directory = (
machine_config.rsync_basepath
/ (machine_config.rsync_module or "data")
/ str(datetime.now().year)
/ visit
)
possible_models = list(
(visit_directory / machine_config.model_search_directory).glob("*.h5")
(visit_directory / machine_config.picking_model_search_directory).glob(
"*.h5"
)
)
if possible_models:
return sorted(possible_models, key=lambda x: x.stat().st_ctime)[-1]
Expand Down
3 changes: 2 additions & 1 deletion src/murfey/server/demo_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
from fastapi.responses import FileResponse, HTMLResponse
from ispyb.sqlalchemy import BLSession
from PIL import Image
from pydantic import BaseModel, BaseSettings
from pydantic import BaseModel
from pydantic_settings import BaseSettings
from sqlalchemy import func
from sqlmodel import col, select
from werkzeug.utils import secure_filename
Expand Down
2 changes: 1 addition & 1 deletion src/murfey/server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from prometheus_client import make_asgi_app
from pydantic import BaseSettings
from pydantic_settings import BaseSettings

import murfey.server
import murfey.server.api.auth
Expand Down
5 changes: 3 additions & 2 deletions src/murfey/util/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from typing import Dict, List, Literal, Optional, Union

import yaml
from pydantic import BaseModel, BaseSettings
from pydantic import BaseModel
from pydantic_settings import BaseSettings


class MachineConfig(BaseModel):
Expand Down Expand Up @@ -57,7 +58,7 @@ class MachineConfig(BaseModel):
upstream_data_download_directory: Optional[Path] = None # Set by microscope config
upstream_data_tiff_locations: List[str] = ["processed"] # Location of CLEM TIFFs

model_search_directory: str = "processing"
picking_model_search_directory: str = "processing"
initial_model_search_directory: str = "processing/initial_model"

failure_queue: str = ""
Expand Down
5 changes: 3 additions & 2 deletions src/murfey/util/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,8 @@ def setup(url: str):

def clear(url: str):
engine = create_engine(url)
metadata = sqlalchemy.MetaData(engine)
metadata.reflect()
metadata = sqlalchemy.MetaData()
metadata.create_all(engine)
metadata.reflect(engine)

metadata.drop_all(engine)
Loading