|
from typing import Iterable, List, Optional |
|
|
|
|
|
class Order: |
|
""" |
|
A list-like structure to specify the order of items in a dictionary. |
|
The main characteristics are: |
|
- Append and insert only. Cannot remove elements. This is not strictly required |
|
by the use-case but probably good practice. |
|
- Elements must be unique. Inserting an element which is already in the list |
|
will throw an error. |
|
|
|
Primarily useful for specifying the order in which UI elements |
|
should be shown in Wave. |
|
""" |
|
|
|
def __init__(self, keys: Optional[List[str]] = None): |
|
if keys is not None: |
|
self._list = list(keys) |
|
else: |
|
self._list = list() |
|
|
|
def _unique_guard(self, *keys: str): |
|
for key in keys: |
|
if key in self._list: |
|
raise ValueError(f"`{key}` is already in the list!") |
|
|
|
def append(self, key: str): |
|
""" |
|
Append a key at the end of the list: |
|
|
|
Args: |
|
key: String to append. |
|
|
|
Raises: |
|
- `ValueError` if the key is already in the list. |
|
""" |
|
|
|
self._unique_guard(key) |
|
|
|
self._list.append(key) |
|
|
|
def extend(self, keys: Iterable[str]): |
|
""" |
|
Extend the list by multiple keys. |
|
|
|
Args: |
|
keys: Iterable of keys. |
|
|
|
Raises: |
|
- `ValueError` if one or more key is already in the list. |
|
""" |
|
|
|
self._unique_guard(*keys) |
|
|
|
self._list.extend(keys) |
|
|
|
def insert( |
|
self, *keys: str, before: Optional[str] = None, after: Optional[str] = None |
|
): |
|
""" |
|
Insert one or more keys. Either `before` or `after`, but not both, must be set |
|
to determine position. |
|
|
|
Args: |
|
keys: One more keys to insert. |
|
after: A key immediately after which the `keys` will be inserted. |
|
before: A key immediately before which the `keys` are inserted. |
|
|
|
Raises: |
|
- `ValueError` if one or more key is already in the list. |
|
- `ValueError` if `before` / `after` does not exist in the list. |
|
- `ValueError` if an invalid combination of arguments is set. |
|
""" |
|
|
|
self._unique_guard(*keys) |
|
|
|
if before is not None: |
|
for key in keys[::-1]: |
|
self._list.insert(self._list.index(before), key) |
|
|
|
if after is not None: |
|
raise ValueError("`after` must be None if `before` is set.") |
|
|
|
if after is not None: |
|
for key in keys[::-1]: |
|
self._list.insert(self._list.index(after) + 1, key) |
|
|
|
if before is not None: |
|
raise ValueError("`before` must be None if `after` is set.") |
|
|
|
if before is None and after is None: |
|
raise ValueError("Either `before` or `after` must be set.") |
|
|
|
def __getitem__(self, idx): |
|
return self._list[idx] |
|
|
|
def __len__(self): |
|
return len(self._list) |
|
|
|
def __iter__(self): |
|
return iter(self._list) |
|
|
|
|
|
def test_order(): |
|
order = Order(["dataset", "training", "validation", "logging"]) |
|
|
|
order.insert("architecture", before="training") |
|
order.insert("environment", after="validation") |
|
|
|
assert [item for item in order] == [ |
|
"dataset", |
|
"architecture", |
|
"training", |
|
"validation", |
|
"environment", |
|
"logging", |
|
] |
|
|