'How do i make a derived class attribute or property where the assigned value is a class instance with it's own attribute setting methods

I have a class that essentially generates a nested dictionary and I currently use an external class instance as one of the attributes. Both classes contain a custom "to_dict()" method to output the attributes as one nested dictionary. Due to a change I now need to be able to make the assignment of attribute with the class instance, a calculated attribute / property depending on the value of another attribute. I can do this with the @property decorator but I'm unable to make the existing methods on the assigned class instance still functional because the @property decorator seems to make it immutable. I can't quite work out how to use the setter to make this work as I require.

Here's my code so far:

class UpdateRqstCls:  

    def __init__(self, coord_sys='IJKMinMax'):
        self.new = []
        self.delete = []
        self.update = []
        self.rename = []
        self.coord_sys = coord_sys
        self.base_commit_uuid = ''

    @property
    def bbox(self):
        if self.coord_sys == 'IJKMinMax':
            return IJKMinMax()
        elif self.coord_sys == 'XYZMinMax':
            return XYZMinMax()
        else:
            raise Exception(f"'{self.coord_sys}' is not a recognised coordinate system for the UpdateRqstCls class")
            
    @bbox.setter
    def bbox(self, coord_sys):
        if self.coord_sys == 'IJKMinMax':
            return IJKMinMax()
        elif self.coord_sys == 'XYZMinMax':
            return XYZMinMax()
        else:
            raise Exception(f"'{self.coord_sys}' is not a recognised coordinate system for the UpdateRqstCls class")
    
    def to_dict(self):
        return {
            'new': self.new,
            'delete': self.delete,
            'update': self.update,
            'rename': self.rename,
            'bbox': self.bbox.to_dict(),
            'base_commit_uuid': self.base_commit_uuid
        }
        

class IJKMinMax:
    '''Default is 0-3'''

    def __init__(self, imin=0, imax=3, jmin=0, jmax=3, kmin=0, kmax=3):
        self.i_minmax = {"min": imin, "max": imax}
        self.j_minmax = {"min": jmin, "max": jmax}
        self.k_minmax = {"min": kmin, "max": kmax}

    def to_dict(self):
        return self.__dict__

    def set_all(self, imin=0, imax=3, jmin=0, jmax=3, kmin=0, kmax=3):
        self.i_minmax = {"min": imin, "max": imax}
        self.j_minmax = {"min": jmin, "max": jmax}
        self.k_minmax = {"min": kmin, "max": kmax}

class XYZMinMax:
    '''Default is 0-100'''

    def __init__(self, xmin=0, xmax=100, ymin=0, ymax=100, zmin=0, zmax=100):
        self.x_minmax = {"min": xmin, "max": xmax}
        self.y_minmax = {"min": ymin, "max": ymax}
        self.z_minmax = {"min": zmin, "max": zmax}

    def to_dict(self):
        return self.__dict__

    def set_all(self, xmin=0, xmax=100, ymin=0, ymax=100, zmin=0, zmax=100):
        self.x_minmax = {"min": xmin, "max": xmax}
        self.y_minmax = {"min": ymin, "max": ymax}
        self.z_minmax = {"min": zmin, "max": zmax}

I want to be able to use it a few different ways: e.g.

myobj = UpdateRqstCls() # Uses the coord_sys default of 'IJKMinMax'
myobj.bbox.set_all(imin=0, imax=5, jmin=2, jmax=3, kmin=1, kmax=3)
myobj.bbox.to_dict()
>>> {'i_minmax': {'min': 0, 'max': 3}, 'j_minmax': {'min': 0, 'max': 3}, 'k_minmax': {'min': 0, 'max': 3}}

^ The set_all method has not updated values and they are still the default 0,3,0,3,0,3

myobj.to_dict()
>>>{'new': [], 'delete': [], 'update': [], 'rename': [], 'bbox': {'i_minmax': {'min': 0, 'max': 3}, 'j_minmax': {'min': 0, 'max': 3}, 'k_minmax': {'min': 0, 'max': 3}}, 'base_commit_uuid': ''}

^ The to_dict() here is working as expected apart from the bbox values

myobj2 = UpdateRqstCls(coord_sys='XYZMinMax') # assigne the coord_sys 
myobj2.bbox.set_all(xmin=0, xmax=50, ymin=10, ymax=100, zmin=0, zmax=40)
myobj2.to_dict()
>>>{'new': [], 'delete': [], 'update': [], 'rename': [], 'bbox': {'x_minmax': {'min': 0, 'max': 100}, 'y_minmax': {'min': 0, 'max': 100}, 'z_minmax': {'min': 0, 'max': 100}}, 'base_commit_uuid': ''}

^ The set_all method has not updated values and they are still the default 0,100,0,100,0,100

myobj3 = UpdateRqstCls(coord_sys='XYZMinMax')
myobj3.coord_sys = 'IJKMinMax' # change coord_sys and expect bbox methods to update accordingly
myobj3.bbox.to_dict()
{'i_minmax': {'min': 0, 'max': 3}, 'j_minmax': {'min': 0, 'max': 3}, 'k_minmax': {'min': 0, 'max': 3}}
myobj3.bbox.set_all(imin=0, imax=5, jmin=2, jmax=3, kmin=1, kmax=3)
myobj3.bbox.to_dict()
>>>{'i_minmax': {'min': 0, 'max': 3}, 'j_minmax': {'min': 0, 'max': 3}, 'k_minmax': {'min': 0, 'max': 3}}

^ While the bbox defaults updated as required it still doesn't let me use the set_all as I want.

In a previous version of this class I only had the one coordinate system and didn't need it to dynamically update, so it use to work when assigned to an attribute.

any suggestions on how to make this work would be greatly appreciated. I do have one solution that simply uses class inheritance during instantiation, but it doesn't let me change to the other coordinate once I've initiated it unless I do direct attribute assignment.

I've googled around and looked at other examples but couldn't find a setter solution that covers this scenario.

Update: So I tried the following to the UpdateRqstCls (and it's probably all kinds of bad python). It seems to work unless I change the coord_sys at which point the bbox attribute does not update to the new object instance unless I execute .bbox.to_dict() It should be noted that I'm not really experienced Object Orientated programming and I avoid it like the plague unless I really have to use it.

class UpdateRqstCls:

    def __init__(self, coord_sys='IJKMinMax'):
        self.new = []
        self.delete = []
        self.update = []
        self.rename = []
        self.coord_sys = coord_sys
        self.base_commit_uuid = ''
        if coord_sys == 'IJKMinMax':
            self.bbox = IJKMinMax()
        elif coord_sys == 'XYZMinMax':
            self.bbox = XYZMinMax()
        else:
            raise Exception(f"'{self.coord_sys}' is not a recognised coordinate system for the UpdateRqstCls class")

    @property
    def __coord_sys(self):
        if self.coord_sys == 'IJKMinMax':
            self.__setattr__('bbox', IJKMinMax())
        elif self.coord_sys == 'XYZMinMax':
             self.__setattr__('bbox', XYZMinMax())
        else:
            raise Exception(f"'{self.coord_sys}' is not a recognised coordinate system for the UpdateRqstCls class")
        return self.__coord_sys

   def to_dict(self):
        return {
            'new': self.new,
            'delete': self.delete,
            'update': self.update,
            'rename': self.rename,
            'bbox': self.bbox.to_dict(),
            'base_commit_uuid': self.base_commit_uuid
        }


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source