-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Python API add events and health checks services
- Loading branch information
1 parent
3f20e50
commit ffd57c2
Showing
8 changed files
with
264 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
from typing import Any, Callable, cast, List | ||
|
||
from grpc import Channel | ||
|
||
from .results import ArmoniKResults | ||
from ..common import EventTypes, Filter, NewTaskEvent, NewResultEvent, ResultOwnerUpdateEvent, ResultStatusUpdateEvent, TaskStatusUpdateEvent, ResultStatus, Event | ||
from .results import ResultFieldFilter | ||
from ..protogen.client.events_service_pb2_grpc import EventsStub | ||
from ..protogen.common.events_common_pb2 import EventSubscriptionRequest, EventSubscriptionResponse | ||
from ..protogen.common.results_filters_pb2 import Filters as rawResultFilters | ||
from ..protogen.common.tasks_filters_pb2 import Filters as rawTaskFilters | ||
|
||
class ArmoniKEvents: | ||
|
||
_events_obj_mapping = { | ||
"new_result": NewResultEvent, | ||
"new_task": NewTaskEvent, | ||
"result_owner_update": ResultOwnerUpdateEvent, | ||
"result_status_update": ResultStatusUpdateEvent, | ||
"task_status_update": TaskStatusUpdateEvent | ||
} | ||
|
||
def __init__(self, grpc_channel: Channel): | ||
"""Events service client | ||
Args: | ||
grpc_channel: gRPC channel to use | ||
""" | ||
self._client = EventsStub(grpc_channel) | ||
self._results_client = ArmoniKResults(grpc_channel) | ||
|
||
def get_events(self, session_id: str, event_types: List[EventTypes], event_handlers: List[Callable[[str, EventTypes, Event], bool]], task_filter: Filter | None = None, result_filter: Filter | None = None) -> None: | ||
"""Get events that represents updates of result and tasks data. | ||
Args: | ||
session_id: The ID of the session. | ||
event_types: The list of the types of event to catch. | ||
event_handlers: The list of handlers that process the events. Handlers are evaluated in he order they are provided. | ||
An handler takes three positional arguments: the ID of the session, the type of event and the event as an object. | ||
An handler returns a boolean, if True the process continues, otherwise the stream is closed and the service stops | ||
listening to new events. | ||
task_filter: A filter on tasks. | ||
result_filter: A filter on results. | ||
""" | ||
request = EventSubscriptionRequest( | ||
session_id=session_id, | ||
returned_events=event_types | ||
) | ||
if task_filter: | ||
request.tasks_filters=cast(rawTaskFilters, task_filter.to_disjunction().to_message()), | ||
if result_filter: | ||
request.results_filters=cast(rawResultFilters, result_filter.to_disjunction().to_message()), | ||
|
||
streaming_call = self._client.GetEvents(request) | ||
for message in streaming_call: | ||
event_type = message.WhichOneof("update") | ||
if any([event_handler(session_id, EventTypes.from_string(event_type), self._events_obj_mapping[event_type].from_raw_event(getattr(message, event_type))) for event_handler in event_handlers]): | ||
break | ||
|
||
def wait_for_result_availability(self, result_id: str, session_id: str) -> None: | ||
"""Wait until a result is ready i.e its status updates to COMPLETED. | ||
Args: | ||
result_id: The ID of the result. | ||
session_id: The ID of the session. | ||
Raises: | ||
RuntimeError: If the result status is ABORTED. | ||
""" | ||
def handler(session_id, event_type, event): | ||
if not isinstance(event, ResultStatusUpdateEvent): | ||
raise ValueError("Handler should receive event of type 'ResultStatusUpdateEvent'.") | ||
if event.status == ResultStatus.COMPLETED: | ||
return False | ||
elif event.status == ResultStatus.ABORTED: | ||
raise RuntimeError(f"Result {result.name} with ID {result_id} is aborted.") | ||
return True | ||
|
||
result = self._results_client.get_result(result_id) | ||
if result.status == ResultStatus.COMPLETED: | ||
return | ||
elif result.status == ResultStatus.ABORTED: | ||
raise RuntimeError(f"Result {result.name} with ID {result_id} is aborted.") | ||
|
||
self.get_events(session_id, [EventTypes.RESULT_STATUS_UPDATE], [handler], result_filter=(ResultFieldFilter.RESULT_ID == result_id)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from typing import cast, List, Tuple | ||
|
||
from grpc import Channel | ||
|
||
from ..common import HealthCheckStatus | ||
from ..protogen.client.health_checks_service_pb2_grpc import HealthChecksServiceStub | ||
from ..protogen.common.health_checks_common_pb2 import CheckHealthRequest, CheckHealthResponse | ||
|
||
|
||
class ArmoniKHealthChecks: | ||
def __init__(self, grpc_channel: Channel): | ||
""" Result service client | ||
Args: | ||
grpc_channel: gRPC channel to use | ||
""" | ||
self._client = HealthChecksServiceStub(grpc_channel) | ||
|
||
def check_health(self): | ||
response: CheckHealthResponse = self._client.CheckHealth(CheckHealthRequest()) | ||
return {service.name: {"message": service.message, "status": service.healthy} for service in response.services} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,41 @@ | ||
from .helpers import datetime_to_timestamp, timestamp_to_datetime, duration_to_timedelta, timedelta_to_duration, get_task_filter | ||
from .helpers import ( | ||
datetime_to_timestamp, | ||
timestamp_to_datetime, | ||
duration_to_timedelta, | ||
timedelta_to_duration, | ||
get_task_filter, | ||
batched | ||
) | ||
from .objects import Task, TaskDefinition, TaskOptions, Output, ResultAvailability, Session, Result, Partition | ||
from .enumwrapper import HealthCheckStatus, TaskStatus, Direction, ResultStatus, SessionStatus | ||
from .filter import StringFilter, StatusFilter | ||
from .enumwrapper import HealthCheckStatus, TaskStatus, Direction, SessionStatus, ResultStatus, EventTypes, ServiceHealthCheckStatus | ||
from .events import * | ||
from .filter import Filter, StringFilter, StatusFilter | ||
|
||
__all__ = [ | ||
'datetime_to_timestamp', | ||
'timestamp_to_datetime', | ||
'duration_to_timedelta', | ||
'timedelta_to_duration', | ||
'get_task_filter', | ||
'batched', | ||
'Task', | ||
'TaskDefinition', | ||
'TaskOptions', | ||
'Output', | ||
'ResultAvailability', | ||
'Session', | ||
'Result', | ||
'Partition', | ||
'HealthCheckStatus', | ||
'TaskStatus', | ||
'Direction', | ||
'SessionStatus', | ||
'ResultStatus', | ||
'EventTypes', | ||
# Include all names from events module | ||
# Add names from filter module | ||
'Filter', | ||
'StringFilter', | ||
'StatusFilter', | ||
'ServiceHealthCheckStatus' | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from abc import ABC | ||
from typing import List | ||
|
||
from dataclasses import dataclass, fields | ||
|
||
from .enumwrapper import TaskStatus, ResultStatus | ||
|
||
|
||
class Event(ABC): | ||
@classmethod | ||
def from_raw_event(cls, raw_event): | ||
values = {} | ||
for raw_field in cls.__annotations__.keys(): | ||
values[raw_field] = getattr(raw_event, raw_field) | ||
return cls(**values) | ||
|
||
|
||
@dataclass | ||
class TaskStatusUpdateEvent(Event): | ||
task_id: str | ||
status: TaskStatus | ||
|
||
|
||
@dataclass | ||
class ResultStatusUpdateEvent(Event): | ||
result_id: str | ||
status: ResultStatus | ||
|
||
|
||
@dataclass | ||
class ResultOwnerUpdateEvent(Event): | ||
result_id: str | ||
previous_owner_id: str | ||
current_owner_id: str | ||
|
||
|
||
@dataclass | ||
class NewTaskEvent(Event): | ||
task_id: str | ||
payload_id: str | ||
origin_task_id: str | ||
status: TaskStatus | ||
expected_output_keys: List[str] | ||
data_dependencies: List[str] | ||
retry_of_ids: List[str] | ||
parent_task_ids: List[str] | ||
|
||
|
||
@dataclass | ||
class NewResultEvent(Event): | ||
result_id: str | ||
owner_id: str | ||
status: ResultStatus |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from .conftest import all_rpc_called, rpc_called, get_client | ||
from armonik.client import ArmoniKEvents | ||
from armonik.common import EventTypes, NewResultEvent, ResultStatus | ||
|
||
|
||
class TestArmoniKEvents: | ||
def test_get_events_no_filter(self): | ||
def test_handler(session_id, event_type, event): | ||
assert session_id == "session-id" | ||
assert event_type == EventTypes.NEW_RESULT | ||
assert isinstance(event, NewResultEvent) | ||
assert event.result_id == "result-id" | ||
assert event.owner_id == "owner-id" | ||
assert event.status == ResultStatus.CREATED | ||
|
||
tasks_client: ArmoniKEvents = get_client("Events") | ||
tasks_client.get_events("session-id", [EventTypes.TASK_STATUS_UPDATE], [test_handler]) | ||
|
||
assert rpc_called("Events", "GetEvents") | ||
|
||
def test_service_fully_implemented(self): | ||
assert all_rpc_called("Events") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import datetime | ||
|
||
from .conftest import all_rpc_called, rpc_called, get_client | ||
from armonik.client import ArmoniKHealthChecks | ||
from armonik.common import ServiceHealthCheckStatus | ||
|
||
|
||
class TestArmoniKHealthChecks: | ||
|
||
def test_check_health(self): | ||
health_checks_client: ArmoniKHealthChecks = get_client("HealthChecks") | ||
services_health = health_checks_client.check_health() | ||
|
||
assert rpc_called("HealthChecks", "CheckHealth") | ||
assert services_health == {'mock': {'message': 'Mock is healthy', 'status': ServiceHealthCheckStatus.HEALTHY}} | ||
|
||
def test_service_fully_implemented(self): | ||
assert all_rpc_called("HealthChecks") |