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
6 changes: 6 additions & 0 deletions .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,19 @@ jobs:
source .venv/bin/activate
ruff check --config ruff.toml hololinked/client
ruff check --config ruff.toml hololinked/serializers
ruff check --config ruff.toml hololinked/schema_validators
ruff check --config ruff.toml hololinked/serialization.py
ruff check --config ruff.toml hololinked/schemas.py

- name: run ty type checker
if: matrix.tool == 'ty'
run: |
source .venv/bin/activate
ty check hololinked/client
ty check hololinked/serializers
ty check hololinked/schema_validators
ty check hololinked/serialization.py
ty check hololinked/schemas.py

scan:
name: security scan (${{ matrix.tool }})
Expand Down
6 changes: 5 additions & 1 deletion hololinked/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
__version__ = "0.4.0"

from .config import global_config # noqa
import hololinked.core # noqa: F401
from .serialization import Serializers as Serializers
from .schemas import JSONSchema as JSONSchema, SchemaValidatorClasses as SchemaValidatorClasses

import hololinked.core # noqa: F401 # this one is lazy for most part
import hololinked.serializers # noqa: F401
import hololinked.schema_validators # noqa: F401
3 changes: 2 additions & 1 deletion hololinked/client/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from paho.mqtt.client import CallbackAPIVersion, MQTTMessage, MQTTProtocolVersion
from paho.mqtt.client import Client as PahoMQTTClient

from hololinked import Serializers
from hololinked.client.abstractions import (
ConsumedThingAction,
ConsumedThingEvent,
Expand All @@ -25,7 +26,7 @@
OAuthDirectAccessGrant,
)
from hololinked.constants import ZMQ_TRANSPORTS
from hololinked.core import Serializers, Thing
from hololinked.core import Thing
from hololinked.td.interaction_affordance import (
ActionAffordance,
EventAffordance,
Expand Down
2 changes: 1 addition & 1 deletion hololinked/client/http/consumed_interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import httpx
import structlog

from hololinked import Serializers
from hololinked.client.abstractions import (
SSE,
ConsumedThingAction,
Expand All @@ -20,7 +21,6 @@
)
from hololinked.client.exceptions import raise_local_exception
from hololinked.constants import Operations
from hololinked.core import Serializers
from hololinked.td.forms import Form
from hololinked.td.interaction_affordance import (
ActionAffordance,
Expand Down
2 changes: 1 addition & 1 deletion hololinked/client/mqtt/consumed_interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from paho.mqtt.client import Client as PahoMQTTClient
from paho.mqtt.client import MQTTMessage

from hololinked import Serializers
from hololinked.client.abstractions import SSE, ConsumedThingEvent
from hololinked.core import Serializers
from hololinked.core.interfaces import BaseSerializer # noqa: F401
from hololinked.td.forms import Form
from hololinked.td.interaction_affordance import EventAffordance, PropertyAffordance
Expand Down
1 change: 1 addition & 0 deletions hololinked/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# types
JSONSerializable = typing.Union[str, int, float, bool, None, typing.Dict[str, typing.Any], typing.List]
JSON = typing.Dict[str, JSONSerializable]
JSONSchema = typing.Dict[str, str | typing.Dict[str, typing.Any] | typing.List[typing.Dict[str, typing.Any]]]

byte_types = (bytes, bytearray, memoryview)

Expand Down
63 changes: 54 additions & 9 deletions hololinked/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,57 @@
State machines, meta classes, descriptor registries and the concrete implementation of how an operation is executed
is also included here.
"""
# Order of import is reflected in this file to avoid circular imports

from .thing import * # noqa
from .events import * # noqa
from .actions import * # noqa
from .property import * # noqa
from .state_machine import StateMachine as StateMachine
from .meta import ThingMeta as ThingMeta
from .serializer_registry import Serializers as Serializers

from typing import TYPE_CHECKING

# Interfaces must be available to register adappters
from .interfaces import BaseSchemaValidator as BaseSchemaValidator
from .interfaces import BaseSerializer as BaseSerializer


__all__ = [
"BaseSchemaValidator",
"BaseSerializer",
"action",
"Action",
"Event",
"ThingMeta",
"Property",
"StateMachine",
"Thing",
]

# Submodules that use SchemaValidatorClasses / Serializers are loaded lazily so
# that hololinked/__init__.py can finish registering the adapters (schema_validators,
# serializers) before any class body in meta.py / actions.py / property.py executes.
_lazy: dict[str, tuple[str, str]] = {
"action": (".actions", "action"),
"Action": (".actions", "Action"),
"Event": (".events", "Event"),
"ThingMeta": (".meta", "ThingMeta"),
"Property": (".property", "Property"),
"StateMachine": (".state_machine", "StateMachine"),
"Thing": (".thing", "Thing"),
}


def __getattr__(name: str):
if name in _lazy:
import importlib

module_path, attr = _lazy[name]
mod = importlib.import_module(module_path, package=__name__)
val = getattr(mod, attr)
globals()[name] = val # cache so subsequent access skips __getattr__
return val
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


if TYPE_CHECKING:
from .actions import Action as Action
from .actions import action as action
from .events import Event as Event
from .meta import ThingMeta as ThingMeta
from .property import Property as Property
from .state_machine import StateMachine as StateMachine
from .thing import Thing as Thing
34 changes: 18 additions & 16 deletions hololinked/core/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,34 @@

from pydantic import BaseModel, RootModel

from ..constants import JSON
from ..param.parameterized import ParameterizedFunction
from ..schema_validators.validators import JSONSchemaValidator, PydanticSchemaValidator
from ..utils import (
from hololinked import SchemaValidatorClasses
from hololinked.constants import JSON
from hololinked.core.exceptions import StateMachineError
from hololinked.param.parameterized import ParameterizedFunction
from hololinked.utils import (
get_input_model_from_signature,
get_return_type_from_signature,
has_async_def,
isclassmethod,
issubklass,
)

from .dataklasses import ActionInfoValidator
from .exceptions import StateMachineError


class Action:
"""
Object that models an action.

These actions are unbound and return a bound action when accessed using the owning object.
"""

__slots__ = ["obj", "owner", "_execution_info"]

def __init__(self, obj: MethodType) -> None:
"""
Initialize an Action.

Parameters
----------
obj: MethodType
Expand Down Expand Up @@ -69,13 +73,13 @@ def __call__(self, *args, **kwargs):

@property
def name(self) -> str:
"""name of the action"""
"""Name of the action."""
return self.obj.__name__

@property
def execution_info(self) -> ActionInfoValidator:
"""
internal dataclass that holds all information about the action
Internal dataclass that holds all information about the action.

TODO: this can be refactored
"""
Expand Down Expand Up @@ -107,9 +111,7 @@ def to_affordance(self, owner_inst=None):


class BoundAction:
"""
A bound action - base class for both sync and async methods.
"""
"""A bound action, base class for both sync and async methods."""

__slots__ = [
"obj",
Expand Down Expand Up @@ -173,14 +175,14 @@ def validate_call(self, args, kwargs: dict[str, Any]) -> None:

@property
def name(self) -> str:
"""name of the action"""
"""Name of the action."""
return self.obj.__name__

def __call__(self, *args, **kwargs):
raise NotImplementedError("call must be implemented by subclass")

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."""
raise NotImplementedError("external_call must be implemented by subclass")

def __str__(self):
Expand Down Expand Up @@ -224,7 +226,7 @@ class BoundSyncAction(BoundAction):
"""

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 @@ -241,7 +243,7 @@ class BoundAsyncAction(BoundAction):
"""

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 Down Expand Up @@ -358,9 +360,9 @@ def inner(obj):
)
if input_schema:
if isinstance(input_schema, dict):
execution_info_validator.schema_validator = JSONSchemaValidator(input_schema)
execution_info_validator.schema_validator = SchemaValidatorClasses.json_schema(input_schema)
elif issubklass(input_schema, (BaseModel, RootModel)):
execution_info_validator.schema_validator = PydanticSchemaValidator(input_schema)
execution_info_validator.schema_validator = SchemaValidatorClasses.pydantic(input_schema)
else:
raise TypeError(
"input schema must be a JSON schema or a Pydantic model, got {}".format(type(input_schema))
Expand Down
10 changes: 5 additions & 5 deletions hololinked/core/dataklasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@

from pydantic import BaseModel, RootModel

from ..constants import USE_OBJECT_NAME
from ..param.parameterized import ParameterizedMetaclass
from ..param.parameters import Boolean, ClassSelector, Parameter, String, Tuple
from ..schema_validators import BaseSchemaValidator
from ..utils import issubklass
from hololinked.constants import USE_OBJECT_NAME
from hololinked.core.interfaces import BaseSchemaValidator
from hololinked.param.parameterized import ParameterizedMetaclass
from hololinked.param.parameters import Boolean, ClassSelector, Parameter, String, Tuple
from hololinked.utils import issubklass


# TODO, this class will be removed in future and merged directly into the corresponding object
Expand Down
25 changes: 9 additions & 16 deletions hololinked/core/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,31 @@
class BreakInnerLoop(Exception):
"""raise to break an inner loop"""
"""Exception classes."""


pass
class BreakInnerLoop(Exception):
"""Raise to break an inner loop."""


class BreakAllLoops(Exception):
"""raise to exit all loops"""

pass
"""Raise to exit all loops."""


class BreakLoop(Exception):
"""raise and catch to exit a loop from within another function or method"""

pass
"""Raise and catch to exit a loop from within another function or method."""


class BreakFlow(Exception):
"""raised to break the flow of the program"""

pass
"""Raise to break the flow of the program."""


# TODO - remove unused and reduce number of definitions


class StateMachineError(Exception):
"""raise to show errors while calling actions or writing properties in wrong state"""

pass
"""Raise to show errors while calling actions or writing properties in wrong state."""


class DatabaseError(Exception):
"""raise to show database related errors"""
"""Raise to show database related errors."""


__all__ = ["BreakInnerLoop", "BreakAllLoops", "BreakLoop", "BreakFlow", "StateMachineError", "DatabaseError"]
1 change: 1 addition & 0 deletions hololinked/core/interfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
"""

# TODO once all items have base classes, dont use relative imports.
from .schema_validators import BaseSchemaValidator as BaseSchemaValidator
from .serializer import BaseSerializer as BaseSerializer
32 changes: 32 additions & 0 deletions hololinked/core/interfaces/schema_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Base class for all schema validators."""

from hololinked.constants import JSONSchema


class BaseSchemaValidator:
"""
Base class for all schema validators.

Serves as a type definition.
"""

def __init__(self, schema) -> None:
self.schema = schema

def validate(self, data) -> None:
"""Validate the data against the schema."""
raise NotImplementedError("validate method must be implemented by subclass")

def validate_method_call(self, args, kwargs) -> None:
"""Validate the method call against the schema."""
raise NotImplementedError("validate_method_call method must be implemented by subclass")

def json(self) -> JSONSchema:
"""Allows JSON serialization of the validator instance itself."""
raise NotImplementedError("json method must be implemented by subclass")

def __get_state__(self):
return self.json()

def __set_state__(self, schema):
raise NotImplementedError("__set_state__ method must be implemented by subclass")
Loading
Loading