Skip to content
Closed
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
1 change: 0 additions & 1 deletion scripts/make-app-icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
from __future__ import annotations

import argparse
import colorsys
import re
import subprocess
import sys
Expand Down
13 changes: 10 additions & 3 deletions src/experimaestro/scheduler/remote/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -876,15 +876,22 @@ def _notification_to_event(self, method: str, params: Dict) -> Optional[EventBas

event_type = params.get("event_type")
data = params.get("data", {})
if not event_type:
logger.warning("Notification missing event_type: %s", params)
return None

event_class = EventBase.get_class(event_type)
if event_class is None:
logger.warning("Unknown event type: %s", event_type)
return None

event_dict = dict(data)
event_dict["event_type"] = event_type

try:
return event_class(**data)
except TypeError as e:
logger.warning("Error deserializing event %s: %s", event_type, e)
return EventBase.from_dict(event_dict)
except Exception as e:
logger.warning("Error deserializing event %s: %s", event_type, e, exc_info=True)
return None

def _notify_listeners(self, event: EventBase):
Expand Down
15 changes: 14 additions & 1 deletion src/experimaestro/scheduler/state_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -1243,7 +1243,20 @@ def _add_submitted_job(self, event: "EventBase") -> None:
self._job_cache[cache_key] = job
logger.debug("Added job %s to cache from event", cache_key)

tags = {tag.key: tag.value for tag in getattr(event, "tags", [])}
tags = {}
for tag in getattr(event, "tags", []) or []:
if isinstance(tag, dict):
k, v = tag.get("key"), tag.get("value")
else:
k, v = getattr(tag, "key", None), getattr(tag, "value", None)
if k is not None:
tags[k] = v
else:
logger.warning(
"Invalid tag element in event %s: %s",
type(event).__name__,
tag,
)
depends_on = list(getattr(event, "depends_on", []))
job_info = ExperimentJobInformation(
job_id=event.job_id,
Expand Down
29 changes: 29 additions & 0 deletions src/experimaestro/tests/test_remote_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -1753,6 +1753,35 @@ def test_client_notification_non_state_event(self, client):

assert event is None

def test_client_notification_to_event_job_submitted_tags(self, client):
"""Test client converts notification to JobSubmittedEvent with JobTag objects"""
from experimaestro.scheduler.state_status import JobSubmittedEvent, JobTag
from experimaestro.scheduler.remote.protocol import NotificationMethod

event = client._notification_to_event(
NotificationMethod.STATE_EVENT.value,
{
"event_type": "JobSubmittedEvent",
"data": {
"job_id": "job_tags_test",
"task_id": "task123",
"run_id": "run_001",
"tags": [{"key": "model", "value": "bert"}, {"key": "lr", "value": "0.01"}],
"timestamp": 1704067260.0,
},
},
)

assert isinstance(event, JobSubmittedEvent)
assert len(event.tags) == 2
assert isinstance(event.tags[0], JobTag)
assert event.tags[0].key == "model"
assert event.tags[0].value == "bert"

# Verify _add_submitted_job handles the event without AttributeError
client._add_submitted_job(event)



# =============================================================================
# Error Handling Tests
Expand Down
24 changes: 18 additions & 6 deletions src/experimaestro/tui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,9 +489,23 @@ def _handle_job_submitted(self, event: JobSubmittedEvent) -> None:
if jobs_table.current_experiment == event_exp_id:
# Add the new job's tags to the cache
if event.tags:
jobs_table.tags_map[event.job_id] = {
tag.key: tag.value for tag in event.tags
}
event_tags = {}
for tag in event.tags:
k = (
tag.get("key")
if isinstance(tag, dict)
else getattr(tag, "key", None)
)
v = (
tag.get("value")
if isinstance(tag, dict)
else getattr(tag, "value", None)
)
if k is not None:
event_tags[k] = v
jobs_table.tags_map[event.job_id] = event_tags
else:
event_tags = {}
# Add the new job's dependencies to the cache
if event.depends_on:
jobs_table.dependencies_map[event.job_id] = event.depends_on
Expand All @@ -508,9 +522,7 @@ def _handle_job_submitted(self, event: JobSubmittedEvent) -> None:
jobs_table.experiment_job_info[event.job_id] = ExperimentJobInformation(
job_id=event.job_id,
task_id=event.task_id,
tags=(
{tag.key: tag.value for tag in event.tags} if event.tags else {}
),
tags=event_tags,
timestamp=timestamp,
)
# Refresh to show the new job
Expand Down
8 changes: 7 additions & 1 deletion src/experimaestro/webui/state_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,13 @@ async def _handle_job_submitted(self, event: JobSubmittedEvent):
# Fetch the full job data from state provider
job = self.state_provider.get_job(event.task_id, event.job_id)
if job:
tags = [(tag.key, tag.value) for tag in event.tags] if event.tags else []
tags = [
(
tag["key"] if isinstance(tag, dict) else getattr(tag, "key"),
tag["value"] if isinstance(tag, dict) else getattr(tag, "value"),
)
for tag in event.tags
] if event.tags else []
payload = serialize_job(
job,
tags=tags,
Expand Down
Loading