Skip to content
Merged
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "zmqruntime"
version = "0.6.0"
version = "0.6.1"
description = "Generic ZMQ-based distributed execution framework"
authors = [{name = "Tristan Simas", email = "tristan.simas@mail.mcgill.ca"}]
license = {text = "MIT"}
Expand Down
2 changes: 0 additions & 2 deletions src/zmqruntime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@
"ShutdownAck",
"SocketType",
"StatusRequest",
"TaskPhase",
"TaskProgress",
"TaskStatus",
"TypedWireMessage",
"WireMessage",
"WireView",
Expand Down
44 changes: 5 additions & 39 deletions src/zmqruntime/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,37 +100,6 @@ def declared_items(cls, data: Mapping[str, Any]) -> Mapping[str, Any]:
# =============================================================================


class TaskPhase(Enum):
"""Generic task phases - base vocabulary for workflow states.

Applications can extend with their own phase enums.
TaskProgress accepts TaskPhase | <AppPhase> union types.
"""

INIT = "init"
QUEUED = "queued"
RUNNING = "running"
COMPILE = "compile"
SUCCESS = "success"
FAILED = "failed"
CANCELLED = "cancelled"


class TaskStatus(Enum):
"""Generic task status - base vocabulary for status values.

Applications can extend with their own status enums.
TaskProgress accepts TaskStatus | <AppStatus> union types.
"""

PENDING = "pending"
STARTED = "started"
RUNNING = "running"
SUCCESS = "success"
FAILED = "failed"
CANCELLED = "cancelled"


@dataclass(frozen=True)
class WorkerState(WireMessage):
"""Generic worker/process information."""
Expand All @@ -147,13 +116,14 @@ class TaskProgress(WireView):
"""The generic fields of one progress event.

Applications publish progress events that carry these fields beside their
own; the runtime reads and validates only these. Phase and status accept
strings (as transported) or application enums (encoded by value).
own; the runtime reads and validates only these. Phase and status are the
application's vocabulary: an application event narrows their type, and the
runtime sees the spelling ``to_jsonable`` gives them on the wire.
"""

execution_id: str
phase: str | Enum
status: str | Enum
phase: object
status: object
percent: float
timestamp: float
completed: int
Expand All @@ -169,10 +139,6 @@ def __post_init__(self):
raise ValueError(f"percent must be in [0, 100], got {self.percent}")
if self.completed > self.total:
raise ValueError(f"completed ({self.completed}) cannot exceed total ({self.total})")
if not isinstance(self.phase, (str, Enum)):
raise TypeError(f"phase must be a string or Enum, got {type(self.phase)}")
if not isinstance(self.status, (str, Enum)):
raise TypeError(f"status must be a string or Enum, got {type(self.status)}")


# =============================================================================
Expand Down
28 changes: 28 additions & 0 deletions tests/test_messages.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from dataclasses import dataclass

import pytest
from metaclass_registry import AutoRegisterMeta

from zmqruntime.client import EndpointClientSession, EndpointCompatibilityClientABC
from zmqruntime.messages import (
Expand All @@ -14,6 +17,7 @@
ProcessResourceUsage,
ResponseType,
ServerRole,
TaskProgress,
WorkerState,
)

Expand Down Expand Up @@ -283,3 +287,27 @@ def test_typed_messages_reject_another_kind() -> None:
messages.ImageAck.from_dict(messages.ShutdownAck("stopping").to_dict())
with pytest.raises(ValueError, match="message_type"):
messages.StatusRequest.from_dict(CancelRequest(execution_id="e-1").to_dict())


class _Phase(metaclass=AutoRegisterMeta):
__registry_key__ = "name"
__skip_if_no_key__ = True
name = None


class _Compiling(_Phase):
name = "compiling"


@dataclass(frozen=True)
class _ApplicationProgress(TaskProgress):
phase: type[_Phase]
status: type[_Phase]


def test_task_progress_carries_the_application_phase_vocabulary() -> None:
event = _ApplicationProgress("e-1", _Compiling, _Compiling, 10.0, 1.0, 1, 10)
wire = event.to_dict()

assert wire["phase"] == wire["status"] == "compiling"
assert TaskProgress.from_dict(wire).phase == "compiling"
Loading