Skip to content

Commit f780c47

Browse files
zrgtFrosty2500
andauthored
Use dir() instead of vars() in Referable.update_from() (#338)
Previously, we used `vars()` to iterate over all attributes of an object when updating it from another object (`Referable.update_from()`). However, `vars()` only retrieves the instance's `__dict__`, e.g. the object's attributes, but does not include properties. As a result, when you use `vars(other)`, you get the `_id_short` attribute instead of the `id_short` property. This made it impossible to utilize custom setter methods we developed for certain cases, e.g. when properties should be immutable. This now adapts the `Referable.update_from()` method to utilize `dir()` instead, which iterates over all attributes and properties of the object. We skip callables and private attributes. We use `setattr()` to set attributes and properties, so that the correct setter methods will be used. Furthermore, we now raise an error if an immutable property changed between the two versions of the object. Fixes #215 --------- Co-authored-by: Sercan Sahin <[email protected]>
1 parent 1d38288 commit f780c47

File tree

1 file changed

+23
-7
lines changed

1 file changed

+23
-7
lines changed

sdk/basyx/aas/model/base.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ def find_source(self) -> Tuple[Optional["Referable"], Optional[List[str]]]: # t
802802

803803
def update_from(self, other: "Referable", update_source: bool = False):
804804
"""
805-
Internal function to updates the object's attributes from another object of a similar type.
805+
Internal function to update the object's attributes from a different version of the exact same object.
806806
807807
This function should not be used directly. It is typically used by backend implementations (database adapters,
808808
protocol clients, etc.) to update the object's data, after ``update()`` has been called.
@@ -811,15 +811,31 @@ def update_from(self, other: "Referable", update_source: bool = False):
811811
:param update_source: Update the source attribute with the other's source attribute. This is not propagated
812812
recursively
813813
"""
814-
for name, var in vars(other).items():
815-
# do not update the parent, namespace_element_sets or source (depending on update_source parameter)
816-
if name in ("parent", "namespace_element_sets") or name == "source" and not update_source:
814+
for name in dir(other):
815+
# Skip private and protected attributes
816+
if name.startswith('_'):
817817
continue
818-
if isinstance(var, NamespaceSet):
818+
819+
# Do not update 'parent', 'namespace_element_sets', or 'source' (depending on update_source parameter)
820+
if name in ("parent", "namespace_element_sets") or (name == "source" and not update_source):
821+
continue
822+
823+
# Skip methods
824+
attr = getattr(other, name)
825+
if callable(attr):
826+
continue
827+
828+
if isinstance(attr, NamespaceSet):
819829
# update the elements of the NameSpaceSet
820-
vars(self)[name].update_nss_from(var)
830+
getattr(self, name).update_nss_from(attr)
821831
else:
822-
vars(self)[name] = var # that variable is not a NameSpaceSet, so it isn't Referable
832+
# Check if this is a property and if it has no setter
833+
prop = getattr(type(self), name, None)
834+
if isinstance(prop, property) and prop.fset is None:
835+
if getattr(self, name) != attr:
836+
raise ValueError(f"property {name} is immutable but has changed between versions of the object")
837+
else:
838+
setattr(self, name, attr)
823839

824840
def commit(self) -> None:
825841
"""

0 commit comments

Comments
 (0)