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
4 changes: 4 additions & 0 deletions .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ jobs:
ruff check --config ruff.toml hololinked/serializers
ruff check --config ruff.toml hololinked/schema_validators
ruff check --config ruff.toml hololinked/storage
ruff check --config ruff.toml hololinked/metadata/td
ruff check --config ruff.toml hololinked/serialization.py
ruff check --config ruff.toml hololinked/schemas.py
ruff check --config ruff.toml hololinked/persistence.py
ruff check --config ruff.toml hololinked/ddl.py

- name: run ty type checker
if: matrix.tool == 'ty'
Expand All @@ -95,9 +97,11 @@ jobs:
ty check hololinked/serializers
ty check hololinked/schema_validators
ty check hololinked/storage
ty check hololinked/metadata/td
ty check hololinked/serialization.py
ty check hololinked/schemas.py
ty check hololinked/persistence.py
ty check hololinked/ddl.py

scan:
name: security scan (${{ matrix.tool }})
Expand Down
4 changes: 2 additions & 2 deletions hololinked/client/abstractions.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
import structlog

from hololinked.constants import Operations
from hololinked.td import ActionAffordance, EventAffordance, PropertyAffordance
from hololinked.td.forms import Form
from hololinked.metadata.td import ActionAffordance, EventAffordance, PropertyAffordance
from hololinked.metadata.td.forms import Form
from hololinked.utils import get_current_async_loop


Expand Down
16 changes: 8 additions & 8 deletions hololinked/client/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
)
from hololinked.constants import ZMQ_TRANSPORTS
from hololinked.core import Thing
from hololinked.td.interaction_affordance import (
from hololinked.metadata.td.interaction_affordance import (
ActionAffordance,
EventAffordance,
PropertyAffordance,
Expand Down Expand Up @@ -164,7 +164,7 @@ def zmq(

# Fetch the TD
Thing.get_thing_model # noqa: B018 # type: Action
FetchTDAffordance = Thing.get_thing_model.to_affordance()
FetchTDAffordance = Thing.get_thing_model.to_metadata()
FetchTDAffordance.override_defaults(name="get_thing_description", thing_id=thing_id)
FetchTD = ZMQAction(
resource=FetchTDAffordance,
Expand Down Expand Up @@ -193,7 +193,7 @@ def zmq(

# add properties
for name in TD.get("properties", []):
affordance = cast(PropertyAffordance, PropertyAffordance.from_TD(name, TD))
affordance = PropertyAffordance.from_TD(name, TD)
consumed_property = ZMQProperty(
resource=affordance,
sync_client=sync_zmq_client,
Expand All @@ -213,7 +213,7 @@ def zmq(
ClientFactory.add_event(object_proxy, consumed_observable)
# add actions
for action in TD.get("actions", []):
affordance = cast(ActionAffordance, ActionAffordance.from_TD(action, TD))
affordance = ActionAffordance.from_TD(action, TD)
consumed_action = ZMQAction(
resource=affordance,
sync_client=sync_zmq_client,
Expand All @@ -226,7 +226,7 @@ def zmq(
ClientFactory.add_action(object_proxy, consumed_action)
# add events
for event in TD.get("events", []):
affordance = cast(EventAffordance, EventAffordance.from_TD(event, TD))
affordance = EventAffordance.from_TD(event, TD)
consumed_event = ZMQEvent(
resource=affordance,
owner_inst=object_proxy,
Expand Down Expand Up @@ -395,7 +395,7 @@ def http(url: str, **kwargs) -> ObjectProxy:
object_proxy = ObjectProxy(id, td=TD, logger=logger, security=security, **kwargs)

for name in TD.get("properties", []):
affordance = cast(PropertyAffordance, PropertyAffordance.from_TD(name, TD))
affordance = PropertyAffordance.from_TD(name, TD)
consumed_property = HTTPProperty(
resource=affordance,
sync_client=req_rep_sync_client,
Expand All @@ -418,7 +418,7 @@ def http(url: str, **kwargs) -> ObjectProxy:
)
ClientFactory.add_event(object_proxy, consumed_event)
for action in TD.get("actions", []):
affordance = cast(ActionAffordance, ActionAffordance.from_TD(action, TD))
affordance = ActionAffordance.from_TD(action, TD)
consumed_action = HTTPAction(
resource=affordance,
sync_client=req_rep_sync_client,
Expand All @@ -430,7 +430,7 @@ def http(url: str, **kwargs) -> ObjectProxy:
)
ClientFactory.add_action(object_proxy, consumed_action)
for event in TD.get("events", []):
affordance = cast(EventAffordance, EventAffordance.from_TD(event, TD))
affordance = EventAffordance.from_TD(event, TD)
consumed_event = HTTPEvent(
resource=affordance,
sync_client=sse_sync_client,
Expand Down
4 changes: 2 additions & 2 deletions hololinked/client/http/consumed_interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
)
from hololinked.client.exceptions import raise_local_exception
from hololinked.constants import Operations
from hololinked.td.forms import Form
from hololinked.td.interaction_affordance import (
from hololinked.metadata.td.forms import Form
from hololinked.metadata.td.interaction_affordance import (
ActionAffordance,
EventAffordance,
PropertyAffordance,
Expand Down
7 changes: 5 additions & 2 deletions hololinked/client/mqtt/consumed_interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
from hololinked import Serializers
from hololinked.client.abstractions import SSE, ConsumedThingEvent
from hololinked.core.interfaces import BaseSerializer # noqa: F401
from hololinked.td.forms import Form
from hololinked.td.interaction_affordance import EventAffordance, PropertyAffordance
from hololinked.metadata.td.forms import Form
from hololinked.metadata.td.interaction_affordance import (
EventAffordance,
PropertyAffordance,
)


class MQTTConsumer(ConsumedThingEvent): # noqa: D101
Expand Down
22 changes: 10 additions & 12 deletions hololinked/client/zmq/consumed_interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,26 @@
ConsumedThingEvent,
ConsumedThingProperty,
)
from hololinked.client.exceptions import raise_local_exception

from ...constants import Operations
from ...core import Action, Thing # noqa: F401
from ...core.zmq.brokers import (
from hololinked.client.exceptions import ReplyNotArrivedError, raise_local_exception
from hololinked.constants import Operations
from hololinked.core import Action, Thing # noqa: F401
from hololinked.core.zmq.brokers import (
AsyncEventConsumer,
AsyncZMQClient,
BreakLoop,
EventConsumer,
SyncZMQClient,
)
from ...core.zmq.message import (
from hololinked.core.zmq.message import (
EMPTY_BYTE,
ERROR,
INVALID_MESSAGE,
TIMEOUT,
ResponseMessage,
)
from ...core.zmq.payloads import SerializableData
from ...td import ActionAffordance, EventAffordance, PropertyAffordance
from ...td.forms import Form
from ..exceptions import ReplyNotArrivedError
from hololinked.core.zmq.payloads import SerializableData
from hololinked.metadata.td import ActionAffordance, EventAffordance, PropertyAffordance
from hololinked.metadata.td.forms import Form


__error_message_types__ = [TIMEOUT, ERROR, INVALID_MESSAGE]
Expand Down Expand Up @@ -604,7 +602,7 @@ def __init__(
timeout for execution of action or property read/write
"""
action = Thing._set_properties # type: Action
resource = action.to_affordance(Thing)
resource = action.to_metadata(Thing)
resource._thing_id = owner_inst.thing_id
super().__init__(
resource=resource,
Expand Down Expand Up @@ -645,7 +643,7 @@ def __init__(
timeout for execution of action or property read/write
"""
action = Thing._get_properties # type: Action
resource = action.to_affordance(Thing)
resource = action.to_metadata(Thing)
resource._thing_id = owner_inst.thing_id
super().__init__(
resource=resource,
Expand Down
59 changes: 46 additions & 13 deletions hololinked/core/actions.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
"""Concrete definition of an Action. Implemention of async and sync versions, action decorator."""

from __future__ import annotations

import warnings

from enum import Enum
from inspect import getfullargspec, iscoroutinefunction
from types import FunctionType, MethodType
from typing import Any
from typing import TYPE_CHECKING, Any

import jsonschema

Expand All @@ -12,6 +16,7 @@
from hololinked import SchemaValidatorClasses
from hololinked.constants import JSON
from hololinked.core.exceptions import StateMachineError
from hololinked.core.interfaces.metadata import ActionMetadata
from hololinked.param.parameterized import ParameterizedFunction
from hololinked.utils import (
get_input_model_from_signature,
Expand All @@ -24,6 +29,11 @@
from .dataklasses import ActionInfoValidator


if TYPE_CHECKING:
from hololinked.core.interfaces import ActionMetadata
from hololinked.core.thing import Thing


class Action:
"""
Object that models an action.
Expand Down Expand Up @@ -91,7 +101,7 @@ def execution_info(self, value: ActionInfoValidator) -> None:
raise TypeError("execution_info must be of type ActionInfoValidator")
self._execution_info = value # type: ActionInfoValidator

def to_affordance(self, owner_inst=None):
def to_metadata(self, owner_inst: Thing | None = None, format: str = "wot") -> ActionMetadata:
"""
Generates a `ActionAffordance` TD fragment for this Action.

Expand All @@ -105,9 +115,12 @@ def to_affordance(self, owner_inst=None):
ActionAffordance
the affordance TD fragment for this action
"""
from ..td import ActionAffordance
from hololinked.ddl import MetadataFormats

return ActionAffordance.generate(self, owner_inst or self.owner)
return MetadataFormats.get(format).action.from_descriptor(
self,
owner_inst or self.owner,
)


class BoundAction:
Expand All @@ -132,7 +145,7 @@ def __init__(self, obj: FunctionType, descriptor: Action, owner_inst, owner) ->

def __post_init__(self):
# never called, neither possible to call, only type hinting
from .thing import Thing, ThingMeta
from .thing import ThingMeta

# owner class and instance
self.owner: ThingMeta
Expand All @@ -145,6 +158,7 @@ def __post_init__(self):
def validate_call(self, args, kwargs: dict[str, Any]) -> None:
"""
Validate the call to the action, like payload, state machine state etc.

Errors are raised as exceptions.

Parameters
Expand All @@ -153,6 +167,13 @@ def validate_call(self, args, kwargs: dict[str, Any]) -> None:
positional arguments to the action
kwargs: dict
keyword arguments to the action

Raises
------
StateMachineError
if the action cannot be executed in the current state of the owning thing
RuntimeError
if the action explicity accepts only keyword arguments but some positional arguments are given
"""
if self.execution_info.isparameterized and len(args) > 0:
raise RuntimeError("parameterized functions cannot have positional arguments")
Expand Down Expand Up @@ -202,7 +223,7 @@ def __getattribute__(self, name):
return self.obj.__doc__
return super().__getattribute__(name)

def to_affordance(self):
def to_metadata(self, owner_inst: Thing | None = None, format: str = "wot") -> ActionMetadata:
"""
Generates a `ActionAffordance` TD fragment for this Action.

Expand All @@ -216,17 +237,19 @@ def to_affordance(self):
ActionAffordance
the affordance TD fragment for this action
"""
return Action.to_affordance(self.descriptor, self.owner_inst or self.owner)
return Action.to_metadata(self.descriptor, owner_inst or self.owner_inst or self.owner, format=format)


class BoundSyncAction(BoundAction):
"""
non async(io) action call. The call is passed to the method as-it-is to allow local
Non-async(io) action call.

The call is passed to the method as-it-is to allow local
invocation without state machine checks. Use `external_call` to have validation.
"""

def external_call(self, *args, **kwargs):
"""Validated call to the action with state machine and payload checks"""
"""Validated call to the action with state machine and payload checks."""
self.validate_call(args, kwargs)
return self.__call__(*args, **kwargs)

Expand All @@ -238,12 +261,14 @@ def __call__(self, *args, **kwargs):

class BoundAsyncAction(BoundAction):
"""
async(io) action call. The call is passed to the method as-it-is to allow local
async(io) action call.

The call is passed to the method as-it-is to allow local
invocation without state machine checks. Use `external_call` to have validation.
"""

async def external_call(self, *args, **kwargs):
"""Validated call to the action with state machine and payload checks"""
"""Validated call to the action with state machine and payload checks."""
self.validate_call(args, kwargs)
return await self.__call__(*args, **kwargs)

Expand All @@ -263,8 +288,9 @@ def action(
**kwargs,
) -> Action:
"""
Decorate on your methods to make them accessible remotely or create 'actions' out of them. When used with hardware,
actions generally command the hardware to do something.
Decorate on your methods to make them accessible remotely or create 'actions' out of them.

When used with hardware, actions generally command the hardware to do something.

Parameters
----------
Expand Down Expand Up @@ -298,6 +324,13 @@ def action(
Action
returns the callable object wrapped in an `Action` object. When accessed at instance level,
a `BoundSyncAction` or `BoundAsyncAction` object is returned.

Raises
------
TypeError
if the decorated object is not a function or method, or if the input/output schema is of invalid type
ValueError
if the decorated function is a dunder method, or if unknown keyword arguments are provided
"""

def inner(obj):
Expand Down
Loading
Loading