Skip to content

Commit

Permalink
fix for #11 and updates for serialization to and from dict
Browse files Browse the repository at this point in the history
  • Loading branch information
eavanvalkenburg committed Apr 12, 2021
1 parent 9263a18 commit 2119100
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 12 deletions.
8 changes: 6 additions & 2 deletions src/pysiaalarm/account.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Class for SIA Accounts."""
import logging
from dataclasses import dataclass
from dataclasses import dataclass, field
from dataclasses_json import dataclass_json, config, Exclude
from typing import Optional, Tuple

from .errors import (
Expand All @@ -13,14 +14,17 @@
_LOGGER = logging.getLogger(__name__)


@dataclass_json
@dataclass
class SIAAccount:
"""Class for SIA Accounts."""

account_id: str
key: Optional[str] = None
allowed_timeband: Tuple[int, int] = (40, 20)
key_b: Optional[bytes] = None
key_b: Optional[bytes] = field(
repr=False, default=None, metadata=config(exclude=Exclude.ALWAYS) # type: ignore
)

def __post_init__(self) -> None:
"""Rewrite the key as bytes."""
Expand Down
3 changes: 3 additions & 0 deletions src/pysiaalarm/data/data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Data related utils for pysiaalarm."""
import json
from dataclasses import dataclass
from dataclasses_json import dataclass_json
from typing import Dict, Optional

import pkg_resources
Expand All @@ -10,6 +11,7 @@
FILE_ADM_MAPPING = "adm_mapping.json"


@dataclass_json
@dataclass
class SIACode:
"""Class for SIACodes."""
Expand All @@ -20,6 +22,7 @@ class SIACode:
concerns: str


@dataclass_json
@dataclass
class SIAXData:
"""Class for Xdata."""
Expand Down
22 changes: 17 additions & 5 deletions src/pysiaalarm/event.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""This is a class for SIA Events."""
from __future__ import annotations

import copy
import logging
from abc import ABC, abstractmethod, abstractproperty
from dataclasses import dataclass
from dataclasses_json import dataclass_json
from dataclasses import dataclass, field
from dataclasses_json import dataclass_json, config, Exclude
from datetime import datetime, timedelta
from typing import Dict, Optional, Union

Expand All @@ -30,7 +30,7 @@
_LOGGER = logging.getLogger(__name__)


@dataclass_json
@dataclass_json()
@dataclass # type: ignore
class BaseEvent(ABC):
"""Base class for Events."""
Expand Down Expand Up @@ -67,7 +67,9 @@ class BaseEvent(ABC):
# Parsed fields
calc_crc: Optional[str] = None
extended_data: Optional[SIAXData] = None
sia_account: Optional[SIAAccount] = None
sia_account: Optional[SIAAccount] = field(
metadata=config(exclude=Exclude.ALWAYS), default=None # type: ignore
)
sia_code: Optional[SIACode] = None

@property
Expand Down Expand Up @@ -188,6 +190,7 @@ def _crc_calc(msg: str) -> str:
return ("%x" % crc).upper().zfill(4)


@dataclass_json
@dataclass
class SIAEvent(BaseEvent):
"""Class for SIAEvents."""
Expand Down Expand Up @@ -418,7 +421,15 @@ def __str__(self) -> str:
Encrypted Content: {self.encrypted_content}, \
Full Message: {self.full_message}."

# def to_dict(self, clear_account=False, **kwargs) -> dict:
# """Return the event as a dict."""
# ev: BaseEvent = copy.deepcopy(self)
# if clear_account and ev.sia_account:
# ev.sia_account = None
# return super().to_dict(ev, **kwargs) # type: ignore # pylint: disable=no-member


@dataclass_json
@dataclass
class OHEvent(SIAEvent):
"""Class for OH events."""
Expand All @@ -444,6 +455,7 @@ def create_response(self) -> bytes:
return '"ACK"'.encode("ascii") # pragma: no cover


@dataclass_json
@dataclass
class NAKEvent(BaseEvent):
"""Class for NAK Events."""
Expand Down
6 changes: 3 additions & 3 deletions src/pysiaalarm/utils/regexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
(?:id)?(?:(?<=id)(?P<id>\d*))?\/?
(?:ri)?(?:(?<=ri)(?P<ri>\d*))?\/?
(?P<code>[a-zA-Z]{2})?
(?P<message>\w*)
(?P<message>[\w\s\^]*)
[\]]
(?:\[(?:(?<=\[)(?P<xdata>\w*)(?=\]))\])?
(?:\[(?:(?<=\[)(?P<xdata>[\w\s\^]*)(?=\]))\])?
[_]?
(?P<timestamp>[0-9:,-]*)?$
"""
Expand All @@ -57,7 +57,7 @@
\s
(?P<ri>\d{3})
[\]]
(?:\[(?:(?<=\[)(?P<xdata>\w*)(?=\]))\])?
(?:\[(?:(?<=\[)(?P<xdata>[\w\s\^]*)(?=\]))\])?
[_]?
(?P<timestamp>[0-9:,-]*)?$
"""
Expand Down
27 changes: 25 additions & 2 deletions tests/test_sia_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,29 @@ class testSIA(object):

pytestmark = pytest.mark.asyncio

@parametrize_with_cases("cl, dic, clear_account", cases=ToFromDict)
def test_to_from_dict(self, cl, dic, clear_account):
"""Test the to and from dict methods."""
_LOGGER.warning("Dict before: %s", dic)
instance = cl.from_dict(dic)
_LOGGER.warning("Class instance %s", instance)
if cl == SIAAccount:
assert instance.account_id == dic["account_id"]
assert instance.key == dic["key"]
dic2 = instance.to_dict()
else:
assert instance.account == dic["account"]
assert instance.code == dic["code"]
dic2 = instance.to_dict(encode_json=True)

_LOGGER.warning("Dict after: %s", dic2)
for key, value in dic.items():
if key != "sia_account":
assert dic2[key] == value
else:
if dic[key]:
assert instance.sia_account is not None

@parametrize_with_cases(
"line, account_id, type, code, error_type, extended_data_flag",
cases=EventParsing,
Expand All @@ -140,8 +163,8 @@ def test_event_parsing(
"""Test event parsing methods."""
try:
event = SIAEvent.from_line(line)
_LOGGER.warning(asdict(event))
_LOGGER.warning(event.to_dict())
# _LOGGER.warning(asdict(event))
# _LOGGER.warning(event.to_dict())
# assert False
assert event.code == code
if code:
Expand Down
133 changes: 133 additions & 0 deletions tests/test_sia_package_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
InvalidKeyFormatError,
InvalidKeyLengthError,
CommunicationsProtocol,
SIAAccount,
SIAEvent,
)
from pysiaalarm.errors import EventFormatError
from pysiaalarm.utils import ResponseType
Expand Down Expand Up @@ -112,6 +114,17 @@ class EventParsing:
"""

def case_bug(self):
"""Test case from #11."""
return (
r'9618004D"SIA-DCS"7960L0#123456[#123456|Nri1CL0^VX FRONTE ^]_22:40:48,04-11-2021',
"123456",
"Closing Report",
"CL",
None,
False
)

def case_dc04(self):
"""Test case DC04 format - NOT SUPPORTED so throws an error."""
return (
Expand Down Expand Up @@ -326,3 +339,123 @@ def case_wrong_event(self):
def case_non_existent_code(self):
"""Test parsing for non existing code."""
return ("ZX", False, False, ResponseType.DUH)


class ToFromDict:
"""Test the event serialization to and from dict.
Emits the class and a dict of that class.
"""

def case_sia_event(self):
"""Test event from dict."""
d_ev = {
"full_message": '"SIA-DCS"5268L0#AAA[Nri1/WA000]_08:40:47,07-08-2020',
"msg_crc": "C416",
"length": "0279",
"encrypted": False,
"message_type": "SIA-DCS",
"receiver": None,
"line": "L0",
"account": "AAA",
"sequence": "5268",
"content": "Nri1/WA000]_08:40:47,07-08-2020",
"encrypted_content": None,
"ti": None,
"id": None,
"ri": "1",
"code": "WA",
"message": "000",
"x_data": None,
"timestamp": 1594197647.0,
"event_qualifier": None,
"event_type": None,
"partition": None,
"calc_crc": "E9A4",
"extended_data": None,
"sia_account": None,
"sia_code": {
"code": "WA",
"type": "Water Alarm",
"description": "Water detected at protected premises",
"concerns": "Zone or point",
},
}
return SIAEvent, d_ev, False

def case_sia_event_with_account(self):
"""Test event from dict."""
d_ev = {
"full_message": '"SIA-DCS"5268L0#AAA[Nri1/WA000]_08:40:47,07-08-2020',
"msg_crc": "C416",
"length": "0279",
"encrypted": False,
"message_type": "SIA-DCS",
"receiver": None,
"line": "L0",
"account": "AAA",
"sequence": "5268",
"content": "Nri1/WA000]_08:40:47,07-08-2020",
"encrypted_content": None,
"ti": None,
"id": None,
"ri": "1",
"code": "WA",
"message": "000",
"x_data": None,
"timestamp": 1594197647.0,
"event_qualifier": None,
"event_type": None,
"partition": None,
"calc_crc": "E9A4",
"extended_data": None,
"sia_account": {"account_id": ACCOUNT, "key": KEY},
"sia_code": {
"code": "WA",
"type": "Water Alarm",
"description": "Water detected at protected premises",
"concerns": "Zone or point",
},
}
return SIAEvent, d_ev, False

def case_sia_event_with_account_clear(self):
"""Test event from dict."""
d_ev = {
"full_message": '"SIA-DCS"5268L0#AAA[Nri1/WA000]_08:40:47,07-08-2020',
"msg_crc": "C416",
"length": "0279",
"encrypted": False,
"message_type": "SIA-DCS",
"receiver": None,
"line": "L0",
"account": "AAA",
"sequence": "5268",
"content": "Nri1/WA000]_08:40:47,07-08-2020",
"encrypted_content": None,
"ti": None,
"id": None,
"ri": "1",
"code": "WA",
"message": "000",
"x_data": None,
"timestamp": 1594197647.0,
"event_qualifier": None,
"event_type": None,
"partition": None,
"calc_crc": "E9A4",
"extended_data": None,
"sia_account": {"account_id": ACCOUNT, "key": KEY},
"sia_code": {
"code": "WA",
"type": "Water Alarm",
"description": "Water detected at protected premises",
"concerns": "Zone or point",
},
}
return SIAEvent, d_ev, True

def case_sia_account(self):
"""Test account to and from dict."""
return SIAAccount, {"account_id": ACCOUNT, "key": KEY}, False

0 comments on commit 2119100

Please sign in to comment.