Skip to content

Commit

Permalink
derive NamedItemList from list
Browse files Browse the repository at this point in the history
unfortunately, `mypy` does not seem to treat `UserList` as a subclass
of `List`. Thus we need to fall back to deriving from `List`, even if
this is strongly discuraged...

Signed-off-by: Andreas Lauser <[email protected]>
Signed-off-by: Katja Köhler <[email protected]>
  • Loading branch information
andlaus committed Dec 21, 2023
1 parent 7864f77 commit 4f83e27
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 29 deletions.
2 changes: 1 addition & 1 deletion examples/somersaultecu.py
Original file line number Diff line number Diff line change
Expand Up @@ -1274,7 +1274,7 @@ class SomersaultSID(IntEnum):
sdgs=[],
),
]),
all_value=None,
all_value=True,
)
}

Expand Down
77 changes: 50 additions & 27 deletions odxtools/nameditemlist.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# SPDX-License-Identifier: MIT
import abc
from collections import OrderedDict, UserList
from keyword import iskeyword
from typing import (Any, Callable, Collection, Dict, Iterable, Iterator, List, Optional, Protocol,
SupportsIndex, Tuple, TypeVar, Union, cast, overload, runtime_checkable, Generic)
from typing import (Any, Collection, Dict, Iterable, List, Optional, Protocol, SupportsIndex, Tuple,
TypeVar, Union, cast, overload, runtime_checkable)

from .exceptions import odxraise

Expand All @@ -20,7 +19,7 @@ def short_name(self) -> str:
TNamed = TypeVar("TNamed", bound=OdxNamed)


class ItemAttributeList(Generic[T], UserList):
class ItemAttributeList(List[T]):
"""A list that provides direct access to its items as named attributes.
This is a hybrid between a list and a user-defined object: One can
Expand All @@ -35,8 +34,7 @@ class ItemAttributeList(Generic[T], UserList):
"""

def __init__(self, input_list: Optional[Iterable[T]] = None) -> None:
self._item_dict: OrderedDict[str, T] = OrderedDict()
self.data: List[T] = []
self._item_dict: Dict[str, T] = {}

if input_list is not None:
for item in input_list:
Expand All @@ -53,6 +51,11 @@ def append(self, item: T) -> None:
\return The name under which item is accessible
"""
self._add_attribute_item(item)

super().append(item)

def _add_attribute_item(self, item: T) -> None:
item_name = self._get_item_key(item)

# eliminate conflicts between the name of the new item and
Expand All @@ -74,24 +77,47 @@ def append(self, item: T) -> None:
item_name = tmp

self._item_dict[item_name] = item
self.data.append(item)

def sort(self, *, key: Any = None, reverse: bool = False) -> None:
if key is None:
self._item_dict = OrderedDict(
sorted(self._item_dict.items(), key=lambda x: x[0], reverse=reverse))
else:
key_fn = cast(Callable[[T], str], key)
self._item_dict = OrderedDict(
sorted(self._item_dict.items(), key=lambda x: key_fn(x[1]), reverse=reverse))
def insert(self, index: SupportsIndex, obj: T) -> None:
self._add_attribute_item(obj)

list.insert(self, index, obj)

self.data = list(self._item_dict.values())
def remove(self, obj: T) -> None:
list.remove(self, obj)

keys = [k for (k, v) in self._item_dict.items() if v == obj]
for key in keys:
del self._item_dict[key]

def pop(self, index: SupportsIndex = -1) -> T:
result = list.pop(self, index)
keys = [k for (k, v) in self._item_dict.items() if v == result]
for key in keys:
del self._item_dict[key]
return result

def extend(self, items: Iterable[T]) -> None:
for item in items:
self.append(item)

def clear(self) -> None:
super().clear()

self._item_dict = {}

def copy(self) -> "ItemAttributeList[T]":
result = self.__class__()
for item in self:
list.append(result, item)
result._item_dict = self._item_dict.copy()
return result

def keys(self) -> Collection[str]:
return self._item_dict.keys()

def values(self) -> Collection[T]:
return self.data
return self._item_dict.values()

def items(self) -> Collection[Tuple[str, T]]:
return self._item_dict.items()
Expand All @@ -114,10 +140,8 @@ def __getitem__(self, key: slice) -> List[T]:
...

def __getitem__(self, key: Union[SupportsIndex, str, slice]) -> Union[T, List[T]]:
if isinstance(key, SupportsIndex):
return self.data[key]
elif isinstance(key, slice):
return self.data[key]
if isinstance(key, (SupportsIndex, slice)):
return super().__getitem__(key)
else:
return self._item_dict[key]

Expand All @@ -129,10 +153,9 @@ def __getattr__(self, key: str) -> T:

def get(self, key: Union[int, str], default: Optional[T] = None) -> Optional[T]:
if isinstance(key, int):
if abs(key) < -len(self._item_dict) or key >= len(self._item_dict):
return default

return self.data[key]
if 0 <= key and key < len(self):
return super().__getitem__(key)
return default
else:
return cast(Optional[T], self._item_dict.get(key, default))

Expand All @@ -146,15 +169,15 @@ def __eq__(self, other: object) -> bool:
return self._item_dict == other._item_dict

def __str__(self) -> str:
return f"[{', '.join(self._item_dict.keys())}]"
return f"[{', '.join( [self._get_item_key(x) for x in self])}]"

def __repr__(self) -> str:
return self.__str__()


class NamedItemList(ItemAttributeList[T]):

def _get_item_key(self, obj: OdxNamed) -> str:
def _get_item_key(self, obj: T) -> str:
"""Transform an object's `short_name` attribute into a valid
python identifier
Expand Down
2 changes: 1 addition & 1 deletion tests/test_odxtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class X:

# try to append an item that cannot be mapped to a name
with self.assertRaises(OdxError):
foo.append((0, 3)) # type: ignore[arg-type]
foo.append((0, 3)) # type: ignore[arg-type]

# add a keyword identifier
foo.append(X("as", 3))
Expand Down

0 comments on commit 4f83e27

Please sign in to comment.