Skip to content

Commit

Permalink
Added value validation mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
akadlec committed Apr 5, 2022
1 parent a767ae2 commit 34b8087
Show file tree
Hide file tree
Showing 19 changed files with 581 additions and 45 deletions.
2 changes: 1 addition & 1 deletion fastybird_devices_module/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@
Devices module
"""

__version__ = "0.50.0"
__version__ = "0.51.0"
30 changes: 24 additions & 6 deletions fastybird_devices_module/entities/property.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
# Library dependencies
from fastnumbers import fast_float, fast_int
from fastybird_metadata.devices_module import PropertyType
from fastybird_metadata.helpers import normalize_value
from fastybird_metadata.types import ButtonPayload, DataType, SwitchPayload
from sqlalchemy import BINARY, BOOLEAN, JSON, VARCHAR, Column, Integer

Expand All @@ -36,6 +35,7 @@
InvalidArgumentException,
InvalidStateException,
)
from fastybird_devices_module.utils import normalize_value


class PropertyMixin: # pylint: disable=too-many-instance-attributes
Expand Down Expand Up @@ -277,12 +277,20 @@ def invalid(self) -> Union[str, int, float, None]:
DataType.INT,
DataType.UINT,
):
return fast_int(self.col_invalid)
try:
return fast_int(self.col_invalid, raise_on_invalid=True)

except ValueError:
return None

if self.data_type == DataType.FLOAT:
return fast_float(self.col_invalid)
try:
return fast_float(self.col_invalid, raise_on_invalid=True)

except ValueError:
return None

return self.col_invalid
return str(self.col_invalid)

# -----------------------------------------------------------------------------

Expand Down Expand Up @@ -316,7 +324,12 @@ def value(self) -> Union[int, float, str, bool, datetime, ButtonPayload, SwitchP
if self.col_value is None:
return None

return normalize_value(data_type=self.data_type, value=self.col_value, value_format=self.format)
return normalize_value(
data_type=self.data_type,
value=self.col_value,
value_format=self.format,
value_invalid=self.invalid,
)

# -----------------------------------------------------------------------------

Expand All @@ -339,7 +352,12 @@ def default(self) -> Union[int, float, str, bool, datetime, ButtonPayload, Switc
if self.col_default is None:
return None

return normalize_value(data_type=self.data_type, value=self.col_default, value_format=self.format)
return normalize_value(
data_type=self.data_type,
value=self.col_default,
value_format=self.format,
value_invalid=self.invalid,
)

# -----------------------------------------------------------------------------

Expand Down
11 changes: 10 additions & 1 deletion fastybird_devices_module/managers/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

# Library dependencies
from fastybird_exchange.publisher import Publisher
from fastybird_metadata.helpers import normalize_value
from fastybird_metadata.routing import RoutingKey
from fastybird_metadata.types import ButtonPayload, SwitchPayload
from kink import inject
Expand All @@ -40,6 +39,7 @@
IConnectorPropertyState,
IDevicePropertyState,
)
from fastybird_devices_module.utils import normalize_value


class IConnectorPropertiesStatesManager:
Expand Down Expand Up @@ -269,6 +269,7 @@ def __publish_entity(
data_type=connector_property.data_type,
value=state.actual_value,
value_format=connector_property.format,
value_invalid=connector_property.invalid,
)
if state is not None
else None
Expand All @@ -278,6 +279,7 @@ def __publish_entity(
data_type=connector_property.data_type,
value=state.expected_value,
value_format=connector_property.format,
value_invalid=connector_property.invalid,
)
if state is not None
else None
Expand All @@ -296,6 +298,7 @@ def __publish_entity(
if isinstance(expected_value, (str, int, float, bool)) or expected_value is None
else str(expected_value),
"pending": state.pending if state is not None else False,
"valid": state.valid if state is not None else False,
},
},
)
Expand Down Expand Up @@ -429,6 +432,7 @@ def __publish_entity(
data_type=device_property.data_type,
value=state.actual_value,
value_format=device_property.format,
value_invalid=device_property.invalid,
)
if state is not None
else None
Expand All @@ -438,6 +442,7 @@ def __publish_entity(
data_type=device_property.data_type,
value=state.expected_value,
value_format=device_property.format,
value_invalid=device_property.invalid,
)
if state is not None
else None
Expand All @@ -456,6 +461,7 @@ def __publish_entity(
if isinstance(expected_value, (str, int, float, bool)) or expected_value is None
else str(expected_value),
"pending": state.pending if state is not None else False,
"valid": state.valid if state is not None else False,
},
},
)
Expand Down Expand Up @@ -592,6 +598,7 @@ def __publish_entity(
data_type=channel_property.data_type,
value=state.actual_value,
value_format=channel_property.format,
value_invalid=channel_property.invalid,
)
if state is not None
else None
Expand All @@ -601,6 +608,7 @@ def __publish_entity(
data_type=channel_property.data_type,
value=state.expected_value,
value_format=channel_property.format,
value_invalid=channel_property.invalid,
)
if state is not None
else None
Expand All @@ -619,6 +627,7 @@ def __publish_entity(
if isinstance(expected_value, (str, int, float, bool)) or expected_value is None
else str(expected_value),
"pending": state.pending if state is not None else False,
"valid": state.valid if state is not None else False,
},
},
)
14 changes: 14 additions & 0 deletions fastybird_devices_module/state/property.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,20 @@ def pending(self, pending: bool) -> None:

# -----------------------------------------------------------------------------

@property
@abstractmethod
def valid(self) -> bool:
"""Property value is valid"""

# -----------------------------------------------------------------------------

@valid.setter # type: ignore[misc]
@abstractmethod
def valid(self, valid: bool) -> None:
"""Property value is valid setter"""

# -----------------------------------------------------------------------------

@abstractmethod
def to_dict(self) -> Dict:
"""Transform state to dictionary"""
Expand Down
189 changes: 189 additions & 0 deletions fastybird_devices_module/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#!/usr/bin/python3

# Copyright 2021. FastyBird s.r.o.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Devices module utilities module
"""

# Python base dependencies
from datetime import datetime
from typing import List, Optional, Tuple, Union

# Library dependencies
from fastnumbers import fast_float, fast_int
from fastybird_metadata.types import ButtonPayload, DataType, SwitchPayload


def filter_enum_format(
item: Union[str, Tuple[str, Optional[str], Optional[str]]],
value: Union[int, float, str, bool, datetime, ButtonPayload, SwitchPayload],
) -> bool:
"""Filter enum format value by value"""
if isinstance(item, tuple):
if len(item) != 3:
return False

item_as_list = list(item)

return (
str(value).lower() == item_as_list[0]
or str(value).lower() == item_as_list[1]
or str(value).lower() == item_as_list[2]
)

return str(value).lower() == item


def normalize_value( # pylint: disable=too-many-return-statements,too-many-branches
data_type: DataType,
value: Union[int, float, str, bool, datetime, ButtonPayload, SwitchPayload, None],
value_invalid: Union[str, int, float, None],
value_format: Union[
Tuple[Optional[int], Optional[int]],
Tuple[Optional[float], Optional[float]],
List[Union[str, Tuple[str, Optional[str], Optional[str]]]],
None,
] = None,
) -> Union[int, float, str, bool, datetime, ButtonPayload, SwitchPayload, None]:
"""Normalize value based on data type & value format"""
if value is None:
return value

if data_type in (
DataType.CHAR,
DataType.UCHAR,
DataType.SHORT,
DataType.USHORT,
DataType.INT,
DataType.UINT,
):
try:
int_value: int = (
value
if isinstance(value, int)
else fast_int(str(value), raise_on_invalid=True) # type: ignore[arg-type]
)

except ValueError:
return None

if value_invalid is not None and value_invalid == int_value:
return value_invalid

if value_format is not None and isinstance(value_format, tuple) and len(value_format) == 2:
min_value, max_value = value_format

if min_value is not None and isinstance(min_value, (int, float)) and min_value > int_value:
return None

if max_value is not None and isinstance(max_value, (int, float)) and max_value < int_value:
return None

return int_value

if data_type == DataType.FLOAT:
try:
float_value: float = (
value
if isinstance(value, int)
else fast_float(str(value), raise_on_invalid=True) # type: ignore[arg-type]
)

except ValueError:
return None

if value_invalid is not None and value_invalid == float_value:
return value_invalid

if value_format is not None and isinstance(value_format, tuple) and len(value_format) == 2:
min_value, max_value = value_format

if min_value is not None and isinstance(min_value, (int, float)) and min_value > float_value:
return None

if max_value is not None and isinstance(max_value, (int, float)) and max_value < float_value:
return None

return float_value

if data_type == DataType.BOOLEAN:
if isinstance(value, bool):
return value

value = str(value)

return value.lower() in ["true", "t", "yes", "y", "1", "on"]

if data_type == DataType.STRING:
return str(value)

if data_type == DataType.ENUM:
if value_format is not None and isinstance(value_format, list):
filtered = [item for item in value_format if filter_enum_format(item=item, value=value)]

return (filtered[0][0] if isinstance(filtered[0], tuple) else filtered[0]) if len(filtered) == 1 else None

return None

if data_type == DataType.DATE:
if isinstance(value, datetime):
return value

try:
return datetime.strptime(str(value), "%Y-%m-%d")

except ValueError:
return None

if data_type == DataType.TIME:
if isinstance(value, datetime):
return value

try:
return datetime.strptime(str(value), "%H:%M:%S%z")

except ValueError:
return None

if data_type == DataType.DATETIME:
if isinstance(value, datetime):
return value

try:
return datetime.strptime(str(value), r"%Y-%m-%dT%H:%M:%S%z")

except ValueError:
return None

if data_type == DataType.BUTTON:
if isinstance(value, ButtonPayload):
return value

if ButtonPayload.has_value(str(value)):
return ButtonPayload(str(value))

return None

if data_type == DataType.SWITCH:
if isinstance(value, SwitchPayload):
return value

if SwitchPayload.has_value(str(value)):
return SwitchPayload(str(value))

return None

return value
Loading

0 comments on commit 34b8087

Please sign in to comment.