ndokter / dsmr_parser

Library to parse Dutch Smart Meter Requirements (DSMR) telegrams.
MIT License
112 stars 63 forks source link

Telegram __str__() and to_json() broken #122

Open lowdef opened 1 year ago

lowdef commented 1 year ago

Telegram to_str() and to_json() broken.

Both return empty results.

lowdef commented 1 year ago

Cause:

        self._item_names = self._get_item_names()

In __init__ does not have any effect anymore, because Telegram is filled after being created. So _item_names is always [].

To do a traversal of _telegram_data_items() for every print or to_json() seems very inefficient.

We could add to the add() method:

   setattr(self, _obis_name_mapping[obis_reference], value)

All the methods str(), to_json(), __get_item_names() can then work on the attributess of the Object itself:

  for attr, value in self:
     # do something with attr and value

__get_attr__() we do not need anymore then, as all items are true first class object attributes.

Following works again, not going to merge as changes are happening in parallel now:

class Telegram(object):
    """
    Container for parsed telegram data.

    Attributes can be accessed on a telegram object by addressing by their english name, for example:
        telegram.ELECTRICITY_USED_TARIFF_1

    All attributes in a telegram can be iterated over, for example:
        [k for k,v in telegram]
    yields:
    ['P1_MESSAGE_HEADER',  'P1_MESSAGE_TIMESTAMP', 'EQUIPMENT_IDENTIFIER', ...]
    """
    def __init__(self):
        self._telegram_data = defaultdict(list)
        self._obis_name_mapping = dsmr_parser.obis_name_mapping.EN
        self._reverse_obis_name_mapping = dsmr_parser.obis_name_mapping.REVERSE_EN
        self._item_names = []

    def add(self, obis_reference, value):
        self._telegram_data[obis_reference].append(value)
        name = self._obis_name_mapping[obis_reference]
        self._item_names.append(name)
        setattr(self, name, value)

    def get(self, obis_reference, channel):
        return next(filter(lambda x: x.channel == channel, self._telegram_data[obis_reference]))

    def __getitem__(self, obis_reference):
        return self._telegram_data[obis_reference][0]

    def __len__(self):
        return len(self._telegram_data)  #TODO: its nested now

    def _get_item_names(self):
        return self._item_names

    def __iter__(self):
        for attr in self._item_names:
            value = getattr(self, attr)
            yield attr, value

    def __str__(self):
        output = ""
        for attr, value in self:
            output += "{}: \t {}\n".format(attr, str(value))
        return output

    def to_json(self):
        return json.dumps(dict([[attr, json.loads(value.to_json())] for attr, value in self]))

Or as diff:

diff --git a/dsmr_parser/objects.py b/dsmr_parser/objects.py
index 15ed27e..b500f25 100644
--- a/dsmr_parser/objects.py
+++ b/dsmr_parser/objects.py
@@ -22,21 +22,17 @@ class Telegram(object):
         self._telegram_data = defaultdict(list)
         self._obis_name_mapping = dsmr_parser.obis_name_mapping.EN
         self._reverse_obis_name_mapping = dsmr_parser.obis_name_mapping.REVERSE_EN
-        self._item_names = self._get_item_names()
+        self._item_names = []

     def add(self, obis_reference, value):
         self._telegram_data[obis_reference].append(value)
+        name = self._obis_name_mapping[obis_reference]
+        self._item_names.append(name)
+        setattr(self, name, value)

     def get(self, obis_reference, channel):
         return next(filter(lambda x: x.channel == channel, self._telegram_data[obis_reference]))

-    def __getattr__(self, name):
-        """ will only get called for undefined attributes """
-        obis_reference = self._reverse_obis_name_mapping[name]
-        value = self._telegram_data[obis_reference][0]
-        setattr(self, name, value)
-        return value
-
     def __getitem__(self, obis_reference):
         return self._telegram_data[obis_reference][0]

@@ -44,7 +40,7 @@ class Telegram(object):
         return len(self._telegram_data)  #TODO: its nested now

     def _get_item_names(self):
-        return [self._obis_name_mapping[k] for k, v in self._telegram_data.items()]
+        return self._item_names

     def __iter__(self):
         for attr in self._item_names: