From de5f3a07e40a46727e9f03d513fc8d38e943ae54 Mon Sep 17 00:00:00 2001 From: gubaidulinvadim Date: Sat, 15 Aug 2026 01:25:36 +0200 Subject: [PATCH 1/2] __pyaml_repr__ prints nested __repr__ of objects --- pyaml/common/element.py | 51 ++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/pyaml/common/element.py b/pyaml/common/element.py index 613d63b3..6410dbec 100644 --- a/pyaml/common/element.py +++ b/pyaml/common/element.py @@ -10,14 +10,9 @@ def __pyaml_repr__(obj, exclude: list[str] | None = None): """ - Returns a string representation of a pyaml object. - - Parameters - ---------- - exclude : list[str] | None - Attribute/property names to exclude from the output. + Returns a string representation of a pyaml object, + including inherited properties and one level of nested objects. """ - if exclude is None: exclude = [] @@ -34,31 +29,39 @@ def __pyaml_repr__(obj, exclude: list[str] | None = None): ) return repr(cfg).replace("ConfigModel", cls_name, 1) - # Generic fallback when there is no _cfg attrs = {} - # Instance attributes - for k, v in obj.__dict__.items(): - # Exclude private attributes and excluded - if not k.startswith("_") and k not in exclude: - attrs[k] = v + for name in dir(obj): + # Skip private attributes and user-excluded names + if name.startswith("_") or name in exclude: + continue + + try: + value = getattr(obj, name) + + # Skip methods/functions (we only want data) + # This prevents: BPM(get_name=) + if callable(value): + continue - # Properties - for name, attr in vars(type(obj)).items(): - if isinstance(attr, property) and name not in exclude: - try: - attrs[name] = getattr(obj, name) - except Exception as e: - attrs[name] = f"" + attrs[name] = value + except Exception as e: + attrs[name] = f"" + # Special handling for 'name' if it's an Element but not in attrs if isinstance(obj, Element) and "name" not in attrs and "name" not in exclude: try: attrs["name"] = obj.get_name() - except Exception as e: - attrs["name"] = f"" + except Exception: + pass + + # The !r flag ensures that if 'v' is another pyaml object, + # its own __repr__ is called (providing the "one level below" effect). + if not attrs: + return cls_name - parts = ", ".join(f"{k}={v!r}" for k, v in attrs.items()) - return f"{cls_name}({parts})" if parts else cls_name + parts = ", ".join(f"{k}={v!r}" for k, v in sorted(attrs.items())) + return f"{cls_name}({parts})" class ElementConfigModel(BaseModel): From 816ff88deb5f52874eadebefa7055a561c929fc0 Mon Sep 17 00:00:00 2001 From: gubaidulinvadim Date: Mon, 17 Aug 2026 13:23:14 +0200 Subject: [PATCH 2/2] Public _x_pos, _y_pos, _x_offset, _y_offset, _tilt_name --- pyaml/bpm/bpm.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyaml/bpm/bpm.py b/pyaml/bpm/bpm.py index ccdbc0c8..7060d152 100644 --- a/pyaml/bpm/bpm.py +++ b/pyaml/bpm/bpm.py @@ -50,11 +50,11 @@ def __init__( tilt: str | None = None, ): super().__init__(name, lattice_names, description) - self._x_pos = x_pos - self._y_pos = y_pos - self._x_offset = x_offset - self._y_offset = y_offset - self._tilt_name = tilt + self.x_pos = x_pos + self.y_pos = y_pos + self.x_offset = x_offset + self.y_offset = y_offset + self.tilt_name = tilt self._positions = None self._offset = None self._tilt = None @@ -160,7 +160,7 @@ def get_pos_devices(self) -> list[str | None]: list[DeviceAccess] Array of DeviceAcess """ - return [self._x_pos, self._y_pos] + return [self.x_pos, self.y_pos] def get_tilt_device(self) -> str | None: """ @@ -171,7 +171,7 @@ def get_tilt_device(self) -> str | None: DeviceAccess DeviceAcess """ - return self._tilt_name + return self.tilt_name def get_offset_devices(self) -> list[str | None]: """ @@ -182,7 +182,7 @@ def get_offset_devices(self) -> list[str | None]: list[DeviceAccess] Array of DeviceAcess """ - return [self._x_offset, self._y_offset] + return [self.x_offset, self.y_offset] def __repr__(self): return __pyaml_repr__(self, exclude=["positions", "offset", "tilt"])