From 3db182ac4176d65b99bb9f903746882fc9d29e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Zar=C4=99bski?= Date: Wed, 20 Aug 2025 14:38:26 +0100 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=90=9B=20Remove=20default=20attribute?= =?UTF-8?q?=20recall=20if=20read-only=20online?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simvue/api/objects/base.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/simvue/api/objects/base.py b/simvue/api/objects/base.py index cb178e44..55d5a8b5 100644 --- a/simvue/api/objects/base.py +++ b/simvue/api/objects/base.py @@ -291,13 +291,6 @@ def _get_attribute( try: return self._staging[attribute] except KeyError as e: - # If the key is not in staging, but the object is not in offline mode - # retrieve from the server and update cache instead - if not _offline_state and ( - _attribute := self._get(url=url).get(attribute) - ): - self._staging[attribute] = _attribute - return _attribute raise AttributeError( f"Could not retrieve attribute '{attribute}' " f"for {self._label} '{self._identifier}' from cached data" @@ -712,7 +705,8 @@ def __str__(self) -> str: def __repr__(self) -> str: _out_str = f"{self.__class__.__module__}.{self.__class__.__qualname__}(" _out_str += ", ".join( - f"{property}={getattr(self, property)!r}" for property in self._properties + f"{property}={getattr(self, property, 'N/A')!r}" + for property in self._properties ) _out_str += ")" return _out_str From b2498f2d3b91f5d22c1c10c9a81b1cfdaaa7bcf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Zar=C4=99bski?= Date: Wed, 20 Aug 2025 15:07:56 +0100 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=A7=AA=20Re-add=20auto-retrieve,=20in?= =?UTF-8?q?stead=20replacing=20with=20a=20local=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rather than the API attempt to retrieve a missing attribute for every item during 'get' use a local default instead. Removal led to test failures. --- simvue/api/objects/base.py | 46 +++++++++++++++++++++++++++++++++----- simvue/api/objects/run.py | 4 ++-- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/simvue/api/objects/base.py b/simvue/api/objects/base.py index 55d5a8b5..c8d9c021 100644 --- a/simvue/api/objects/base.py +++ b/simvue/api/objects/base.py @@ -183,6 +183,7 @@ def __init__( ) -> None: self._logger = logging.getLogger(f"simvue.{self.__class__.__name__}") self._label: str = getattr(self, "_label", self.__class__.__name__.lower()) + self._local: bool = _local self._read_only: bool = _read_only self._is_set: bool = False self._endpoint: str = getattr(self, "_endpoint", f"{self._label}s") @@ -232,7 +233,7 @@ def __init__( if ( not self._identifier.startswith("offline_") and self._read_only - and not _local + and not self._local ): self._staging = self._get() @@ -277,8 +278,34 @@ def _stage_to_other(self, obj_label: str, key: str, value: typing.Any) -> None: json.dump(_staged_data, out_f, indent=2) def _get_attribute( - self, attribute: str, *default, url: str | None = None - ) -> typing.Any: + self, + attribute: str, + *, + local_default: object | None = None, + url: str | None = None, + ) -> object: + """Retrieve an attribute for the given object. + + The argument 'local_default' refers to the value to return when + retrieving objects of this type via 'get'. In this case, if a key + is not present (likely due to the user specifying key=False on retrieval) + we do not want to attempt to retrieve the value from the server, as doing + so for every item would cause significant overhead. Instead we use this value. + + Parameters + ---------- + attribute : str + name of attribute to retrieve + local_default : str | None, optional + if specified, the default value to return if the object is in 'local' mode. + url : str | None, optional + alternative URL to use for retrieval. + + Returns + ------- + object + the attribute value + """ # In the case where the object is read-only, staging is the data # already retrieved from the server _attribute_is_property: bool = attribute in self._properties @@ -291,6 +318,15 @@ def _get_attribute( try: return self._staging[attribute] except KeyError as e: + if self._local: + return local_default + # If the key is not in staging, but the object is not in offline mode + # retrieve from the server and update cache instead + if not _offline_state and ( + _attribute := self._get(url=url).get(attribute) + ): + self._staging[attribute] = _attribute + return _attribute raise AttributeError( f"Could not retrieve attribute '{attribute}' " f"for {self._label} '{self._identifier}' from cached data" @@ -302,8 +338,8 @@ def _get_attribute( ) return self._get(url=url)[attribute] except KeyError as e: - if default: - return default[0] + if local_default: + return local_default if self._offline: raise AttributeError( diff --git a/simvue/api/objects/run.py b/simvue/api/objects/run.py index c00b3f9e..56be8533 100644 --- a/simvue/api/objects/run.py +++ b/simvue/api/objects/run.py @@ -372,7 +372,7 @@ def get_alert_details(self) -> typing.Generator[dict[str, typing.Any], None, Non raise RuntimeError( "Cannot get alert details from an offline run - use .alerts to access a list of IDs instead" ) - for alert in self._get_attribute("alerts"): + for alert in self._get_attribute("alerts", local_default=[]): yield alert["alert"] @property @@ -457,7 +457,7 @@ def metrics( ------- Generator[tuple[str, dict[str, int | float | bool]] """ - yield from self._get_attribute("metrics").items() + yield from self._get_attribute("metrics", local_default={}).items() @property def events( From 7adb62e4d963901b4814e9e72fc47509fbe2a200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Zar=C4=99bski?= Date: Wed, 20 Aug 2025 15:54:29 +0100 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Keep=20KeyError=20when?= =?UTF-8?q?=20user=20attempts=20to=20access=20attribute=20they=20didnt=20a?= =?UTF-8?q?sk=20for?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simvue/api/objects/base.py | 33 ++++++++++++++++----------------- simvue/api/objects/run.py | 4 ++-- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/simvue/api/objects/base.py b/simvue/api/objects/base.py index c8d9c021..e26c6481 100644 --- a/simvue/api/objects/base.py +++ b/simvue/api/objects/base.py @@ -7,6 +7,7 @@ import abc import pathlib +import types import typing import inspect import uuid @@ -281,23 +282,14 @@ def _get_attribute( self, attribute: str, *, - local_default: object | None = None, url: str | None = None, ) -> object: """Retrieve an attribute for the given object. - The argument 'local_default' refers to the value to return when - retrieving objects of this type via 'get'. In this case, if a key - is not present (likely due to the user specifying key=False on retrieval) - we do not want to attempt to retrieve the value from the server, as doing - so for every item would cause significant overhead. Instead we use this value. - Parameters ---------- attribute : str name of attribute to retrieve - local_default : str | None, optional - if specified, the default value to return if the object is in 'local' mode. url : str | None, optional alternative URL to use for retrieval. @@ -319,7 +311,7 @@ def _get_attribute( return self._staging[attribute] except KeyError as e: if self._local: - return local_default + raise e # If the key is not in staging, but the object is not in offline mode # retrieve from the server and update cache instead if not _offline_state and ( @@ -338,9 +330,6 @@ def _get_attribute( ) return self._get(url=url)[attribute] except KeyError as e: - if local_default: - return local_default - if self._offline: raise AttributeError( f"A value for attribute '{attribute}' has " @@ -740,9 +729,19 @@ def __str__(self) -> str: def __repr__(self) -> str: _out_str = f"{self.__class__.__module__}.{self.__class__.__qualname__}(" - _out_str += ", ".join( - f"{property}={getattr(self, property, 'N/A')!r}" - for property in self._properties - ) + _property_values: list[str] = [] + + for property in self._properties: + try: + _value = getattr(self, property) + except KeyError: + continue + + if isinstance(_value, types.GeneratorType): + continue + + _property_values.append(f"{property}={_value!r}") + + _out_str += ", ".join(_property_values) _out_str += ")" return _out_str diff --git a/simvue/api/objects/run.py b/simvue/api/objects/run.py index 56be8533..c00b3f9e 100644 --- a/simvue/api/objects/run.py +++ b/simvue/api/objects/run.py @@ -372,7 +372,7 @@ def get_alert_details(self) -> typing.Generator[dict[str, typing.Any], None, Non raise RuntimeError( "Cannot get alert details from an offline run - use .alerts to access a list of IDs instead" ) - for alert in self._get_attribute("alerts", local_default=[]): + for alert in self._get_attribute("alerts"): yield alert["alert"] @property @@ -457,7 +457,7 @@ def metrics( ------- Generator[tuple[str, dict[str, int | float | bool]] """ - yield from self._get_attribute("metrics", local_default={}).items() + yield from self._get_attribute("metrics").items() @property def events(