From 139d8e402b912d8dbbe63657f98e7a1812dfe499 Mon Sep 17 00:00:00 2001 From: zrgt Date: Mon, 11 Nov 2024 16:49:59 +0100 Subject: [PATCH 1/2] Add SetObjectStore to provider SetObjectStore is backed by a set. It does not have a problem with not updated ids as DictObjectStore (See #216). --- sdk/basyx/aas/model/provider.py | 52 ++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/sdk/basyx/aas/model/provider.py b/sdk/basyx/aas/model/provider.py index 0d21b951d..1d6fe7801 100644 --- a/sdk/basyx/aas/model/provider.py +++ b/sdk/basyx/aas/model/provider.py @@ -11,7 +11,7 @@ """ import abc -from typing import MutableSet, Iterator, Generic, TypeVar, Dict, List, Optional, Iterable +from typing import MutableSet, Iterator, Generic, TypeVar, Dict, List, Optional, Iterable, Set from .base import Identifier, Identifiable @@ -117,6 +117,56 @@ def __iter__(self) -> Iterator[_IT]: return iter(self._backend.values()) +class SetObjectStore(AbstractObjectStore[_IT], Generic[_IT]): + """ + A local in-memory object store for :class:`~basyx.aas.model.base.Identifiable` objects, backed by a set + """ + def __init__(self, objects: Iterable[_IT] = ()) -> None: + self._backend: Set[_IT] = set() + for x in objects: + self.add(x) + + def get_identifiable(self, identifier: Identifier) -> _IT: + for x in self._backend: + if x.id == identifier: + return x + raise KeyError(identifier) + + def add(self, x: _IT) -> None: + if x in self: + # Object is already in store + return + try: + self.get_identifiable(x.id) + except KeyError: + self._backend.add(x) + else: + raise KeyError(f"Identifiable object with same id {x.id} is already stored in this store") + + def discard(self, x: _IT) -> None: + self._backend.discard(x) + + def remove(self, x: _IT) -> None: + self._backend.remove(x) + + def __contains__(self, x: object) -> bool: + if isinstance(x, Identifier): + try: + self.get_identifiable(x) + return True + except KeyError: + return False + if not isinstance(x, Identifiable): + return False + return x in self._backend + + def __len__(self) -> int: + return len(self._backend) + + def __iter__(self) -> Iterator[_IT]: + return iter(self._backend) + + class ObjectProviderMultiplexer(AbstractObjectProvider): """ A multiplexer for Providers of :class:`~basyx.aas.model.base.Identifiable` objects. From 8a19ec8a1681281d68049342c2ecf9f6d2bd6de4 Mon Sep 17 00:00:00 2001 From: zrgt Date: Mon, 20 Jan 2025 16:19:26 +0100 Subject: [PATCH 2/2] Add notes to `DictObjectStore` and `SetObjectStore` --- sdk/basyx/aas/model/provider.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sdk/basyx/aas/model/provider.py b/sdk/basyx/aas/model/provider.py index 07f5e8c74..b95363315 100644 --- a/sdk/basyx/aas/model/provider.py +++ b/sdk/basyx/aas/model/provider.py @@ -84,6 +84,15 @@ class DictObjectStore(AbstractObjectStore[_IT], Generic[_IT]): """ A local in-memory object store for :class:`~basyx.aas.model.base.Identifiable` objects, backed by a dict, mapping :class:`~basyx.aas.model.base.Identifier` → :class:`~basyx.aas.model.base.Identifiable` + + .. note:: + The `DictObjectStore` provides efficient retrieval of objects by their :class:`~basyx.aas.model.base.Identifier` + However, since object stores are not referenced via the parent attribute, the mapping is not updated + if the :class:`~basyx.aas.model.base.Identifier` of an :class:`~basyx.aas.model.base.Identifiable` changes. + For more details, see [issue #216](https://github.com/eclipse-basyx/basyx-python-sdk/issues/216). + As a result, the `DictObjectStore` is unsuitable for storing objects whose + :class:`~basyx.aas.model.base.Identifier` may change. + In such cases, consider using a :class:`~.SetObjectStore` instead. """ def __init__(self, objects: Iterable[_IT] = ()) -> None: self._backend: Dict[Identifier, _IT] = {} @@ -120,6 +129,14 @@ def __iter__(self) -> Iterator[_IT]: class SetObjectStore(AbstractObjectStore[_IT], Generic[_IT]): """ A local in-memory object store for :class:`~basyx.aas.model.base.Identifiable` objects, backed by a set + + .. note:: + The `SetObjectStore` is slower than the `DictObjectStore` for retrieval of objects, because it has to iterate + over all objects to find the one with the correct :class:`~basyx.aas.model.base.Identifier`. + On the other hand, the `SetObjectStore` is more secure, because it is less affected by changes in the + :class:`~basyx.aas.model.base.Identifier` of an :class:`~basyx.aas.model.base.Identifiable` object. + Therefore, the `SetObjectStore` is suitable for storing objects whose :class:`~basyx.aas.model.base.Identifier` + may change. """ def __init__(self, objects: Iterable[_IT] = ()) -> None: self._backend: Set[_IT] = set()