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
1,091 changes: 1,091 additions & 0 deletions .bandit-baseline.json

Large diffs are not rendered by default.

32 changes: 31 additions & 1 deletion .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,39 @@ jobs:
- name: run ruff linter
run: ruff check hololinked

scan:
name: security scan with bandit
runs-on: ubuntu-latest
needs: codestyle

steps:
- name: checkout code
uses: actions/checkout@v4

- name: set up python 3.11
uses: actions/setup-python@v3
with:
python-version: 3.11

- name: install bandit
run: pip install bandit

- name: run bandit security scan
run: |
bandit -c pyproject.toml -r hololinked/ -b .bandit-baseline.json
echo "----------------------------"
echo "Rerunning to generate bandit report in JSON format..."
bandit -c pyproject.toml -r hololinked/ -f json -b .bandit-baseline.json -o bandit-report.json

- name: upload bandit report artifact
uses: actions/upload-artifact@v4
with:
name: bandit-security-scan-report
path: bandit-report.json

test:
name: unit-integration tests
needs: codestyle
needs: scan

strategy:
matrix:
Expand Down
34 changes: 17 additions & 17 deletions hololinked/client/factory.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
import base64
import ssl
import threading
import uuid
import base64
import warnings
from typing import Any

import aiomqtt
import httpx
import ssl
import structlog
from typing import Any
from paho.mqtt.client import Client as PahoMQTTClient, MQTTProtocolVersion, CallbackAPIVersion, MQTTMessage

from paho.mqtt.client import CallbackAPIVersion, MQTTMessage, MQTTProtocolVersion
from paho.mqtt.client import Client as PahoMQTTClient

from ..core import Thing, Action
from ..core.zmq import SyncZMQClient, AsyncZMQClient
from ..constants import ZMQ_TRANSPORTS
from ..core import Thing
from ..core.zmq import AsyncZMQClient, SyncZMQClient
from ..serializers import Serializers
from ..td.interaction_affordance import (
PropertyAffordance,
ActionAffordance,
EventAffordance,
PropertyAffordance,
)
from ..serializers import Serializers
from ..utils import set_global_event_loop_policy
from ..constants import ZMQ_TRANSPORTS
from .abstractions import ConsumedThingAction, ConsumedThingProperty, ConsumedThingEvent
from .abstractions import ConsumedThingAction, ConsumedThingEvent, ConsumedThingProperty
from .http.consumed_interactions import HTTPAction, HTTPEvent, HTTPProperty
from .mqtt.consumed_interactions import MQTTConsumer # only one type for now
from .proxy import ObjectProxy
from .http.consumed_interactions import HTTPProperty, HTTPAction, HTTPEvent
from .zmq.consumed_interactions import (
ReadMultipleProperties,
WriteMultipleProperties,
ZMQAction,
ZMQEvent,
ZMQProperty,
WriteMultipleProperties,
ReadMultipleProperties,
)
from .mqtt.consumed_interactions import MQTTConsumer # only one type for now


set_global_event_loop_policy()

Expand Down Expand Up @@ -103,7 +103,7 @@ def zmq(
async_zmq_client = AsyncZMQClient(f"{id}|async", server_id=server_id, logger=logger, access_point=access_point)

# Fetch the TD
assert isinstance(Thing.get_thing_model, Action)
Thing.get_thing_model # type: Action
FetchTDAffordance = Thing.get_thing_model.to_affordance()
FetchTDAffordance.override_defaults(name="get_thing_description", thing_id=thing_id)
FetchTD = ZMQAction(
Expand Down
3 changes: 1 addition & 2 deletions hololinked/core/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ class IPAddress(Property):

def __init__(
self,
default: typing.Optional[str] = "0.0.0.0",
default: typing.Optional[str] = "127.0.0.1",
*,
allow_ipv4: bool = True,
allow_ipv6: bool = True,
Expand Down Expand Up @@ -512,7 +512,6 @@ def _crop_to_bounds(self, value: typing.Union[int, float]) -> typing.Union[int,
"""
# Values outside the bounds are silently cropped to
# be inside the bounds.
assert self.bounds is not None, "Cannot crop to bounds when bounds is None"
vmin, vmax = self.bounds
incmin, incmax = self.inclusive_bounds
if vmin is not None:
Expand Down
11 changes: 3 additions & 8 deletions hololinked/core/property.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import typing
from enum import Enum

from ..param.parameterized import Parameter, Parameterized, ParameterizedMetaclass
from ..utils import issubklass
from ..exceptions import StateMachineError
from ..param.parameterized import Parameter, Parameterized, ParameterizedMetaclass
from ..schema_validators import JSONSchemaValidator
from ..utils import issubklass
from .dataklasses import RemoteResourceInfoValidator
from .events import Event, EventDispatcher # noqa: F401

Expand Down Expand Up @@ -264,11 +264,6 @@ def external_set(self, obj: Parameterized, value: typing.Any) -> None:

def _post_value_set(self, obj, value: typing.Any) -> None:
if (self.db_persist or self.db_commit) and hasattr(obj, "db_engine"):
from .thing import Thing

assert isinstance(obj, Thing), (
f"database property {self.name} bound to a non Thing, currently not supported"
)
obj.db_engine.set_property(self, value)
self.push_change_event(obj, value)
return super()._post_value_set(obj, value)
Expand Down Expand Up @@ -330,7 +325,7 @@ def to_affordance(self, owner_inst=None):


try:
from pydantic import BaseModel, RootModel, create_model, ConfigDict
from pydantic import BaseModel, ConfigDict, RootModel, create_model

def wrap_plain_types_in_rootmodel(model: type) -> type[BaseModel] | type[RootModel]:
"""
Expand Down
11 changes: 5 additions & 6 deletions hololinked/core/state_machine.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import typing
from enum import Enum, EnumMeta, StrEnum
from types import FunctionType, MethodType
from enum import EnumMeta, Enum, StrEnum

from ..param import edit_constant
from ..exceptions import StateMachineError
from ..param import edit_constant
from .actions import Action
from .meta import ThingMeta
from .properties import Boolean, ClassSelector, TypedDict
from .property import Property
from .properties import ClassSelector, TypedDict, Boolean
from .thing import Thing
from .meta import ThingMeta
from .actions import Action


class StateMachine:
Expand Down Expand Up @@ -364,7 +364,6 @@ def machine(self):

def prepare_object_FSM(instance: Thing) -> None:
"""validate and prepare the state machine attached to a Thing class"""
assert isinstance(instance, Thing), "state machine can only be attached to a Thing class."
cls = instance.__class__
if cls.state_machine and isinstance(cls.state_machine, StateMachine):
cls.state_machine.validate(instance)
Expand Down
58 changes: 25 additions & 33 deletions hololinked/core/zmq/brokers.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import asyncio
import os
import threading
import time
import typing
import warnings
from enum import Enum

import structlog
import zmq
import zmq.asyncio
import asyncio
import threading
import typing
import structlog
from enum import Enum
from zmq.utils.monitor import parse_monitor_message

from ...utils import format_exception_as_json, run_callable_somehow, uuid_hex, get_current_async_loop
from ...config import global_config
from ...constants import ZMQ_EVENT_MAP, ZMQ_TRANSPORTS, get_socket_type_name
from ...serializers.serializers import Serializers
from ...exceptions import BreakLoop
from ...serializers.serializers import Serializers
from ...utils import format_exception_as_json, get_current_async_loop, run_callable_somehow, uuid_hex
from .message import (
ERROR,
EXIT,
Expand All @@ -24,14 +25,14 @@
SERVER_DISCONNECTED,
TIMEOUT,
EventMessage,
PreserializedData,
PreserializedEmptyByte,
RequestMessage,
ResponseMessage,
SerializableData,
PreserializedData,
SerializableNone,
ServerExecutionContext,
ThingExecutionContext,
SerializableNone,
PreserializedEmptyByte,
default_server_execution_context,
default_thing_execution_context,
)
Expand Down Expand Up @@ -119,7 +120,8 @@ def get_socket(
RuntimeError
if transport is `TCP` and a socket connection from client side is requested but a socket address is not supplied
"""
assert node_type.lower() in ["server", "client"], f"Invalid node_type: {node_type}"
if node_type.lower() not in ["server", "client"]:
raise ValueError(f"Invalid node_type: {node_type}")
bind = node_type.lower() == "server"
if len(access_point) == 3 or len(access_point) == 6 or isinstance(access_point, Enum):
transport = access_point
Expand Down Expand Up @@ -929,20 +931,16 @@ def exit(self) -> None:
BaseZMQ.exit(self)
self.poller.unregister(self.socket)
# TODO - there is some issue here while quitting
# print("poller exception did not occur 1")
if self._monitor_socket is not None:
# print("poller exception did not occur 2")
self.poller.unregister(self._monitor_socket)
# print("poller exception did not occur 3")
except Exception as ex: # noqa
# TODO log message and undo noqa
# raises a weird key error for some reason
# unable to deregister from poller - <zmq.asyncio.Socket(zmq.PAIR) at 0x1c9e5028830> - KeyError
# unable to deregister from poller - <zmq.asyncio.Socket(zmq.PAIR) at 0x1c9e502a350> - KeyError
# unable to deregister from poller - <zmq.asyncio.Socket(zmq.PAIR) at 0x1c9e5080750> - KeyError
# unable to deregister from poller - <zmq.asyncio.Socket(zmq.PAIR) at 0x1c9e5082430> - KeyError
# self.logger.warning(f"unable to deregister from poller - {str(ex)} - {type(ex).__name__}")
pass
self.logger.warning(f"unable to deregister socket from poller - {str(ex)} - {type(ex).__name__}")
try:
if self._monitor_socket is not None:
self._monitor_socket.close(0)
Expand Down Expand Up @@ -1151,7 +1149,7 @@ def recv_response(self, message_id: bytes) -> ResponseMessage:
# put the expected message in response message cache
# 2. also release the lock in every iteration because a message may be added in response cache
# and may not return the method, which means the loop will run again and the lock needs to reacquired
pass
self.logger.warning(f"could not release poller lock for recv_response - {str(ex)}")

def execute(
self,
Expand Down Expand Up @@ -1462,8 +1460,8 @@ async def async_recv_response(self, message_id: str) -> typing.List[ResponseMess
finally:
try:
self._poller_lock.release()
except Exception:
pass
except Exception as ex:
self.logger.warning(f"could not release poller lock for async_recv_response - {str(ex)}")

async def async_execute(
self,
Expand Down Expand Up @@ -2145,9 +2143,6 @@ def register(self, event: "EventDispatcher") -> None:
`Event` object that needs to be registered. Events created at `__init__()` of `Thing` are
automatically registered.
"""
from ...core.events import EventDispatcher

assert isinstance(event, EventDispatcher), "event must be an instance of EventDispatcher"
if event._unique_identifier in self.events and event not in self.events:
raise AttributeError(f"event {event._unique_identifier} already registered, please use another name.")
self.event_ids.add(event._unique_identifier)
Expand Down Expand Up @@ -2215,8 +2210,8 @@ def publish(self, event, data: typing.Any) -> None:
finally:
try:
self._send_lock.release()
except Exception:
pass
except Exception as ex:
self.logger.warning(f"could not release publish lock for event publisher - {str(ex)}")

def exit(self):
try:
Expand Down Expand Up @@ -2282,7 +2277,7 @@ def __init__(
id=id,
event_id=event_unique_identifier,
)
self.logger = logger
self.logger = logger # type: structlog.stdlib.BoundLogger
self.create_socket(
server_id=id,
socket_id=id,
Expand Down Expand Up @@ -2336,10 +2331,7 @@ def exit(self):
self.poller.unregister(self.interruptor)
except Exception as ex: # noqa
# TODO - log message and undo noqa
# self.logger.warning("could not properly terminate socket or attempted to terminate an already terminated socket of event consuming socket at address '{}'. Exception message: {}".format(
# self.socket_address, str(E)))
# above line prints too many warnings
pass
self.logger.warning(f"could not unregister sockets from poller for event consumer - {str(ex)}")
try:
self.socket.close(0)
self.interruptor.close(0)
Expand Down Expand Up @@ -2395,8 +2387,8 @@ def receive(
finally:
try:
self._poller_lock.release()
except Exception:
pass
except Exception as ex:
self.logger.warning(f"could not release poller lock for event receive - {str(ex)}")

def interrupt(self):
"""
Expand Down Expand Up @@ -2459,8 +2451,8 @@ async def receive(
finally:
try:
self._poller_lock.release()
except Exception:
pass
except Exception as ex:
self.logger.warning(f"could not release poller lock for event receive - {str(ex)}")

async def interrupt(self):
"""
Expand Down
Loading