Skip to content

Commit 726c5b1

Browse files
committed
Merge main
2 parents 477f58c + f0ff051 commit 726c5b1

23 files changed

+657
-189
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ jobs:
4141

4242
steps:
4343
- uses: actions/checkout@v4
44+
4445
- name: Use Python ${{ matrix.python-version }}
4546
uses: actions/setup-python@v5
4647
with:
@@ -67,13 +68,13 @@ jobs:
6768
docker run --detach --name rabbitmq -p 127.0.0.1:5672:5672 -p 127.0.0.1:15672:15672 test-rabbitmq
6869
docker container list -a
6970
70-
- name: Get ispyb database
71+
- name: Get ISPyB database
7172
uses: actions/download-artifact@v4
7273
with:
7374
name: database
7475
path: database/
7576

76-
- name: Install package
77+
- name: Install Murfey
7778
run: |
7879
set -eux
7980
pip install --disable-pip-version-check -e "."[cicd,client,server,developer]
@@ -84,7 +85,7 @@ jobs:
8485
mysql-version: "11.3"
8586
auto-start: false
8687

87-
- name: Set up test ipsyb database
88+
- name: Set up test ISPyB database
8889
run: |
8990
set -eu
9091
cp ".github/workflows/config/my.cnf" .my.cnf
@@ -101,9 +102,14 @@ jobs:
101102
schemas/ispyb/routines.sql \
102103
grants/ispyb_processing.sql \
103104
grants/ispyb_import.sql; do
104-
echo Importing ${f}...
105+
106+
echo "Patching ${f} in SQL files to fix CLI escape issues..."
107+
sed -i 's/\\-/-/g' "$f"
108+
109+
echo "Importing ${f}..."
105110
mariadb --defaults-file=.my.cnf < $f
106111
done
112+
107113
mariadb --defaults-file=.my.cnf -e "CREATE USER ispyb_api@'%' IDENTIFIED BY 'password_1234'; GRANT ispyb_processing to ispyb_api@'%'; GRANT ispyb_import to ispyb_api@'%'; SET DEFAULT ROLE ispyb_processing FOR ispyb_api@'%';"
108114
mariadb --defaults-file=.my.cnf -e "CREATE USER ispyb_api_future@'%' IDENTIFIED BY 'password_4321'; GRANT SELECT ON ispybtest.* to ispyb_api_future@'%';"
109115
mariadb --defaults-file=.my.cnf -e "CREATE USER ispyb_api_sqlalchemy@'%' IDENTIFIED BY 'password_5678'; GRANT SELECT ON ispybtest.* to ispyb_api_sqlalchemy@'%'; GRANT INSERT ON ispybtest.* to ispyb_api_sqlalchemy@'%'; GRANT UPDATE ON ispybtest.* to ispyb_api_sqlalchemy@'%';"
@@ -112,18 +118,17 @@ jobs:
112118
- name: Check RabbitMQ is alive
113119
run: wget -t 10 -w 1 http://127.0.0.1:15672 -O -
114120

115-
- name: Run tests
121+
- name: Run Murfey tests
116122
env:
117123
POSTGRES_HOST: localhost
118124
POSTGRES_PORT: 5432
119125
POSTGRES_DB: murfey_test_db
120126
POSTGRES_PASSWORD: psql_pwd
121127
POSTGRES_USER: psql_user
122128
run: |
123-
export ISPYB_CREDENTIALS=".github/workflows/config/ispyb.cfg"
124129
PYTHONDEVMODE=1 pytest -v -ra --cov=murfey --cov-report=xml --cov-branch
125130
126-
- name: Upload to Codecov
131+
- name: Upload test results to Codecov
127132
uses: codecov/codecov-action@v5
128133
with:
129134
name: ${{ matrix.python-version }}

.github/workflows/test.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ name: Build and test
33
on: [push, pull_request]
44

55
env:
6-
DATABASE_SCHEMA: 4.2.1 # released 2024-08-19
6+
ISPYB_DATABASE_SCHEMA: 4.6.0
77
# Installs from GitHub
88
# Versions: https://github.com/DiamondLightSource/ispyb-database/tags
99
# Previous version(s):
10-
# 4.1.0
10+
# 4.2.1 # released 2024-08-19
11+
# 4.1.0 # released 2024-03-26
1112

1213
permissions:
1314
contents: read
@@ -53,10 +54,10 @@ jobs:
5354
runs-on: ubuntu-latest
5455
steps:
5556
- uses: actions/checkout@v4
56-
- name: Download ISPyB DB schema v${{ env.DATABASE_SCHEMA }} for tests
57+
- name: Download ISPyB DB schema v${{ env.ISPYB_DATABASE_SCHEMA }} for tests
5758
run: |
5859
mkdir database
59-
wget -t 3 --waitretry=20 https://github.com/DiamondLightSource/ispyb-database/releases/download/v${{ env.DATABASE_SCHEMA }}/ispyb-database-${{ env.DATABASE_SCHEMA }}.tar.gz -O database/ispyb-database.tar.gz
60+
wget -t 3 --waitretry=20 https://github.com/DiamondLightSource/ispyb-database/releases/download/v${{ env.ISPYB_DATABASE_SCHEMA }}/ispyb-database-${{ env.ISPYB_DATABASE_SCHEMA }}.tar.gz -O database/ispyb-database.tar.gz
6061
- name: Store database artifact
6162
uses: actions/upload-artifact@v4
6263
with:

src/murfey/cli/spa_ispyb_messages.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from murfey.client.contexts.spa import _get_xml_list_index
2121
from murfey.server import _murfey_id, _register
22-
from murfey.server.ispyb import Session, TransportManager, get_session_id
22+
from murfey.server.ispyb import ISPyBSession, TransportManager, get_session_id
2323
from murfey.server.murfey_db import url
2424
from murfey.util import db
2525
from murfey.util.config import get_machine_config, get_microscope, get_security_config
@@ -256,7 +256,7 @@ def run():
256256
proposal_code=args.visit[:2],
257257
proposal_number=args.visit[2:].split("-")[0],
258258
visit_number=args.visit.split("-")[1],
259-
db=Session(),
259+
db=ISPyBSession(),
260260
),
261261
)
262262

src/murfey/client/__init__.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
import webbrowser
1212
from datetime import datetime
1313
from pathlib import Path
14+
from pprint import pprint
1415
from queue import Queue
15-
from typing import List, Literal
16+
from typing import Literal
1617
from urllib.parse import ParseResult, urlparse
1718

1819
import requests
@@ -53,7 +54,7 @@ def write_config(config: configparser.ConfigParser):
5354

5455

5556
def main_loop(
56-
source_watchers: List[murfey.client.watchdir.DirWatcher],
57+
source_watchers: list[murfey.client.watchdir.DirWatcher],
5758
appearance_time: float,
5859
transfer_all: bool,
5960
):
@@ -104,6 +105,7 @@ def _check_for_updates(
104105

105106

106107
def run():
108+
# Load client config and server information
107109
config = read_config()
108110
instrument_name = config["Murfey"]["instrument_name"]
109111
try:
@@ -122,6 +124,7 @@ def run():
122124
else:
123125
known_server = config["Murfey"].get("server")
124126

127+
# Set up argument parser with dynamic defaults based on client config
125128
parser = argparse.ArgumentParser(description="Start the Murfey client")
126129
parser.add_argument(
127130
"--server",
@@ -207,23 +210,23 @@ def run():
207210
default=False,
208211
help="Do not trigger processing for any data directories currently on disk (you may have started processing for them in a previous murfey run)",
209212
)
210-
211213
args = parser.parse_args()
212214

215+
# Logic to exit early based on parsed args
213216
if not args.server:
214217
exit("Murfey server not set. Please run with --server host:port")
215218
if not args.server.startswith(("http://", "https://")):
216219
if "://" in args.server:
217220
exit("Unknown server protocol. Only http:// and https:// are allowed")
218221
args.server = f"http://{args.server}"
219-
220222
if args.remove_files:
221223
remove_prompt = Confirm.ask(
222224
f"Are you sure you want to remove files from {args.source or Path('.').absolute()}?"
223225
)
224226
if not remove_prompt:
225227
exit("Exiting")
226228

229+
# If a new server URL is provided, save info to config file
227230
murfey_url = urlparse(args.server, allow_fragments=False)
228231
if args.server != known_server:
229232
# New server specified. Verify that it is real
@@ -245,8 +248,7 @@ def run():
245248
if args.no_transfer:
246249
log.info("No files will be transferred as --no-transfer flag was specified")
247250

248-
from pprint import pprint
249-
251+
# Check ISPyB (if set up) for ongoing visits
250252
ongoing_visits = []
251253
if args.visit:
252254
ongoing_visits = [args.visit]
@@ -263,35 +265,38 @@ def run():
263265

264266
_enable_webbrowser_in_cygwin()
265267

268+
# Set up additional log handlers
266269
log.setLevel(logging.DEBUG)
267270
log_queue = Queue()
268271
input_queue = Queue()
269272

270-
# rich_handler = DirectableRichHandler(log_queue, enable_link_path=False)
273+
# Rich-based console handler
271274
rich_handler = DirectableRichHandler(enable_link_path=False)
272275
rich_handler.setLevel(logging.DEBUG if args.debug else logging.INFO)
273276

277+
# Set up websocket app and handler
274278
client_id = requests.get(f"{murfey_url.geturl()}/new_client_id/").json()
275279
ws = murfey.client.websocket.WSApp(
276280
server=args.server,
277281
id=client_id["new_id"],
278282
)
283+
ws_handler = CustomHandler(ws.send)
279284

285+
# Add additional handlers and set logging levels
280286
logging.getLogger().addHandler(rich_handler)
281-
handler = CustomHandler(ws.send)
282-
logging.getLogger().addHandler(handler)
287+
logging.getLogger().addHandler(ws_handler)
283288
logging.getLogger("murfey").setLevel(logging.INFO)
284289
logging.getLogger("websocket").setLevel(logging.WARNING)
285290

286291
log.info("Starting Websocket connection")
287292

288-
status_bar = StatusBar()
289-
293+
# Load machine data for subsequent sections
290294
machine_data = requests.get(
291295
f"{murfey_url.geturl()}/instruments/{instrument_name}/machine"
292296
).json()
293297
gain_ref: Path | None = None
294298

299+
# Set up Murfey environment instance and map it to websocket app
295300
instance_environment = MurfeyInstanceEnvironment(
296301
url=murfey_url,
297302
client_id=ws.id,
@@ -308,9 +313,10 @@ def run():
308313
else ""
309314
),
310315
)
311-
312316
ws.environment = instance_environment
313317

318+
# Set up and run Murfey TUI app
319+
status_bar = StatusBar()
314320
rich_handler.redirect = True
315321
app = MurfeyTUI(
316322
environment=instance_environment,

src/murfey/client/multigrid_control.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class MultigridController:
5454
data_collection_parameters: dict = field(default_factory=lambda: {})
5555
token: str = ""
5656
_machine_config: dict = field(default_factory=lambda: {})
57+
visit_end_time: Optional[datetime] = None
5758

5859
def __post_init__(self):
5960
if self.token:
@@ -105,6 +106,15 @@ def __post_init__(self):
105106
register_client=False,
106107
)
107108

109+
if self.visit_end_time:
110+
current_time = datetime.now()
111+
server_timestamp = requests.get(
112+
f"{self.murfey_url}{session_router.url_path_for('get_current_timestamp')}"
113+
).json()["timestamp"]
114+
self.visit_end_time += current_time - datetime.fromtimestamp(
115+
server_timestamp
116+
)
117+
108118
def _multigrid_watcher_finalised(self):
109119
self.multigrid_watcher_active = False
110120
self.dormancy_check()
@@ -156,7 +166,8 @@ def _start_rsyncer_multigrid(
156166
tag: str = "",
157167
limited: bool = False,
158168
):
159-
log.info(f"starting multigrid rsyncer: {source}")
169+
log.info(f"Starting multigrid rsyncer: {source}")
170+
log.debug(f"Analysis of {source} is {('enabled' if analyse else 'disabled')}")
160171
destination_overrides = destination_overrides or {}
161172
machine_data = requests.get(
162173
f"{self._environment.url.geturl()}{session_router.url_path_for('machine_info_by_instrument', instrument_name=self.instrument_name)}"
@@ -283,6 +294,7 @@ def _start_rsyncer(
283294
stop_callback=self._rsyncer_stopped,
284295
do_transfer=self.do_transfer,
285296
remove_files=remove_files,
297+
end_time=self.visit_end_time,
286298
)
287299

288300
def rsync_result(update: RSyncerUpdate):

src/murfey/client/rsync.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import subprocess
1414
import threading
1515
import time
16+
from datetime import datetime
1617
from enum import Enum
1718
from pathlib import Path
1819
from typing import Awaitable, Callable, List, NamedTuple
@@ -63,6 +64,7 @@ def __init__(
6364
remove_files: bool = False,
6465
required_substrings_for_removal: List[str] = [],
6566
notify: bool = True,
67+
end_time: datetime | None = None,
6668
):
6769
super().__init__()
6870
self._basepath = basepath_local.absolute()
@@ -76,6 +78,9 @@ def __init__(
7678
self._server_url = server_url
7779
self._notify = notify
7880
self._finalised = False
81+
self._end_time = end_time
82+
83+
self._skipped_files: List[Path] = []
7984

8085
# Set rsync destination
8186
if local:
@@ -214,6 +219,10 @@ def enqueue(self, file_path: Path):
214219
absolute_path = self._basepath / file_path
215220
self.queue.put(absolute_path)
216221

222+
def flush_skipped(self):
223+
for f in self._skipped_files:
224+
self.queue.put(f)
225+
217226
def _process(self):
218227
logger.info("RSync thread starting")
219228
files_to_transfer: list[Path]
@@ -304,14 +313,23 @@ def _fake_transfer(self, files: list[Path]) -> bool:
304313

305314
return True
306315

307-
def _transfer(self, files: list[Path]) -> bool:
316+
def _transfer(self, infiles: list[Path]) -> bool:
308317
"""
309318
Transfer files via an rsync sub-process, and parses the rsync stdout to verify
310319
the success of the transfer.
311320
"""
312321

313322
# Set up initial variables
314-
files = [f for f in files if f.is_file()]
323+
if self._end_time:
324+
files = [
325+
f
326+
for f in infiles
327+
if f.is_file() and f.stat().st_ctime < self._end_time.timestamp()
328+
]
329+
self._skipped_files.extend(set(infiles).difference(set(files)))
330+
else:
331+
files = [f for f in infiles if f.is_file()]
332+
315333
previously_transferred = self._files_transferred
316334
transfer_success: set[Path] = set()
317335
successful_updates: list[RSyncerUpdate] = []

0 commit comments

Comments
 (0)