From f9b7f19576f03e065f5268f55cfc7998e96fc81f Mon Sep 17 00:00:00 2001 From: Parker Norton Date: Fri, 8 May 2026 13:53:21 -0600 Subject: [PATCH] Replace __getattr__ with explicit collection interface in Dimensions - Remove __getattr__ delegation to internal dict (prevents implicit exposure of mutating dict methods, fixes IDE support and pickling) - Add __contains__ for 'name in dims' syntax - Add __iter__ for 'for name in dims:' iteration - Add __len__ for len(dims) - Add explicit keys(), values(), items() methods to preserve existing API used throughout the codebase - Update exists() to use __contains__ - Update ndim to use __len__ --- pyPRMS/dimensions/Dimensions.py | 53 ++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/pyPRMS/dimensions/Dimensions.py b/pyPRMS/dimensions/Dimensions.py index 35f56d6..3b86633 100644 --- a/pyPRMS/dimensions/Dimensions.py +++ b/pyPRMS/dimensions/Dimensions.py @@ -41,17 +41,13 @@ def __init__(self, metadata: MetaDataType | None = None, # self.add(name=cdim, meta=self.metadata) # - def __getattr__(self, name: str): - """Get named dimension. + def __contains__(self, name: str) -> bool: + """Check if a dimension exists. - :param name: name of dimensions - :returns: dimension object + :param name: Name of the dimension + :returns: True if dimension exists, otherwise False """ - - # https://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html - if name == "__setstate__": - raise AttributeError(name) - return getattr(self.__dimensions, name) + return name in self.__dimensions def __getitem__(self, item: str) -> Dimension: """Get named dimension. @@ -75,6 +71,41 @@ def __str__(self) -> str: outstr += f'{vv}\n' return outstr + def __iter__(self): + """Iterate over dimension names. + + :returns: Iterator over dimension names + """ + return iter(self.__dimensions) + + def __len__(self) -> int: + """Return number of dimensions. + + :returns: Number of dimensions + """ + return len(self.__dimensions) + + def keys(self): + """Return dimension names. + + :returns: View of dimension names + """ + return self.__dimensions.keys() + + def values(self): + """Return Dimension objects. + + :returns: View of Dimension objects + """ + return self.__dimensions.values() + + def items(self): + """Return dimension name/object pairs. + + :returns: View of (name, Dimension) pairs + """ + return self.__dimensions.items() + @property def dimensions(self) -> dict[str, Dimension]: """Get ordered dictionary of Dimension objects. @@ -90,7 +121,7 @@ def ndim(self) -> int: :returns: Number of dimensions """ - return len(self.__dimensions) + return len(self) @property def xml(self) -> xmlET.Element: @@ -129,7 +160,7 @@ def exists(self, name: str) -> bool: :returns: True if dimension exists, otherwise False """ - return name in self.dimensions.keys() + return name in self def get(self, name: str) -> Dimension: """Get dimension.