Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

basyx.provider: Add SetObjectStore to provider #340

Merged
merged 3 commits into from
Jan 20, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion sdk/basyx/aas/model/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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] = {}
Expand Down Expand Up @@ -117,6 +126,64 @@ 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

.. 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()
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.
Expand Down
Loading