Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
21 changes: 15 additions & 6 deletions src/murfey/client/destinations.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,28 @@


def find_longest_data_directory(
match_path: Path, data_directories: list[str] | list[Path]
):
match_path: Path, data_directories: list[str | Path]
) -> tuple[Path | None, Path | None]:
"""
Determine the longest path in the data_directories list
which the match path is relative to
"""
base_dir: Path | None = None
mid_dir: Path | None = None
for dd in data_directories:
dd_base = str(Path(dd).absolute())
if str(match_path).startswith(str(dd)) and len(dd_base) > len(str(base_dir)):
base_dir = Path(dd_base)
mid_dir = match_path.absolute().relative_to(Path(base_dir)).parent
dd_base = Path(dd).absolute()
if match_path.absolute().is_relative_to(dd_base) and len(dd_base.parts) > (
len(base_dir.parts) if base_dir else 0
):
base_dir = dd_base
# Extract the path parts from immediately after the visit directory
mid_dir = Path(
"/".join(
list(
match_path.absolute().relative_to(Path(base_dir)).parent.parts
)[1:]
)
)
return base_dir, mid_dir


Expand Down
3 changes: 2 additions & 1 deletion src/murfey/util/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import copy
import os
import socket
from functools import lru_cache
Expand Down Expand Up @@ -183,7 +184,7 @@ def _recursive_update(base: dict[str, Any], new: dict[str, Any]):
base[key].extend(value)
# Otherwise, overwrite/add values as normal
else:
base[key] = value
base[key] = copy.deepcopy(value)
return base

# Load the dict from the file
Expand Down
109 changes: 108 additions & 1 deletion tests/client/test_destinations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,65 @@
from unittest import mock

import pytest
from pytest_mock import MockerFixture

from murfey.client.destinations import (
determine_default_destination,
find_longest_data_directory,
)


@pytest.mark.parametrize(
"test_params",
(
# File to match | Use Path? | Expected result
(
"X:/cm12345-6/Supervisor/Images-Disc1/file",
True,
("X:/", "Supervisor/Images-Disc1"),
),
(
"X:/DATA/cm12345-6/Supervisor/Images-Disc1/file",
False,
("X:/DATA", "Supervisor/Images-Disc1"),
),
(
"X:/DoseFractions/cm12345-6/Supervisor/Images-Disc1/file",
True,
("X:/DoseFractions", "Supervisor/Images-Disc1"),
),
(
"X:/DoseFractions/DATA/cm12345-6/Supervisor/Images-Disc1/file",
False,
("X:/DoseFractions/DATA", "Supervisor/Images-Disc1"),
),
),
)
def test_find_longest_data_directory(
mocker: MockerFixture, test_params: tuple[str, bool, tuple[str, str]]
):
# Unpack test params
match_path, use_path, (expected_base_dir, expected_mid_dir) = test_params
data_directories: list[str | Path] = [
Path(dd) if use_path else dd
for dd in (
"X:",
"X:/DATA",
"X:/DoseFractions",
"X:/DoseFractions/DATA",
)
]
# Patch Pathlib's 'absolute()' function, since we are simulating Windows on Linux
mocker.patch("murfey.client.destinations.Path.absolute", lambda self: self)

base_dir, mid_dir = find_longest_data_directory(
Path(match_path),
data_directories,
)
assert base_dir == Path(expected_base_dir)
assert mid_dir == Path(expected_mid_dir)
pass
Comment thread Fixed

from murfey.client.destinations import determine_default_destination

source_list = [
["X:/DoseFractions/cm12345-6/Supervisor", "Supervisor", True, "extra_name"],
Expand Down Expand Up @@ -71,6 +128,56 @@
)


@mock.patch("murfey.client.destinations.capture_get")
@mock.patch("murfey.client.destinations.capture_post")
def test_determine_default_destinations_short_path(mock_post, mock_get):
"""Test for the case where the data directory is a drive"""
mock_environment = mock.Mock()
mock_environment.murfey_session = 2
mock_environment.instrument_name = "m01"
mock_environment.destination_registry = {}

mock_get().json.return_value = {"data_directories": ["X:/"]}
mock_post().json.return_value = {"suggested_path": "/base_path/2025/cm12345-6/raw"}

destination = determine_default_destination(
visit="cm12345-6",
source=Path("X:/cm12345-6/Supervisor"),
destination="2025",
environment=mock_environment,
token="token",
touch=True,
extra_directory="",
use_suggested_path=True,
)
mock_get.assert_any_call(
base_url=str(mock_environment.url.geturl()),
router_name="session_control.router",
function_name="machine_info_by_instrument",
token="token",
instrument_name="m01",
)
mock_post.assert_any_call(
base_url=str(mock_environment.url.geturl()),
router_name="file_io_instrument.router",
function_name="suggest_path",
token="token",
visit_name="cm12345-6",
session_id=2,
data={
"base_path": "2025/cm12345-6/raw",
"touch": True,
"extra_directory": "",
},
)

assert destination == "/base_path/2025/cm12345-6/raw/"
assert (
mock_environment.destination_registry.get("Supervisor")
== "/base_path/2025/cm12345-6/raw"
)


@mock.patch("murfey.client.destinations.capture_get")
@pytest.mark.parametrize("sources", source_list)
def test_determine_default_destinations_skip_suggested(mock_get, sources):
Expand Down
48 changes: 37 additions & 11 deletions tests/util/test_config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
from pathlib import Path
from typing import Any

Expand Down Expand Up @@ -132,19 +133,31 @@ def mock_instrument_config():
}


@pytest.fixture
def mock_second_instrument_config(mock_instrument_config: dict[str, Any]):
"""Test the case of recursive dictionaries which are present
in both the general and instrument configs"""
second_instrument_config = copy.deepcopy(mock_instrument_config)
second_instrument_config.update(
{"pkg_1": {"file_path": "/path/to/pkg_1/file_2.txt"}}
)
return second_instrument_config


@pytest.fixture
def mock_hierarchical_machine_config_yaml(
mock_general_config: dict[str, Any],
mock_tem_shared_config: dict[str, Any],
mock_instrument_config: dict[str, Any],
mock_second_instrument_config: dict[str, Any],
tmp_path: Path,
):
# Create machine config (with all currently supported keys) for the instrument
hierarchical_config = {
"general": mock_general_config,
"tem": mock_tem_shared_config,
"m01": mock_instrument_config,
"m02": mock_instrument_config,
"m02": mock_second_instrument_config,
}
config_file = tmp_path / "config" / "murfey-machine-config-hierarchical.yaml"
config_file.parent.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -210,6 +223,7 @@ def test_get_machine_config(
mock_general_config: dict[str, Any],
mock_tem_shared_config: dict[str, Any],
mock_instrument_config: dict[str, Any],
mock_second_instrument_config: dict[str, Any],
mock_hierarchical_machine_config_yaml: Path,
mock_standard_machine_config_yaml: Path,
test_params: tuple[str, list[str]],
Expand Down Expand Up @@ -353,14 +367,26 @@ def test_get_machine_config(
== mock_instrument_config["node_creator_queue"]
)
# Extra keys
assert config[i].pkg_1 == {
"file_path": "/path/to/pkg_1/file.txt",
"command": [
"/path/to/executable",
"--some_arg",
"-a",
"./path/to/file",
],
"step_size": 100,
}
if config_to_test == "standard" or i == "m01":
assert config[i].pkg_1 == {
"file_path": "/path/to/pkg_1/file.txt",
"command": [
"/path/to/executable",
"--some_arg",
"-a",
"./path/to/file",
],
"step_size": 100,
}
elif i == "m02":
assert config[i].pkg_1 == {
"file_path": "/path/to/pkg_1/file_2.txt",
"command": [
"/path/to/executable",
"--some_arg",
"-a",
"./path/to/file",
],
"step_size": 100,
}
assert config[i].pkg_2 == mock_general_config["pkg_2"]