Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4a235bd
Migrated client-side only functions out into a separate utils module
tieneupin Apr 8, 2025
2714a64
Updated imports to point to new module location; removed duplicate of…
tieneupin Apr 8, 2025
daba1da
Updated import for 'test_set_default_acquisition_output'
tieneupin Apr 8, 2025
4a0f1dc
Merged recent changes from 'main' branch
tieneupin Apr 8, 2025
ff10009
Merged recent changes from 'main' branch
tieneupin Apr 10, 2025
b2b1e7e
Merged recent changes from 'main' branch
tieneupin Apr 10, 2025
475ebd7
Renamed 'test_set_default_acquisition_output.py' to 'test_client.py'
tieneupin Apr 10, 2025
896a4a9
Removed redundant 'test_lif.py' test file
tieneupin Apr 10, 2025
2406a46
Added logic to 'read_config()' to look for a 'MURFEY_CLIENT_CONFIGURA…
tieneupin Apr 10, 2025
ac91f96
Added unit test for 'read_config()' function
tieneupin Apr 10, 2025
8efef32
Moved config file path resolution logic out of try-except block
tieneupin Apr 10, 2025
5c88d2f
Adjusted comment
tieneupin Apr 10, 2025
c44f771
Path().suffix is not callable
tieneupin Apr 10, 2025
d339cad
Added logic to '_get_visit_list()' to generate correct URL to backend…
tieneupin Apr 10, 2025
4f33b93
Added unit test for '_get_visit_list'
tieneupin Apr 10, 2025
50d6112
Adjusted logic for comparing actual output against expected one
tieneupin Apr 10, 2025
e62559d
'Visit' Pydantic model converts timestamp into a 'datetime' object, s…
tieneupin Apr 10, 2025
0daa002
Merged recent changes from 'main' branch
tieneupin Apr 11, 2025
a59248e
Migrated 'Observer' class to 'murfey.util.client' as well
tieneupin Apr 11, 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
37 changes: 2 additions & 35 deletions src/murfey/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import argparse
import configparser

# import json
import logging
import os
import platform
Expand All @@ -12,57 +10,26 @@
import time
import webbrowser
from datetime import datetime
from functools import partial
from pathlib import Path
from queue import Queue
from typing import List, Literal
from urllib.parse import ParseResult, urlparse

import requests

# from multiprocessing import Process, Queue
from rich.prompt import Confirm

import murfey.client.rsync
import murfey.client.update
import murfey.client.watchdir
import murfey.client.websocket
from murfey.client.customlogging import CustomHandler, DirectableRichHandler
from murfey.client.instance_environment import MurfeyInstanceEnvironment
from murfey.client.tui.app import MurfeyTUI
from murfey.client.tui.status_bar import StatusBar
from murfey.util import _get_visit_list

# from asyncio import Queue


# from rich.prompt import Prompt

from murfey.util.client import _get_visit_list, authorised_requests, read_config

log = logging.getLogger("murfey.client")


def read_config() -> configparser.ConfigParser:
config = configparser.ConfigParser()
try:
mcch = os.environ.get("MURFEY_CLIENT_CONFIG_HOME")
murfey_client_config_home = Path(mcch) if mcch else Path.home()
with open(murfey_client_config_home / ".murfey") as configfile:
config.read_file(configfile)
except FileNotFoundError:
log.warning(
f"Murfey client configuration file {murfey_client_config_home / '.murfey'} not found"
)
if "Murfey" not in config:
config["Murfey"] = {}
return config


token = read_config()["Murfey"].get("token", "")

requests.get = partial(requests.get, headers={"Authorization": f"Bearer {token}"})
requests.post = partial(requests.post, headers={"Authorization": f"Bearer {token}"})
requests.delete = partial(requests.delete, headers={"Authorization": f"Bearer {token}"})
requests.get, requests.post, requests.put, requests.delete = authorised_requests()


def write_config(config: configparser.ConfigParser):
Expand Down
3 changes: 2 additions & 1 deletion src/murfey/client/analyser.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
from murfey.client.instance_environment import MurfeyInstanceEnvironment
from murfey.client.rsync import RSyncerUpdate, TransferResult
from murfey.client.tui.forms import FormDependency
from murfey.util import Observer, get_machine_config_client
from murfey.util import Observer
from murfey.util.client import get_machine_config_client
from murfey.util.mdoc import get_block
from murfey.util.models import PreprocessingParametersTomo, ProcessingParametersSPA

Expand Down
2 changes: 1 addition & 1 deletion src/murfey/client/contexts/clem.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from murfey.client.context import Context
from murfey.client.instance_environment import MurfeyInstanceEnvironment
from murfey.util import capture_post, get_machine_config_client
from murfey.util.client import capture_post, get_machine_config_client

# Create logger object
logger = logging.getLogger("murfey.client.contexts.clem")
Expand Down
2 changes: 1 addition & 1 deletion src/murfey/client/contexts/fib.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from murfey.client.context import Context
from murfey.client.instance_environment import MurfeyInstanceEnvironment
from murfey.util import authorised_requests
from murfey.util.client import authorised_requests

Check warning on line 13 in src/murfey/client/contexts/fib.py

View check run for this annotation

Codecov / codecov/patch

src/murfey/client/contexts/fib.py#L13

Added line #L13 was not covered by tests

logger = logging.getLogger("murfey.client.contexts.fib")

Expand Down
2 changes: 1 addition & 1 deletion src/murfey/client/contexts/spa.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
MurfeyID,
MurfeyInstanceEnvironment,
)
from murfey.util import (
from murfey.util.client import (
authorised_requests,
capture_get,
capture_post,
Expand Down
6 changes: 5 additions & 1 deletion src/murfey/client/contexts/spa_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
from murfey.client.context import Context
from murfey.client.contexts.spa import _file_transferred_to, _get_source
from murfey.client.instance_environment import MurfeyInstanceEnvironment, SampleInfo
from murfey.util import authorised_requests, capture_post, get_machine_config_client
from murfey.util.client import (
authorised_requests,
capture_post,
get_machine_config_client,
)
from murfey.util.spa_metadata import (
FoilHoleInfo,
get_grid_square_atlas_positions,
Expand Down
6 changes: 5 additions & 1 deletion src/murfey/client/contexts/tomo.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
MurfeyID,
MurfeyInstanceEnvironment,
)
from murfey.util import authorised_requests, capture_post, get_machine_config_client
from murfey.util.client import (
authorised_requests,
capture_post,
get_machine_config_client,
)
from murfey.util.mdoc import get_block, get_global_data, get_num_blocks
from murfey.util.tomo import midpoint

Expand Down
3 changes: 2 additions & 1 deletion src/murfey/client/multigrid_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
from murfey.client.rsync import RSyncer, RSyncerUpdate, TransferResult
from murfey.client.tui.screens import determine_default_destination
from murfey.client.watchdir import DirWatcher
from murfey.util import capture_post, get_machine_config_client, posix_path
from murfey.util import posix_path
from murfey.util.client import capture_post, get_machine_config_client

log = logging.getLogger("murfey.client.mutligrid_control")

Expand Down
4 changes: 2 additions & 2 deletions src/murfey/client/tui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
from murfey.client.tui.status_bar import StatusBar
from murfey.client.watchdir import DirWatcher
from murfey.client.watchdir_multigrid import MultigridDirWatcher
from murfey.util import (
from murfey.util import posix_path
from murfey.util.client import (
capture_post,
get_machine_config_client,
posix_path,
read_config,
set_default_acquisition_output,
)
Expand Down
3 changes: 2 additions & 1 deletion src/murfey/client/tui/screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
)
from murfey.client.rsync import RSyncer
from murfey.client.tui.forms import FormDependency
from murfey.util import capture_post, get_machine_config_client, posix_path, read_config
from murfey.util import posix_path
from murfey.util.client import capture_post, get_machine_config_client, read_config
from murfey.util.models import PreprocessingParametersTomo, ProcessingParametersSPA

log = logging.getLogger("murfey.tui.screens")
Expand Down
2 changes: 1 addition & 1 deletion src/murfey/instrument_server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
from rich.logging import RichHandler

import murfey
from murfey.client import read_config
from murfey.client.customlogging import CustomHandler
from murfey.util import LogFilter
from murfey.util.client import read_config

logger = logging.getLogger("murfey.instrument_server")

Expand Down
2 changes: 1 addition & 1 deletion src/murfey/instrument_server/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
from pydantic import BaseModel
from werkzeug.utils import secure_filename

from murfey.client import read_config
from murfey.client.multigrid_control import MultigridController
from murfey.client.rsync import RSyncer
from murfey.client.watchdir_multigrid import MultigridDirWatcher
from murfey.util import posix_path, sanitise, sanitise_nonpath, secure_path
from murfey.util.client import read_config
from murfey.util.instrument_models import MultigridWatcherSpec
from murfey.util.models import File, Token

Expand Down
150 changes: 1 addition & 149 deletions src/murfey/util/__init__.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,19 @@
from __future__ import annotations

import asyncio
import configparser
import copy
import inspect
import json
import logging
import os
import shutil
from functools import lru_cache, partial
from pathlib import Path
from queue import Queue
from threading import Thread
from typing import Awaitable, Callable, Dict, List, Optional, Tuple, Union
from urllib.parse import ParseResult, urlparse, urlunparse
from typing import Awaitable, Callable, Optional
from uuid import uuid4

import requests
from werkzeug.utils import secure_filename

from murfey.util.models import Visit

logger = logging.getLogger("murfey.util")


def read_config() -> configparser.ConfigParser:
config = configparser.ConfigParser()
try:
mcch = os.environ.get("MURFEY_CLIENT_CONFIG_HOME")
murfey_client_config_home = Path(mcch) if mcch else Path.home()
with open(murfey_client_config_home / ".murfey") as configfile:
config.read_file(configfile)
except FileNotFoundError:
logger.warning(
f"Murfey client configuration file {murfey_client_config_home / '.murfey'} not found"
)
if "Murfey" not in config:
config["Murfey"] = {}
return config


@lru_cache(maxsize=1)
def get_machine_config_client(
url: str, instrument_name: str = "", demo: bool = False
) -> dict:
_instrument_name: str | None = instrument_name or os.getenv("BEAMLINE")
if not _instrument_name:
return {}
return requests.get(f"{url}/instruments/{_instrument_name}/machine").json()


def authorised_requests() -> Tuple[Callable, Callable, Callable, Callable]:
token = read_config()["Murfey"].get("token", "")
_get = partial(requests.get, headers={"Authorization": f"Bearer {token}"})
_post = partial(requests.post, headers={"Authorization": f"Bearer {token}"})
_put = partial(requests.put, headers={"Authorization": f"Bearer {token}"})
_delete = partial(requests.delete, headers={"Authorization": f"Bearer {token}"})
return _get, _post, _put, _delete


requests.get, requests.post, requests.put, requests.delete = authorised_requests()


def sanitise(in_string: str) -> str:
return in_string.replace("\r\n", "").replace("\n", "")

Expand Down Expand Up @@ -113,106 +65,6 @@ def posix_path(path: Path) -> str:
return str(path)


def _get_visit_list(api_base: ParseResult, instrument_name: str):
get_visits_url = api_base._replace(
path=f"/instruments/{instrument_name}/visits_raw"
)
server_reply = requests.get(get_visits_url.geturl())
if server_reply.status_code != 200:
raise ValueError(f"Server unreachable ({server_reply.status_code})")
return [Visit.parse_obj(v) for v in server_reply.json()]


def capture_post(url: str, json: dict | list = {}) -> requests.Response | None:
try:
response = requests.post(url, json=json)
except Exception as e:
logger.error(f"Exception encountered in post to {url}: {e}")
response = requests.Response()
if response.status_code != 200:
logger.warning(
f"Response to post to {url} with data {json} had status code "
f"{response.status_code}. The reason given was {response.reason}"
)
split_url = urlparse(url)
client_config = read_config()
failure_url = urlunparse(
split_url._replace(
path=f"/instruments/{client_config['Murfey']['instrument_name']}/failed_client_post"
)
)
try:
resend_response = requests.post(
failure_url, json={"url": url, "data": json}
)
except Exception as e:
logger.error(f"Exception encountered in post to {failure_url}: {e}")
resend_response = requests.Response()
if resend_response.status_code != 200:
logger.warning(
f"Response to post to {failure_url} failed with {resend_response.reason}"
)

return response


def capture_get(url: str) -> requests.Response | None:
try:
response = requests.get(url)
except Exception as e:
logger.error(f"Exception encountered in get from {url}: {e}")
response = None
if response and response.status_code != 200:
logger.warning(
f"Response to get from {url} had status code {response.status_code}. "
f"The reason given was {response.reason}"
)
return response


def set_default_acquisition_output(
new_output_dir: Path,
software_settings_output_directories: Dict[str, List[str]],
safe: bool = True,
):
for p, keys in software_settings_output_directories.items():
if safe:
settings_copy_path = Path(p)
settings_copy_path = settings_copy_path.parent / (
"_murfey_" + settings_copy_path.name
)
shutil.copy(p, str(settings_copy_path))
with open(p, "r") as for_parsing:
settings = json.load(for_parsing)
# for safety
settings_copy = copy.deepcopy(settings)

def _set(d: dict, keys_list: List[str], value: str) -> dict:
if len(keys_list) > 1:
tmp_value: Union[dict, str] = _set(
d[keys_list[0]], keys_list[1:], value
)
else:
tmp_value = value
return {_k: tmp_value if _k == keys_list[0] else _v for _k, _v in d.items()}

settings_copy = _set(settings_copy, keys, str(new_output_dir))

def _check_dict_structure(d1: dict, d2: dict) -> bool:
if set(d1.keys()) != set(d2.keys()):
return False
for k in d1.keys():
if isinstance(d1[k], dict):
if not isinstance(d2[k], dict):
return False
_check_dict_structure(d1[k], d2[k])
return True

if _check_dict_structure(settings, settings_copy):
with open(p, "w") as sf:
json.dump(settings_copy, sf)


class Observer:
"""
A helper class implementing the observer pattern supporting both
Expand Down
Loading