K20GH
K20GH

Reputation: 6263

Prevent Class from updating attributes if None

I have a Device class like so, that I pass data from a websocket to. The data from the websocket isn't always the same, so what I want to do is only update the value of each item in the class, if it is not None.

As an example, the message_data might be the below on the first message

{
  "ssdp": "foo",
  "ip": "bar"
}

and then the following on the second:

{
  "ssdp": "foo",
}

My code is below:

Script

                if self._device is None:
                    self._device = Device(message_data)

                device = self._device.update_from_dict(message_data)

Class

@dataclass
class Info:
    ssdp: str
    ip: str

    @staticmethod
    def from_dict(data):
        return Info(
            ssdp=data.get('SSDP'),
            ip=data.get('ip'),
        )


class Device:
    def __init__(self, data):
        self.info = None
        self.update_from_dict(data)

    def update_from_dict(self, data):
        self.info = Info.from_dict(data)
        return self

Unfortunately at the moment, this is returning

{'info': Info(ssdp='foo', ip='bar')}

and then

{'info': Info(ssdp='foo', ip=None)}

Upvotes: 0

Views: 57

Answers (4)

quamrana
quamrana

Reputation: 39354

You never show exactly how you use your class Device, so I'm going to have to guess:

You create a new Device from data, and later you get new data and want to do an update.

This constitutes two completely different operations, and Device already has these two operations, but Info does not.

Here is my code with two methods for Info:

from dataclasses import dataclass

@dataclass
class Info:
    ssdp: str
    ip: str

    @staticmethod
    def from_dict(data):
        return Info(
            ssdp=data.get('ssdp'),
            ip=data.get('ip'),
        )
    def update_from_dict(self, data):
        self.ssdp = data.get('ssdp', self.ssdp)
        self.ip = data.get('ip', self.ip)


class Device:
    def __init__(self, data):
        self.info = Info.from_dict(data)

    def update_from_dict(self, data):
        self.info.update_from_dict(data)
        return self
    
    def __repr__(self):
        return str(self.info)
    

d1 = {
  "ssdp": "foo",
  "ip": "bar"
}

d2 = {
  "ssdp": "test",
}

dev = Device(d1)
print(dev)
dev.update_from_dict(d2)
print(dev)

Output:

Info(ssdp='foo', ip='bar')
Info(ssdp='test', ip='bar')

Upvotes: 1

Riccardo Bucco
Riccardo Bucco

Reputation: 15364

Try with these redefined new classes:

from dataclasses import dataclass, fields, replace
from operator import attrgetter

@dataclass
class Info:
    ssdp: str = None
    ip: str = None


class Device:
    def __init__(self, data):
        self.info = Info()
        self.update_from_dict(data)

    def update_from_dict(self, data):
        keys = set(map(attrgetter('name'), fields(self.info)))
        data = {k: v for k, v in data.items() if k in keys}
        self.info = replace(self.info, **data)
        return self

This solution is using the replace function, which basically creates a new object of the same type as the input object, replacing fields with values from changes.

Upvotes: 0

mrCopiCat
mrCopiCat

Reputation: 919

You can try with this readapted version of your code:

from dataclasses import dataclass 

@dataclass
class Info:
    ssdp: str = None
    ip: str = None

    def from_dict(self, data):
        self.ssdp = data['ssdp'] if 'ssdp' in data.keys() else self.ssdp[0],
        self.ip = data['ip'] if 'ip'  in data.keys()  else self.ip[0],
        return self 



class Device:
    def __init__(self, data):
        self.info = Info()
        self.update_from_dict(data)

    def update_from_dict(self, data):
        self.info.from_dict(data)
        return self

For test purposes :

if __name__ == "__main__":

    data = {
    "ssdp": "foo",
    "ip": "bar"
    }

    msg_data = {
    "ssdp": "test",
    }

    device = Device(data)
    print("before: ", device.info)

    device = device.update_from_dict(msg_data)
    print("after: ", device.info)

output :

before:  Info(ssdp=('foo',), ip=('bar',))
after:  Info(ssdp=('test',), ip=('bar',))

Upvotes: 0

olijeffers0n
olijeffers0n

Reputation: 93

@dataclass
class Info:
    ssdp: str
    ip: str

    @staticmethod
    def update(data, info):
        return Info(
            ssdp=data.get('SSDP', info.ssdp),
            ip=data.get('ip', info.ip),
        )


class Device:
    def __init__(self, data):
        self.info = Info(None, None)
        self.update_from_dict(data)

    def update_from_dict(self, data):
        self.info = Info.update(data, self.info)

This is how I would go about it. I make use of the default value to the dict.get() and then consequently pass the current info through to the update method. It is annoying that you have to initialise the info as Info(None, None), but someone else might have a better approach :)

Upvotes: 0

Related Questions